Let the enterprise build be able to update the colors.
This commit is contained in:
committed by
Benoit Marty
parent
447f6a1b11
commit
8b703ed046
@@ -324,6 +324,7 @@ licensee {
|
||||
allowUrl("https://jsoup.org/license")
|
||||
allowUrl("https://asm.ow2.io/license.html")
|
||||
allowUrl("https://www.gnu.org/licenses/agpl-3.0.txt")
|
||||
allowUrl("https://github.com/mhssn95/compose-color-picker/blob/main/LICENSE")
|
||||
ignoreDependencies("com.github.matrix-org", "matrix-analytics-events")
|
||||
// Ignore dependency that are not third-party licenses to us.
|
||||
ignoreDependencies(groupId = "io.element.android")
|
||||
|
||||
Submodule enterprise updated: ffc02b8d0f...58f37695d2
@@ -5,7 +5,7 @@
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
plugins {
|
||||
id("io.element.android-library")
|
||||
id("io.element.android-compose-library")
|
||||
}
|
||||
|
||||
android {
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
|
||||
package io.element.android.features.enterprise.api
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.State
|
||||
import io.element.android.compound.tokens.generated.SemanticColors
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
@@ -17,8 +19,17 @@ interface EnterpriseService {
|
||||
fun defaultHomeserverList(): List<String>
|
||||
suspend fun isAllowedToConnectToHomeserver(homeserverUrl: String): Boolean
|
||||
|
||||
fun semanticColorsLight(): SemanticColors
|
||||
fun semanticColorsDark(): SemanticColors
|
||||
/**
|
||||
* Override the brand color.
|
||||
* @param brandColor the color in hex format (#RRGGBBAA or #RRGGBB), or null to reset to default.
|
||||
*/
|
||||
fun overrideBrandColor(brandColor: String?)
|
||||
|
||||
@Composable
|
||||
fun semanticColorsLight(): State<SemanticColors>
|
||||
|
||||
@Composable
|
||||
fun semanticColorsDark(): State<SemanticColors>
|
||||
|
||||
fun firebasePushGateway(): String?
|
||||
fun unifiedPushDefaultPushGateway(): String?
|
||||
|
||||
@@ -8,7 +8,7 @@ import extension.testCommonDependencies
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
plugins {
|
||||
id("io.element.android-library")
|
||||
id("io.element.android-compose-library")
|
||||
}
|
||||
|
||||
android {
|
||||
|
||||
@@ -7,6 +7,10 @@
|
||||
|
||||
package io.element.android.features.enterprise.impl
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import dev.zacsweers.metro.AppScope
|
||||
import dev.zacsweers.metro.ContributesBinding
|
||||
import dev.zacsweers.metro.Inject
|
||||
@@ -28,9 +32,17 @@ class DefaultEnterpriseService : EnterpriseService {
|
||||
override fun defaultHomeserverList(): List<String> = emptyList()
|
||||
override suspend fun isAllowedToConnectToHomeserver(homeserverUrl: String) = true
|
||||
|
||||
override fun semanticColorsLight(): SemanticColors = compoundColorsLight
|
||||
override fun overrideBrandColor(brandColor: String?) = Unit
|
||||
|
||||
override fun semanticColorsDark(): SemanticColors = compoundColorsDark
|
||||
@Composable
|
||||
override fun semanticColorsLight(): State<SemanticColors> {
|
||||
return remember { derivedStateOf { compoundColorsLight } }
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun semanticColorsDark(): State<SemanticColors> {
|
||||
return remember { derivedStateOf { compoundColorsDark } }
|
||||
}
|
||||
|
||||
override fun firebasePushGateway(): String? = null
|
||||
override fun unifiedPushDefaultPushGateway(): String? = null
|
||||
|
||||
@@ -7,7 +7,12 @@
|
||||
|
||||
package io.element.android.features.enterprise.impl
|
||||
|
||||
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.compound.tokens.generated.compoundColorsDark
|
||||
import io.element.android.compound.tokens.generated.compoundColorsLight
|
||||
import io.element.android.libraries.matrix.test.A_HOMESERVER_URL
|
||||
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
||||
import kotlinx.coroutines.test.runTest
|
||||
@@ -37,4 +42,30 @@ class DefaultEnterpriseServiceTest {
|
||||
val defaultEnterpriseService = DefaultEnterpriseService()
|
||||
assertThat(defaultEnterpriseService.isEnterpriseUser(A_SESSION_ID)).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `semanticColorsLight always emits the same value`() = runTest {
|
||||
val defaultEnterpriseService = DefaultEnterpriseService()
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
defaultEnterpriseService.semanticColorsLight().value
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState).isEqualTo(compoundColorsLight)
|
||||
defaultEnterpriseService.overrideBrandColor("#87654321")
|
||||
expectNoEvents()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `semanticColorsDark always emits the same value`() = runTest {
|
||||
val defaultEnterpriseService = DefaultEnterpriseService()
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
defaultEnterpriseService.semanticColorsDark().value
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState).isEqualTo(compoundColorsDark)
|
||||
defaultEnterpriseService.overrideBrandColor("#87654321")
|
||||
expectNoEvents()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
|
||||
package io.element.android.features.enterprise.test
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.State
|
||||
import io.element.android.compound.tokens.generated.SemanticColors
|
||||
import io.element.android.features.enterprise.api.BugReportUrl
|
||||
import io.element.android.features.enterprise.api.EnterpriseService
|
||||
@@ -22,8 +24,9 @@ class FakeEnterpriseService(
|
||||
private val isEnterpriseUserResult: (SessionId) -> Boolean = { lambdaError() },
|
||||
private val defaultHomeserverListResult: () -> List<String> = { emptyList() },
|
||||
private val isAllowedToConnectToHomeserverResult: (String) -> Boolean = { lambdaError() },
|
||||
private val semanticColorsLightResult: () -> SemanticColors = { lambdaError() },
|
||||
private val semanticColorsDarkResult: () -> SemanticColors = { lambdaError() },
|
||||
private val semanticColorsLightResult: () -> State<SemanticColors> = { lambdaError() },
|
||||
private val semanticColorsDarkResult: () -> State<SemanticColors> = { lambdaError() },
|
||||
private val overrideBrandColorResult: (String?) -> Unit = { lambdaError() },
|
||||
private val firebasePushGatewayResult: () -> String? = { lambdaError() },
|
||||
private val unifiedPushDefaultPushGatewayResult: () -> String? = { lambdaError() },
|
||||
) : EnterpriseService {
|
||||
@@ -39,11 +42,17 @@ class FakeEnterpriseService(
|
||||
isAllowedToConnectToHomeserverResult(homeserverUrl)
|
||||
}
|
||||
|
||||
override fun semanticColorsLight(): SemanticColors {
|
||||
override fun overrideBrandColor(brandColor: String?) {
|
||||
overrideBrandColorResult(brandColor)
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun semanticColorsLight(): State<SemanticColors> {
|
||||
return semanticColorsLightResult()
|
||||
}
|
||||
|
||||
override fun semanticColorsDark(): SemanticColors {
|
||||
@Composable
|
||||
override fun semanticColorsDark(): State<SemanticColors> {
|
||||
return semanticColorsDarkResult()
|
||||
}
|
||||
|
||||
|
||||
@@ -37,8 +37,8 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVoiceContent
|
||||
import io.element.android.features.messages.impl.timeline.protection.TimelineProtectionEvent
|
||||
import io.element.android.features.messages.impl.timeline.protection.TimelineProtectionState
|
||||
import io.element.android.libraries.designsystem.colors.gradientSubtleColors
|
||||
import io.element.android.libraries.designsystem.modifiers.onKeyboardContextMenuAction
|
||||
import io.element.android.libraries.designsystem.modifiers.subtleColorStops
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.text.toPx
|
||||
@@ -227,12 +227,12 @@ private fun Modifier.focusedEvent(
|
||||
} else {
|
||||
ElementTheme.colors.borderAccentSubtle
|
||||
}
|
||||
val gradientColors = subtleColorStops(isEnterpriseBuild)
|
||||
val gradientColors = gradientSubtleColors()
|
||||
val verticalOffset = focusedEventOffset.toPx()
|
||||
val verticalRatio = 0.7f
|
||||
return drawWithCache {
|
||||
val brush = Brush.verticalGradient(
|
||||
colorStops = gradientColors,
|
||||
colors = gradientColors,
|
||||
endY = size.height * verticalRatio,
|
||||
)
|
||||
onDrawBehind {
|
||||
|
||||
@@ -72,6 +72,7 @@ dependencies {
|
||||
implementation(projects.features.rageshake.api)
|
||||
implementation(projects.features.lockscreen.api)
|
||||
implementation(projects.features.analytics.api)
|
||||
implementation(projects.features.enterprise.api)
|
||||
implementation(projects.features.licenses.api)
|
||||
implementation(projects.features.logout.api)
|
||||
implementation(projects.features.deactivation.api)
|
||||
@@ -83,6 +84,7 @@ dependencies {
|
||||
implementation(projects.services.toolbox.api)
|
||||
implementation(libs.datetime)
|
||||
implementation(libs.coil.compose)
|
||||
implementation(libs.color.picker)
|
||||
implementation(libs.androidx.browser)
|
||||
implementation(libs.androidx.datastore.preferences)
|
||||
api(projects.features.preferences.api)
|
||||
@@ -100,6 +102,7 @@ dependencies {
|
||||
testImplementation(projects.libraries.preferences.test)
|
||||
testImplementation(projects.libraries.push.test)
|
||||
testImplementation(projects.libraries.pushstore.test)
|
||||
testImplementation(projects.features.enterprise.test)
|
||||
testImplementation(projects.features.invite.test)
|
||||
testImplementation(projects.features.rageshake.test)
|
||||
testImplementation(projects.features.logout.test)
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
package io.element.android.features.preferences.impl.developer
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import io.element.android.features.preferences.impl.developer.tracing.LogLevelItem
|
||||
import io.element.android.libraries.featureflag.ui.model.FeatureUiModel
|
||||
import io.element.android.libraries.matrix.api.tracing.TraceLogPack
|
||||
@@ -16,5 +17,7 @@ sealed interface DeveloperSettingsEvents {
|
||||
data class SetCustomElementCallBaseUrl(val baseUrl: String?) : DeveloperSettingsEvents
|
||||
data class SetTracingLogLevel(val logLevel: LogLevelItem) : DeveloperSettingsEvents
|
||||
data class ToggleTracingLogPack(val logPack: TraceLogPack, val enabled: Boolean) : DeveloperSettingsEvents
|
||||
data class SetShowColorPicker(val show: Boolean) : DeveloperSettingsEvents
|
||||
data class ChangeBrandColor(val color: Color) : DeveloperSettingsEvents
|
||||
data object ClearCache : DeveloperSettingsEvents
|
||||
}
|
||||
|
||||
@@ -18,8 +18,10 @@ import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.produceState
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.snapshots.SnapshotStateMap
|
||||
import dev.zacsweers.metro.Inject
|
||||
import io.element.android.features.enterprise.api.EnterpriseService
|
||||
import io.element.android.features.preferences.impl.developer.tracing.toLogLevel
|
||||
import io.element.android.features.preferences.impl.developer.tracing.toLogLevelItem
|
||||
import io.element.android.features.preferences.impl.tasks.ClearCacheUseCase
|
||||
@@ -54,6 +56,7 @@ class DeveloperSettingsPresenter(
|
||||
private val rageshakePresenter: Presenter<RageshakePreferencesState>,
|
||||
private val appPreferencesStore: AppPreferencesStore,
|
||||
private val buildMeta: BuildMeta,
|
||||
private val enterpriseService: EnterpriseService,
|
||||
) : Presenter<DeveloperSettingsState> {
|
||||
@Composable
|
||||
override fun present(): DeveloperSettingsState {
|
||||
@@ -71,6 +74,9 @@ class DeveloperSettingsPresenter(
|
||||
val clearCacheAction = remember {
|
||||
mutableStateOf<AsyncAction<Unit>>(AsyncAction.Uninitialized)
|
||||
}
|
||||
var showColorPicker by remember {
|
||||
mutableStateOf(false)
|
||||
}
|
||||
val customElementCallBaseUrl by remember {
|
||||
appPreferencesStore
|
||||
.getCustomElementCallBaseUrlFlow()
|
||||
@@ -136,6 +142,14 @@ class DeveloperSettingsPresenter(
|
||||
}
|
||||
appPreferencesStore.setTracingLogPacks(currentPacks)
|
||||
}
|
||||
is DeveloperSettingsEvents.ChangeBrandColor -> {
|
||||
showColorPicker = false
|
||||
val color = event.color.value.toHexString(HexFormat.UpperCase).substring(2, 8)
|
||||
enterpriseService.overrideBrandColor(color)
|
||||
}
|
||||
is DeveloperSettingsEvents.SetShowColorPicker -> {
|
||||
showColorPicker = event.show
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,6 +164,8 @@ class DeveloperSettingsPresenter(
|
||||
),
|
||||
tracingLogLevel = tracingLogLevel,
|
||||
tracingLogPacks = tracingLogPacks,
|
||||
isEnterpriseBuild = enterpriseService.isEnterpriseBuild,
|
||||
showColorPicker = showColorPicker,
|
||||
eventSink = ::handleEvents
|
||||
)
|
||||
}
|
||||
|
||||
@@ -23,6 +23,8 @@ data class DeveloperSettingsState(
|
||||
val customElementCallBaseUrlState: CustomElementCallBaseUrlState,
|
||||
val tracingLogLevel: AsyncData<LogLevelItem>,
|
||||
val tracingLogPacks: ImmutableList<TraceLogPack>,
|
||||
val isEnterpriseBuild: Boolean,
|
||||
val showColorPicker: Boolean,
|
||||
val eventSink: (DeveloperSettingsEvents) -> Unit
|
||||
)
|
||||
|
||||
|
||||
@@ -28,6 +28,10 @@ open class DeveloperSettingsStateProvider : PreviewParameterProvider<DeveloperSe
|
||||
baseUrl = "https://call.element.ahoy",
|
||||
)
|
||||
),
|
||||
aDeveloperSettingsState(
|
||||
isEnterpriseBuild = true,
|
||||
showColorPicker = true,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -35,6 +39,8 @@ fun aDeveloperSettingsState(
|
||||
clearCacheAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
|
||||
customElementCallBaseUrlState: CustomElementCallBaseUrlState = aCustomElementCallBaseUrlState(),
|
||||
traceLogPacks: List<TraceLogPack> = emptyList(),
|
||||
isEnterpriseBuild: Boolean = false,
|
||||
showColorPicker: Boolean = false,
|
||||
eventSink: (DeveloperSettingsEvents) -> Unit = {},
|
||||
) = DeveloperSettingsState(
|
||||
features = aFeatureUiModelList(),
|
||||
@@ -44,6 +50,8 @@ fun aDeveloperSettingsState(
|
||||
customElementCallBaseUrlState = customElementCallBaseUrlState,
|
||||
tracingLogLevel = AsyncData.Success(LogLevelItem.INFO),
|
||||
tracingLogPacks = traceLogPacks.toImmutableList(),
|
||||
isEnterpriseBuild = isEnterpriseBuild,
|
||||
showColorPicker = showColorPicker,
|
||||
eventSink = eventSink,
|
||||
)
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.progressSemantics
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
@@ -36,8 +37,11 @@ import io.element.android.libraries.featureflag.ui.FeatureListView
|
||||
import io.element.android.libraries.featureflag.ui.model.FeatureUiModel
|
||||
import io.element.android.libraries.matrix.api.tracing.TraceLogPack
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import io.mhssn.colorpicker.ColorPickerDialog
|
||||
import io.mhssn.colorpicker.ColorPickerType
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
@OptIn(ExperimentalComposeUiApi::class)
|
||||
@Composable
|
||||
fun DeveloperSettingsView(
|
||||
state: DeveloperSettingsState,
|
||||
@@ -99,6 +103,18 @@ fun DeveloperSettingsView(
|
||||
RageshakePreferencesView(
|
||||
state = state.rageshakeState,
|
||||
)
|
||||
if (state.isEnterpriseBuild) {
|
||||
PreferenceCategory(title = "Theme", showTopDivider = false) {
|
||||
ListItem(
|
||||
headlineContent = {
|
||||
Text("Change brand color")
|
||||
},
|
||||
onClick = {
|
||||
state.eventSink(DeveloperSettingsEvents.SetShowColorPicker(true))
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
PreferenceCategory(title = "Crash", showTopDivider = false) {
|
||||
ListItem(
|
||||
headlineContent = {
|
||||
@@ -133,6 +149,18 @@ fun DeveloperSettingsView(
|
||||
)
|
||||
}
|
||||
}
|
||||
ColorPickerDialog(
|
||||
show = state.showColorPicker,
|
||||
type = ColorPickerType.Classic(
|
||||
showAlphaBar = false,
|
||||
),
|
||||
onDismissRequest = {
|
||||
state.eventSink(DeveloperSettingsEvents.SetShowColorPicker(false))
|
||||
},
|
||||
onPickedColor = {
|
||||
state.eventSink(DeveloperSettingsEvents.ChangeBrandColor(it))
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
@@ -189,7 +217,9 @@ private fun FeatureListContent(
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun DeveloperSettingsViewPreview(@PreviewParameter(DeveloperSettingsStateProvider::class) state: DeveloperSettingsState) = ElementPreview {
|
||||
internal fun DeveloperSettingsViewPreview(
|
||||
@PreviewParameter(DeveloperSettingsStateProvider::class) state: DeveloperSettingsState
|
||||
) = ElementPreview {
|
||||
DeveloperSettingsView(
|
||||
state = state,
|
||||
onOpenShowkase = {},
|
||||
|
||||
@@ -9,7 +9,10 @@
|
||||
|
||||
package io.element.android.features.preferences.impl.developer
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.features.enterprise.api.EnterpriseService
|
||||
import io.element.android.features.enterprise.test.FakeEnterpriseService
|
||||
import io.element.android.features.preferences.impl.developer.tracing.LogLevelItem
|
||||
import io.element.android.features.preferences.impl.tasks.FakeClearCacheUseCase
|
||||
import io.element.android.features.preferences.impl.tasks.FakeComputeCacheSizeUseCase
|
||||
@@ -24,6 +27,8 @@ import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
|
||||
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
||||
import io.element.android.libraries.preferences.test.InMemoryAppPreferencesStore
|
||||
import io.element.android.tests.testutils.WarmUpRule
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import io.element.android.tests.testutils.lambda.value
|
||||
import io.element.android.tests.testutils.test
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.runTest
|
||||
@@ -57,6 +62,8 @@ class DeveloperSettingsPresenterTest {
|
||||
assertThat(state.rageshakeState.isSupported).isTrue()
|
||||
assertThat(state.rageshakeState.sensitivity).isEqualTo(0.3f)
|
||||
assertThat(state.tracingLogLevel).isEqualTo(AsyncData.Uninitialized)
|
||||
assertThat(state.isEnterpriseBuild).isFalse()
|
||||
assertThat(state.showColorPicker).isFalse()
|
||||
}
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.features).isNotEmpty()
|
||||
@@ -170,6 +177,32 @@ class DeveloperSettingsPresenterTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - enterprise build can change the brand color`() = runTest {
|
||||
val overrideBrandColorResult = lambdaRecorder<String?, Unit> { }
|
||||
val presenter = createDeveloperSettingsPresenter(
|
||||
enterpriseService = FakeEnterpriseService(
|
||||
isEnterpriseBuild = true,
|
||||
overrideBrandColorResult = overrideBrandColorResult,
|
||||
)
|
||||
)
|
||||
presenter.test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.isEnterpriseBuild).isTrue()
|
||||
initialState.eventSink(DeveloperSettingsEvents.SetShowColorPicker(true))
|
||||
assertThat(awaitItem().showColorPicker).isTrue()
|
||||
initialState.eventSink(DeveloperSettingsEvents.SetShowColorPicker(false))
|
||||
assertThat(awaitItem().showColorPicker).isFalse()
|
||||
initialState.eventSink(DeveloperSettingsEvents.SetShowColorPicker(true))
|
||||
assertThat(awaitItem().showColorPicker).isTrue()
|
||||
initialState.eventSink(DeveloperSettingsEvents.ChangeBrandColor(Color.Green))
|
||||
assertThat(awaitItem().showColorPicker).isFalse()
|
||||
overrideBrandColorResult.assertions().isCalledOnce()
|
||||
.with(value("00FF00"))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - won't display features in labs or finished`() = runTest {
|
||||
val availableFeatures = listOf(
|
||||
@@ -219,6 +252,7 @@ class DeveloperSettingsPresenterTest {
|
||||
clearCacheUseCase: FakeClearCacheUseCase = FakeClearCacheUseCase(),
|
||||
preferencesStore: InMemoryAppPreferencesStore = InMemoryAppPreferencesStore(),
|
||||
buildMeta: BuildMeta = aBuildMeta(),
|
||||
enterpriseService: EnterpriseService = FakeEnterpriseService(),
|
||||
): DeveloperSettingsPresenter {
|
||||
return DeveloperSettingsPresenter(
|
||||
featureFlagService = featureFlagService,
|
||||
@@ -227,6 +261,7 @@ class DeveloperSettingsPresenterTest {
|
||||
rageshakePresenter = { aRageshakePreferencesState() },
|
||||
appPreferencesStore = preferencesStore,
|
||||
buildMeta = buildMeta,
|
||||
enterpriseService = enterpriseService,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,6 +203,7 @@ opusencoder = "io.element.android:opusencoder:1.2.0"
|
||||
zxing_cpp = "io.github.zxing-cpp:android:2.3.0"
|
||||
haze = { module = "dev.chrisbanes.haze:haze", version.ref = "haze" }
|
||||
haze_materials = { module = "dev.chrisbanes.haze:haze-materials", version.ref = "haze" }
|
||||
color_picker = "io.mhssn:colorpicker:1.0.0"
|
||||
|
||||
# Analytics
|
||||
posthog = "com.posthog:posthog-android:3.23.0"
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* 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.libraries.androidutils.assets
|
||||
|
||||
import android.content.Context
|
||||
import dev.zacsweers.metro.Inject
|
||||
import io.element.android.libraries.di.annotations.ApplicationContext
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
/**
|
||||
* Read asset files.
|
||||
*/
|
||||
@Inject
|
||||
class AssetReader(
|
||||
@ApplicationContext private val context: Context,
|
||||
) {
|
||||
private val cache = ConcurrentHashMap<String, String?>()
|
||||
|
||||
/**
|
||||
* Read an asset from resource and return a String or null in case of error.
|
||||
*
|
||||
* @param assetFilename Asset filename
|
||||
* @return the content of the asset file, or null in case of error
|
||||
*/
|
||||
fun readAssetFile(assetFilename: String): String? {
|
||||
return cache.getOrPut(assetFilename, {
|
||||
return try {
|
||||
context.assets.open(assetFilename)
|
||||
.use { asset ->
|
||||
buildString {
|
||||
var ch = asset.read()
|
||||
while (ch != -1) {
|
||||
append(ch.toChar())
|
||||
ch = asset.read()
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "## readAssetFile() failed")
|
||||
null
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fun clearCache() {
|
||||
cache.clear()
|
||||
}
|
||||
}
|
||||
@@ -15,13 +15,10 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.libraries.designsystem.colors.gradientSubtleColors
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.theme.LocalBuildMeta
|
||||
|
||||
/**
|
||||
* Ref: https://www.figma.com/design/kcnHxunG1LDWXsJhaNuiHz/ER-145--Workspaces-V1?node-id=1141-24692
|
||||
@@ -30,35 +27,15 @@ import io.element.android.libraries.designsystem.theme.LocalBuildMeta
|
||||
@Composable
|
||||
fun Modifier.backgroundVerticalGradient(
|
||||
isVisible: Boolean = true,
|
||||
isEnterpriseBuild: Boolean = LocalBuildMeta.current.isEnterpriseBuild,
|
||||
): Modifier {
|
||||
if (!isVisible) return this
|
||||
return background(
|
||||
brush = Brush.verticalGradient(
|
||||
colorStops = subtleColorStops(isEnterpriseBuild),
|
||||
colors = gradientSubtleColors(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun subtleColorStops(
|
||||
isEnterpriseBuild: Boolean = LocalBuildMeta.current.isEnterpriseBuild,
|
||||
): Array<Pair<Float, Color>> {
|
||||
return buildList {
|
||||
if (isEnterpriseBuild) {
|
||||
// For enterprise builds, ensure that we are theming the gradient
|
||||
add(0f to ElementTheme.colors.textActionAccent.copy(alpha = 0.5f))
|
||||
add(0.75f to ElementTheme.colors.bgCanvasDefault)
|
||||
add(1f to Color.Transparent)
|
||||
} else {
|
||||
val colors = gradientSubtleColors()
|
||||
colors.forEachIndexed { index, color ->
|
||||
add(index.toFloat() / (colors.size - 1) to color)
|
||||
}
|
||||
}
|
||||
}.toTypedArray()
|
||||
}
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun BackgroundVerticalGradientPreview() = ElementPreview {
|
||||
@@ -70,19 +47,6 @@ internal fun BackgroundVerticalGradientPreview() = ElementPreview {
|
||||
)
|
||||
}
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun BackgroundVerticalGradientEnterprisePreview() = ElementPreview {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(height = 100.dp)
|
||||
.backgroundVerticalGradient(
|
||||
isEnterpriseBuild = true,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun BackgroundVerticalGradientDisabledPreview() = ElementPreview {
|
||||
|
||||
@@ -70,8 +70,8 @@ fun ElementThemeApp(
|
||||
}
|
||||
)
|
||||
}
|
||||
val compoundLight = remember { enterpriseService.semanticColorsLight() }
|
||||
val compoundDark = remember { enterpriseService.semanticColorsDark() }
|
||||
val compoundLight by enterpriseService.semanticColorsLight()
|
||||
val compoundDark by enterpriseService.semanticColorsDark()
|
||||
CompositionLocalProvider(
|
||||
LocalBuildMeta provides buildMeta,
|
||||
) {
|
||||
|
||||
@@ -79,7 +79,6 @@ class KonsistPreviewTest {
|
||||
"AsyncIndicatorFailurePreview",
|
||||
"AsyncIndicatorLoadingPreview",
|
||||
"BackgroundVerticalGradientDisabledPreview",
|
||||
"BackgroundVerticalGradientEnterprisePreview",
|
||||
"BackgroundVerticalGradientPreview",
|
||||
"ColorAliasesPreview",
|
||||
"DefaultRoomListTopBarMultiAccountPreview",
|
||||
|
||||
Reference in New Issue
Block a user