Merge pull request #5542 from element-hq/feature/bma/assetReader
Improve colors customization
This commit is contained in:
commit
42c775d740
33 changed files with 258 additions and 133 deletions
|
|
@ -324,6 +324,7 @@ licensee {
|
||||||
allowUrl("https://jsoup.org/license")
|
allowUrl("https://jsoup.org/license")
|
||||||
allowUrl("https://asm.ow2.io/license.html")
|
allowUrl("https://asm.ow2.io/license.html")
|
||||||
allowUrl("https://www.gnu.org/licenses/agpl-3.0.txt")
|
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")
|
ignoreDependencies("com.github.matrix-org", "matrix-analytics-events")
|
||||||
// Ignore dependency that are not third-party licenses to us.
|
// Ignore dependency that are not third-party licenses to us.
|
||||||
ignoreDependencies(groupId = "io.element.android")
|
ignoreDependencies(groupId = "io.element.android")
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit ffc02b8d0f35188c3ef8a876dc1532bfe3e533da
|
Subproject commit 38992f58ef472520a2192696c1bbf20e3066191e
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
plugins {
|
plugins {
|
||||||
id("io.element.android-library")
|
id("io.element.android-compose-library")
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@
|
||||||
|
|
||||||
package io.element.android.features.enterprise.api
|
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.compound.tokens.generated.SemanticColors
|
||||||
import io.element.android.libraries.matrix.api.core.SessionId
|
import io.element.android.libraries.matrix.api.core.SessionId
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
@ -17,8 +19,17 @@ interface EnterpriseService {
|
||||||
fun defaultHomeserverList(): List<String>
|
fun defaultHomeserverList(): List<String>
|
||||||
suspend fun isAllowedToConnectToHomeserver(homeserverUrl: String): Boolean
|
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 firebasePushGateway(): String?
|
||||||
fun unifiedPushDefaultPushGateway(): String?
|
fun unifiedPushDefaultPushGateway(): String?
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import extension.testCommonDependencies
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
plugins {
|
plugins {
|
||||||
id("io.element.android-library")
|
id("io.element.android-compose-library")
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,10 @@
|
||||||
|
|
||||||
package io.element.android.features.enterprise.impl
|
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.AppScope
|
||||||
import dev.zacsweers.metro.ContributesBinding
|
import dev.zacsweers.metro.ContributesBinding
|
||||||
import dev.zacsweers.metro.Inject
|
import dev.zacsweers.metro.Inject
|
||||||
|
|
@ -28,9 +32,17 @@ class DefaultEnterpriseService : EnterpriseService {
|
||||||
override fun defaultHomeserverList(): List<String> = emptyList()
|
override fun defaultHomeserverList(): List<String> = emptyList()
|
||||||
override suspend fun isAllowedToConnectToHomeserver(homeserverUrl: String) = true
|
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 firebasePushGateway(): String? = null
|
||||||
override fun unifiedPushDefaultPushGateway(): String? = null
|
override fun unifiedPushDefaultPushGateway(): String? = null
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,12 @@
|
||||||
|
|
||||||
package io.element.android.features.enterprise.impl
|
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 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_HOMESERVER_URL
|
||||||
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
|
|
@ -37,4 +42,30 @@ class DefaultEnterpriseServiceTest {
|
||||||
val defaultEnterpriseService = DefaultEnterpriseService()
|
val defaultEnterpriseService = DefaultEnterpriseService()
|
||||||
assertThat(defaultEnterpriseService.isEnterpriseUser(A_SESSION_ID)).isFalse()
|
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
|
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.compound.tokens.generated.SemanticColors
|
||||||
import io.element.android.features.enterprise.api.BugReportUrl
|
import io.element.android.features.enterprise.api.BugReportUrl
|
||||||
import io.element.android.features.enterprise.api.EnterpriseService
|
import io.element.android.features.enterprise.api.EnterpriseService
|
||||||
|
|
@ -22,8 +24,9 @@ class FakeEnterpriseService(
|
||||||
private val isEnterpriseUserResult: (SessionId) -> Boolean = { lambdaError() },
|
private val isEnterpriseUserResult: (SessionId) -> Boolean = { lambdaError() },
|
||||||
private val defaultHomeserverListResult: () -> List<String> = { emptyList() },
|
private val defaultHomeserverListResult: () -> List<String> = { emptyList() },
|
||||||
private val isAllowedToConnectToHomeserverResult: (String) -> Boolean = { lambdaError() },
|
private val isAllowedToConnectToHomeserverResult: (String) -> Boolean = { lambdaError() },
|
||||||
private val semanticColorsLightResult: () -> SemanticColors = { lambdaError() },
|
private val semanticColorsLightResult: () -> State<SemanticColors> = { lambdaError() },
|
||||||
private val semanticColorsDarkResult: () -> SemanticColors = { lambdaError() },
|
private val semanticColorsDarkResult: () -> State<SemanticColors> = { lambdaError() },
|
||||||
|
private val overrideBrandColorResult: (String?) -> Unit = { lambdaError() },
|
||||||
private val firebasePushGatewayResult: () -> String? = { lambdaError() },
|
private val firebasePushGatewayResult: () -> String? = { lambdaError() },
|
||||||
private val unifiedPushDefaultPushGatewayResult: () -> String? = { lambdaError() },
|
private val unifiedPushDefaultPushGatewayResult: () -> String? = { lambdaError() },
|
||||||
) : EnterpriseService {
|
) : EnterpriseService {
|
||||||
|
|
@ -39,11 +42,17 @@ class FakeEnterpriseService(
|
||||||
isAllowedToConnectToHomeserverResult(homeserverUrl)
|
isAllowedToConnectToHomeserverResult(homeserverUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun semanticColorsLight(): SemanticColors {
|
override fun overrideBrandColor(brandColor: String?) {
|
||||||
|
overrideBrandColorResult(brandColor)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
override fun semanticColorsLight(): State<SemanticColors> {
|
||||||
return semanticColorsLightResult()
|
return semanticColorsLightResult()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun semanticColorsDark(): SemanticColors {
|
@Composable
|
||||||
|
override fun semanticColorsDark(): State<SemanticColors> {
|
||||||
return semanticColorsDarkResult()
|
return semanticColorsDarkResult()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -37,12 +37,11 @@ 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.model.event.TimelineItemVoiceContent
|
||||||
import io.element.android.features.messages.impl.timeline.protection.TimelineProtectionEvent
|
import io.element.android.features.messages.impl.timeline.protection.TimelineProtectionEvent
|
||||||
import io.element.android.features.messages.impl.timeline.protection.TimelineProtectionState
|
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.onKeyboardContextMenuAction
|
||||||
import io.element.android.libraries.designsystem.modifiers.subtleColorStops
|
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
import io.element.android.libraries.designsystem.text.toPx
|
import io.element.android.libraries.designsystem.text.toPx
|
||||||
import io.element.android.libraries.designsystem.theme.LocalBuildMeta
|
|
||||||
import io.element.android.libraries.matrix.api.core.EventId
|
import io.element.android.libraries.matrix.api.core.EventId
|
||||||
import io.element.android.libraries.matrix.api.timeline.Timeline
|
import io.element.android.libraries.matrix.api.timeline.Timeline
|
||||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||||
|
|
@ -220,19 +219,14 @@ internal fun TimelineItemRow(
|
||||||
@Composable
|
@Composable
|
||||||
private fun Modifier.focusedEvent(
|
private fun Modifier.focusedEvent(
|
||||||
focusedEventOffset: Dp,
|
focusedEventOffset: Dp,
|
||||||
isEnterpriseBuild: Boolean = LocalBuildMeta.current.isEnterpriseBuild,
|
|
||||||
): Modifier {
|
): Modifier {
|
||||||
val highlightedLineColor = if (isEnterpriseBuild) {
|
val highlightedLineColor = ElementTheme.colors.borderAccentSubtle
|
||||||
ElementTheme.colors.textActionAccent
|
val gradientColors = gradientSubtleColors()
|
||||||
} else {
|
|
||||||
ElementTheme.colors.borderAccentSubtle
|
|
||||||
}
|
|
||||||
val gradientColors = subtleColorStops(isEnterpriseBuild)
|
|
||||||
val verticalOffset = focusedEventOffset.toPx()
|
val verticalOffset = focusedEventOffset.toPx()
|
||||||
val verticalRatio = 0.7f
|
val verticalRatio = 0.7f
|
||||||
return drawWithCache {
|
return drawWithCache {
|
||||||
val brush = Brush.verticalGradient(
|
val brush = Brush.verticalGradient(
|
||||||
colorStops = gradientColors,
|
colors = gradientColors,
|
||||||
endY = size.height * verticalRatio,
|
endY = size.height * verticalRatio,
|
||||||
)
|
)
|
||||||
onDrawBehind {
|
onDrawBehind {
|
||||||
|
|
@ -261,18 +255,3 @@ internal fun FocusedEventPreview() = ElementPreview {
|
||||||
.focusedEvent(0.dp),
|
.focusedEvent(0.dp),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@PreviewsDayNight
|
|
||||||
@Composable
|
|
||||||
internal fun FocusedEventEnterprisePreview() = ElementPreview {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(16.dp)
|
|
||||||
.fillMaxWidth()
|
|
||||||
.height(160.dp)
|
|
||||||
.focusedEvent(
|
|
||||||
focusedEventOffset = 0.dp,
|
|
||||||
isEnterpriseBuild = true,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,7 @@ dependencies {
|
||||||
implementation(projects.features.rageshake.api)
|
implementation(projects.features.rageshake.api)
|
||||||
implementation(projects.features.lockscreen.api)
|
implementation(projects.features.lockscreen.api)
|
||||||
implementation(projects.features.analytics.api)
|
implementation(projects.features.analytics.api)
|
||||||
|
implementation(projects.features.enterprise.api)
|
||||||
implementation(projects.features.licenses.api)
|
implementation(projects.features.licenses.api)
|
||||||
implementation(projects.features.logout.api)
|
implementation(projects.features.logout.api)
|
||||||
implementation(projects.features.deactivation.api)
|
implementation(projects.features.deactivation.api)
|
||||||
|
|
@ -83,6 +84,7 @@ dependencies {
|
||||||
implementation(projects.services.toolbox.api)
|
implementation(projects.services.toolbox.api)
|
||||||
implementation(libs.datetime)
|
implementation(libs.datetime)
|
||||||
implementation(libs.coil.compose)
|
implementation(libs.coil.compose)
|
||||||
|
implementation(libs.color.picker)
|
||||||
implementation(libs.androidx.browser)
|
implementation(libs.androidx.browser)
|
||||||
implementation(libs.androidx.datastore.preferences)
|
implementation(libs.androidx.datastore.preferences)
|
||||||
api(projects.features.preferences.api)
|
api(projects.features.preferences.api)
|
||||||
|
|
@ -100,6 +102,7 @@ dependencies {
|
||||||
testImplementation(projects.libraries.preferences.test)
|
testImplementation(projects.libraries.preferences.test)
|
||||||
testImplementation(projects.libraries.push.test)
|
testImplementation(projects.libraries.push.test)
|
||||||
testImplementation(projects.libraries.pushstore.test)
|
testImplementation(projects.libraries.pushstore.test)
|
||||||
|
testImplementation(projects.features.enterprise.test)
|
||||||
testImplementation(projects.features.invite.test)
|
testImplementation(projects.features.invite.test)
|
||||||
testImplementation(projects.features.rageshake.test)
|
testImplementation(projects.features.rageshake.test)
|
||||||
testImplementation(projects.features.logout.test)
|
testImplementation(projects.features.logout.test)
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
package io.element.android.features.preferences.impl.developer
|
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.features.preferences.impl.developer.tracing.LogLevelItem
|
||||||
import io.element.android.libraries.featureflag.ui.model.FeatureUiModel
|
import io.element.android.libraries.featureflag.ui.model.FeatureUiModel
|
||||||
import io.element.android.libraries.matrix.api.tracing.TraceLogPack
|
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 SetCustomElementCallBaseUrl(val baseUrl: String?) : DeveloperSettingsEvents
|
||||||
data class SetTracingLogLevel(val logLevel: LogLevelItem) : DeveloperSettingsEvents
|
data class SetTracingLogLevel(val logLevel: LogLevelItem) : DeveloperSettingsEvents
|
||||||
data class ToggleTracingLogPack(val logPack: TraceLogPack, val enabled: Boolean) : 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
|
data object ClearCache : DeveloperSettingsEvents
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,10 @@ import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.produceState
|
import androidx.compose.runtime.produceState
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.runtime.snapshots.SnapshotStateMap
|
import androidx.compose.runtime.snapshots.SnapshotStateMap
|
||||||
import dev.zacsweers.metro.Inject
|
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.toLogLevel
|
||||||
import io.element.android.features.preferences.impl.developer.tracing.toLogLevelItem
|
import io.element.android.features.preferences.impl.developer.tracing.toLogLevelItem
|
||||||
import io.element.android.features.preferences.impl.tasks.ClearCacheUseCase
|
import io.element.android.features.preferences.impl.tasks.ClearCacheUseCase
|
||||||
|
|
@ -54,6 +56,7 @@ class DeveloperSettingsPresenter(
|
||||||
private val rageshakePresenter: Presenter<RageshakePreferencesState>,
|
private val rageshakePresenter: Presenter<RageshakePreferencesState>,
|
||||||
private val appPreferencesStore: AppPreferencesStore,
|
private val appPreferencesStore: AppPreferencesStore,
|
||||||
private val buildMeta: BuildMeta,
|
private val buildMeta: BuildMeta,
|
||||||
|
private val enterpriseService: EnterpriseService,
|
||||||
) : Presenter<DeveloperSettingsState> {
|
) : Presenter<DeveloperSettingsState> {
|
||||||
@Composable
|
@Composable
|
||||||
override fun present(): DeveloperSettingsState {
|
override fun present(): DeveloperSettingsState {
|
||||||
|
|
@ -71,6 +74,9 @@ class DeveloperSettingsPresenter(
|
||||||
val clearCacheAction = remember {
|
val clearCacheAction = remember {
|
||||||
mutableStateOf<AsyncAction<Unit>>(AsyncAction.Uninitialized)
|
mutableStateOf<AsyncAction<Unit>>(AsyncAction.Uninitialized)
|
||||||
}
|
}
|
||||||
|
var showColorPicker by remember {
|
||||||
|
mutableStateOf(false)
|
||||||
|
}
|
||||||
val customElementCallBaseUrl by remember {
|
val customElementCallBaseUrl by remember {
|
||||||
appPreferencesStore
|
appPreferencesStore
|
||||||
.getCustomElementCallBaseUrlFlow()
|
.getCustomElementCallBaseUrlFlow()
|
||||||
|
|
@ -136,6 +142,14 @@ class DeveloperSettingsPresenter(
|
||||||
}
|
}
|
||||||
appPreferencesStore.setTracingLogPacks(currentPacks)
|
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,
|
tracingLogLevel = tracingLogLevel,
|
||||||
tracingLogPacks = tracingLogPacks,
|
tracingLogPacks = tracingLogPacks,
|
||||||
|
isEnterpriseBuild = enterpriseService.isEnterpriseBuild,
|
||||||
|
showColorPicker = showColorPicker,
|
||||||
eventSink = ::handleEvents
|
eventSink = ::handleEvents
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,8 @@ data class DeveloperSettingsState(
|
||||||
val customElementCallBaseUrlState: CustomElementCallBaseUrlState,
|
val customElementCallBaseUrlState: CustomElementCallBaseUrlState,
|
||||||
val tracingLogLevel: AsyncData<LogLevelItem>,
|
val tracingLogLevel: AsyncData<LogLevelItem>,
|
||||||
val tracingLogPacks: ImmutableList<TraceLogPack>,
|
val tracingLogPacks: ImmutableList<TraceLogPack>,
|
||||||
|
val isEnterpriseBuild: Boolean,
|
||||||
|
val showColorPicker: Boolean,
|
||||||
val eventSink: (DeveloperSettingsEvents) -> Unit
|
val eventSink: (DeveloperSettingsEvents) -> Unit
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,10 @@ open class DeveloperSettingsStateProvider : PreviewParameterProvider<DeveloperSe
|
||||||
baseUrl = "https://call.element.ahoy",
|
baseUrl = "https://call.element.ahoy",
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
aDeveloperSettingsState(
|
||||||
|
isEnterpriseBuild = true,
|
||||||
|
showColorPicker = true,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -35,6 +39,8 @@ fun aDeveloperSettingsState(
|
||||||
clearCacheAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
|
clearCacheAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
|
||||||
customElementCallBaseUrlState: CustomElementCallBaseUrlState = aCustomElementCallBaseUrlState(),
|
customElementCallBaseUrlState: CustomElementCallBaseUrlState = aCustomElementCallBaseUrlState(),
|
||||||
traceLogPacks: List<TraceLogPack> = emptyList(),
|
traceLogPacks: List<TraceLogPack> = emptyList(),
|
||||||
|
isEnterpriseBuild: Boolean = false,
|
||||||
|
showColorPicker: Boolean = false,
|
||||||
eventSink: (DeveloperSettingsEvents) -> Unit = {},
|
eventSink: (DeveloperSettingsEvents) -> Unit = {},
|
||||||
) = DeveloperSettingsState(
|
) = DeveloperSettingsState(
|
||||||
features = aFeatureUiModelList(),
|
features = aFeatureUiModelList(),
|
||||||
|
|
@ -44,6 +50,8 @@ fun aDeveloperSettingsState(
|
||||||
customElementCallBaseUrlState = customElementCallBaseUrlState,
|
customElementCallBaseUrlState = customElementCallBaseUrlState,
|
||||||
tracingLogLevel = AsyncData.Success(LogLevelItem.INFO),
|
tracingLogLevel = AsyncData.Success(LogLevelItem.INFO),
|
||||||
tracingLogPacks = traceLogPacks.toImmutableList(),
|
tracingLogPacks = traceLogPacks.toImmutableList(),
|
||||||
|
isEnterpriseBuild = isEnterpriseBuild,
|
||||||
|
showColorPicker = showColorPicker,
|
||||||
eventSink = eventSink,
|
eventSink = eventSink,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.progressSemantics
|
import androidx.compose.foundation.progressSemantics
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.input.KeyboardType
|
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.featureflag.ui.model.FeatureUiModel
|
||||||
import io.element.android.libraries.matrix.api.tracing.TraceLogPack
|
import io.element.android.libraries.matrix.api.tracing.TraceLogPack
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
|
import io.mhssn.colorpicker.ColorPickerDialog
|
||||||
|
import io.mhssn.colorpicker.ColorPickerType
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
|
||||||
|
@OptIn(ExperimentalComposeUiApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun DeveloperSettingsView(
|
fun DeveloperSettingsView(
|
||||||
state: DeveloperSettingsState,
|
state: DeveloperSettingsState,
|
||||||
|
|
@ -54,7 +58,6 @@ fun DeveloperSettingsView(
|
||||||
// Note: this is OK to hardcode strings in this debug screen.
|
// Note: this is OK to hardcode strings in this debug screen.
|
||||||
PreferenceCategory(
|
PreferenceCategory(
|
||||||
title = "Feature flags",
|
title = "Feature flags",
|
||||||
showTopDivider = true,
|
|
||||||
) {
|
) {
|
||||||
FeatureListContent(state)
|
FeatureListContent(state)
|
||||||
}
|
}
|
||||||
|
|
@ -99,7 +102,27 @@ fun DeveloperSettingsView(
|
||||||
RageshakePreferencesView(
|
RageshakePreferencesView(
|
||||||
state = state.rageshakeState,
|
state = state.rageshakeState,
|
||||||
)
|
)
|
||||||
PreferenceCategory(title = "Crash", showTopDivider = false) {
|
if (state.isEnterpriseBuild) {
|
||||||
|
PreferenceCategory(title = "Theme") {
|
||||||
|
ListItem(
|
||||||
|
headlineContent = {
|
||||||
|
Text("Change brand color")
|
||||||
|
},
|
||||||
|
onClick = {
|
||||||
|
state.eventSink(DeveloperSettingsEvents.SetShowColorPicker(true))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
ListItem(
|
||||||
|
headlineContent = {
|
||||||
|
Text("Reset brand color")
|
||||||
|
},
|
||||||
|
onClick = {
|
||||||
|
state.eventSink(DeveloperSettingsEvents.ChangeBrandColor(null))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PreferenceCategory(title = "Crash") {
|
||||||
ListItem(
|
ListItem(
|
||||||
headlineContent = {
|
headlineContent = {
|
||||||
Text("Crash the app 💥")
|
Text("Crash the app 💥")
|
||||||
|
|
@ -108,7 +131,7 @@ fun DeveloperSettingsView(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
val cache = state.cacheSize
|
val cache = state.cacheSize
|
||||||
PreferenceCategory(title = "Cache", showTopDivider = false) {
|
PreferenceCategory(title = "Cache") {
|
||||||
ListItem(
|
ListItem(
|
||||||
headlineContent = {
|
headlineContent = {
|
||||||
Text("Clear cache")
|
Text("Clear cache")
|
||||||
|
|
@ -133,13 +156,25 @@ fun DeveloperSettingsView(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ColorPickerDialog(
|
||||||
|
show = state.showColorPicker,
|
||||||
|
type = ColorPickerType.Classic(
|
||||||
|
showAlphaBar = false,
|
||||||
|
),
|
||||||
|
onDismissRequest = {
|
||||||
|
state.eventSink(DeveloperSettingsEvents.SetShowColorPicker(false))
|
||||||
|
},
|
||||||
|
onPickedColor = {
|
||||||
|
state.eventSink(DeveloperSettingsEvents.ChangeBrandColor(it))
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ElementCallCategory(
|
private fun ElementCallCategory(
|
||||||
state: DeveloperSettingsState,
|
state: DeveloperSettingsState,
|
||||||
) {
|
) {
|
||||||
PreferenceCategory(title = "Element Call", showTopDivider = true) {
|
PreferenceCategory(title = "Element Call") {
|
||||||
val callUrlState = state.customElementCallBaseUrlState
|
val callUrlState = state.customElementCallBaseUrlState
|
||||||
|
|
||||||
val supportingText = if (callUrlState.baseUrl.isNullOrEmpty()) {
|
val supportingText = if (callUrlState.baseUrl.isNullOrEmpty()) {
|
||||||
|
|
@ -189,7 +224,9 @@ private fun FeatureListContent(
|
||||||
|
|
||||||
@PreviewsDayNight
|
@PreviewsDayNight
|
||||||
@Composable
|
@Composable
|
||||||
internal fun DeveloperSettingsViewPreview(@PreviewParameter(DeveloperSettingsStateProvider::class) state: DeveloperSettingsState) = ElementPreview {
|
internal fun DeveloperSettingsViewPreview(
|
||||||
|
@PreviewParameter(DeveloperSettingsStateProvider::class) state: DeveloperSettingsState
|
||||||
|
) = ElementPreview {
|
||||||
DeveloperSettingsView(
|
DeveloperSettingsView(
|
||||||
state = state,
|
state = state,
|
||||||
onOpenShowkase = {},
|
onOpenShowkase = {},
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,10 @@
|
||||||
|
|
||||||
package io.element.android.features.preferences.impl.developer
|
package io.element.android.features.preferences.impl.developer
|
||||||
|
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
import com.google.common.truth.Truth.assertThat
|
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.developer.tracing.LogLevelItem
|
||||||
import io.element.android.features.preferences.impl.tasks.FakeClearCacheUseCase
|
import io.element.android.features.preferences.impl.tasks.FakeClearCacheUseCase
|
||||||
import io.element.android.features.preferences.impl.tasks.FakeComputeCacheSizeUseCase
|
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.matrix.test.core.aBuildMeta
|
||||||
import io.element.android.libraries.preferences.test.InMemoryAppPreferencesStore
|
import io.element.android.libraries.preferences.test.InMemoryAppPreferencesStore
|
||||||
import io.element.android.tests.testutils.WarmUpRule
|
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 io.element.android.tests.testutils.test
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
|
|
@ -57,6 +62,8 @@ class DeveloperSettingsPresenterTest {
|
||||||
assertThat(state.rageshakeState.isSupported).isTrue()
|
assertThat(state.rageshakeState.isSupported).isTrue()
|
||||||
assertThat(state.rageshakeState.sensitivity).isEqualTo(0.3f)
|
assertThat(state.rageshakeState.sensitivity).isEqualTo(0.3f)
|
||||||
assertThat(state.tracingLogLevel).isEqualTo(AsyncData.Uninitialized)
|
assertThat(state.tracingLogLevel).isEqualTo(AsyncData.Uninitialized)
|
||||||
|
assertThat(state.isEnterpriseBuild).isFalse()
|
||||||
|
assertThat(state.showColorPicker).isFalse()
|
||||||
}
|
}
|
||||||
awaitItem().also { state ->
|
awaitItem().also { state ->
|
||||||
assertThat(state.features).isNotEmpty()
|
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
|
@Test
|
||||||
fun `present - won't display features in labs or finished`() = runTest {
|
fun `present - won't display features in labs or finished`() = runTest {
|
||||||
val availableFeatures = listOf(
|
val availableFeatures = listOf(
|
||||||
|
|
@ -219,6 +252,7 @@ class DeveloperSettingsPresenterTest {
|
||||||
clearCacheUseCase: FakeClearCacheUseCase = FakeClearCacheUseCase(),
|
clearCacheUseCase: FakeClearCacheUseCase = FakeClearCacheUseCase(),
|
||||||
preferencesStore: InMemoryAppPreferencesStore = InMemoryAppPreferencesStore(),
|
preferencesStore: InMemoryAppPreferencesStore = InMemoryAppPreferencesStore(),
|
||||||
buildMeta: BuildMeta = aBuildMeta(),
|
buildMeta: BuildMeta = aBuildMeta(),
|
||||||
|
enterpriseService: EnterpriseService = FakeEnterpriseService(),
|
||||||
): DeveloperSettingsPresenter {
|
): DeveloperSettingsPresenter {
|
||||||
return DeveloperSettingsPresenter(
|
return DeveloperSettingsPresenter(
|
||||||
featureFlagService = featureFlagService,
|
featureFlagService = featureFlagService,
|
||||||
|
|
@ -227,6 +261,7 @@ class DeveloperSettingsPresenterTest {
|
||||||
rageshakePresenter = { aRageshakePreferencesState() },
|
rageshakePresenter = { aRageshakePreferencesState() },
|
||||||
appPreferencesStore = preferencesStore,
|
appPreferencesStore = preferencesStore,
|
||||||
buildMeta = buildMeta,
|
buildMeta = buildMeta,
|
||||||
|
enterpriseService = enterpriseService,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -93,6 +93,7 @@ androidx_constraintlayout_compose = { module = "androidx.constraintlayout:constr
|
||||||
androidx_camera_lifecycle = { module = "androidx.camera:camera-lifecycle", version.ref = "camera" }
|
androidx_camera_lifecycle = { module = "androidx.camera:camera-lifecycle", version.ref = "camera" }
|
||||||
androidx_camera_view = { module = "androidx.camera:camera-view", version.ref = "camera" }
|
androidx_camera_view = { module = "androidx.camera:camera-view", version.ref = "camera" }
|
||||||
androidx_camera_camera2 = { module = "androidx.camera:camera-camera2", version.ref = "camera" }
|
androidx_camera_camera2 = { module = "androidx.camera:camera-camera2", version.ref = "camera" }
|
||||||
|
androidx_javascriptengine = "androidx.javascriptengine:javascriptengine:1.0.0"
|
||||||
|
|
||||||
androidx_recyclerview = "androidx.recyclerview:recyclerview:1.4.0"
|
androidx_recyclerview = "androidx.recyclerview:recyclerview:1.4.0"
|
||||||
androidx_browser = "androidx.browser:browser:1.9.0"
|
androidx_browser = "androidx.browser:browser:1.9.0"
|
||||||
|
|
@ -126,6 +127,7 @@ androidx_compose_material_icons = { module = "androidx.compose.material:material
|
||||||
|
|
||||||
# Coroutines
|
# Coroutines
|
||||||
coroutines_core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" }
|
coroutines_core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" }
|
||||||
|
coroutines_guava = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-guava", version.ref = "coroutines" }
|
||||||
coroutines_test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "coroutines" }
|
coroutines_test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "coroutines" }
|
||||||
|
|
||||||
# Accompanist
|
# Accompanist
|
||||||
|
|
@ -203,6 +205,7 @@ opusencoder = "io.element.android:opusencoder:1.2.0"
|
||||||
zxing_cpp = "io.github.zxing-cpp:android:2.3.0"
|
zxing_cpp = "io.github.zxing-cpp:android:2.3.0"
|
||||||
haze = { module = "dev.chrisbanes.haze:haze", version.ref = "haze" }
|
haze = { module = "dev.chrisbanes.haze:haze", version.ref = "haze" }
|
||||||
haze_materials = { module = "dev.chrisbanes.haze:haze-materials", version.ref = "haze" }
|
haze_materials = { module = "dev.chrisbanes.haze:haze-materials", version.ref = "haze" }
|
||||||
|
color_picker = "io.mhssn:colorpicker:1.0.0"
|
||||||
|
|
||||||
# Analytics
|
# Analytics
|
||||||
posthog = "com.posthog:posthog-android:3.23.0"
|
posthog = "com.posthog:posthog-android:3.23.0"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
/*
|
||||||
|
* 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 { it.bufferedReader().readText() }
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Timber.e(e, "## readAssetFile() failed")
|
||||||
|
null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearCache() {
|
||||||
|
cache.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -37,12 +37,10 @@ import androidx.compose.ui.graphics.Shape
|
||||||
import androidx.compose.ui.graphics.graphicsLayer
|
import androidx.compose.ui.graphics.graphicsLayer
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import io.element.android.compound.annotations.CoreColorToken
|
import io.element.android.compound.annotations.CoreColorToken
|
||||||
import io.element.android.compound.theme.ElementTheme
|
|
||||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||||
import io.element.android.compound.tokens.generated.internal.LightColorTokens
|
import io.element.android.libraries.designsystem.colors.gradientActionColors
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
import io.element.android.libraries.designsystem.theme.LocalBuildMeta
|
|
||||||
import io.element.android.libraries.designsystem.theme.components.Icon
|
import io.element.android.libraries.designsystem.theme.components.Icon
|
||||||
|
|
||||||
@OptIn(CoreColorToken::class)
|
@OptIn(CoreColorToken::class)
|
||||||
|
|
@ -53,26 +51,14 @@ fun GradientFloatingActionButton(
|
||||||
shape: Shape = RoundedCornerShape(25),
|
shape: Shape = RoundedCornerShape(25),
|
||||||
content: @Composable () -> Unit,
|
content: @Composable () -> Unit,
|
||||||
) {
|
) {
|
||||||
val color1 = if (LocalBuildMeta.current.isEnterpriseBuild) {
|
val colors = gradientActionColors()
|
||||||
ElementTheme.colors.textActionAccent
|
|
||||||
} else {
|
|
||||||
LightColorTokens.colorGreen700
|
|
||||||
}
|
|
||||||
val color2 = if (LocalBuildMeta.current.isEnterpriseBuild) {
|
|
||||||
ElementTheme.colors.textActionAccent
|
|
||||||
} else {
|
|
||||||
LightColorTokens.colorBlue900
|
|
||||||
}
|
|
||||||
val linearShaderBrush = remember {
|
val linearShaderBrush = remember {
|
||||||
object : ShaderBrush() {
|
object : ShaderBrush() {
|
||||||
override fun createShader(size: Size): Shader {
|
override fun createShader(size: Size): Shader {
|
||||||
return LinearGradientShader(
|
return LinearGradientShader(
|
||||||
from = Offset(size.width, size.height),
|
from = Offset(size.width, size.height),
|
||||||
to = Offset(size.width, 0f),
|
to = Offset(size.width, 0f),
|
||||||
colors = listOf(
|
colors = colors,
|
||||||
color2,
|
|
||||||
color1,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -83,10 +69,7 @@ fun GradientFloatingActionButton(
|
||||||
return RadialGradientShader(
|
return RadialGradientShader(
|
||||||
center = size.center,
|
center = size.center,
|
||||||
radius = size.width / 2,
|
radius = size.width / 2,
|
||||||
colors = listOf(
|
colors = colors,
|
||||||
color1,
|
|
||||||
color2,
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,6 @@ import io.element.android.compound.theme.ElementTheme
|
||||||
import io.element.android.libraries.designsystem.colors.gradientActionColors
|
import io.element.android.libraries.designsystem.colors.gradientActionColors
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
import io.element.android.libraries.designsystem.theme.LocalBuildMeta
|
|
||||||
import io.element.android.libraries.designsystem.theme.components.ButtonSize
|
import io.element.android.libraries.designsystem.theme.components.ButtonSize
|
||||||
import io.element.android.libraries.designsystem.theme.components.HorizontalDivider
|
import io.element.android.libraries.designsystem.theme.components.HorizontalDivider
|
||||||
import io.element.android.libraries.designsystem.theme.components.lowHorizontalPaddingValue
|
import io.element.android.libraries.designsystem.theme.components.lowHorizontalPaddingValue
|
||||||
|
|
@ -63,15 +62,7 @@ fun SuperButton(
|
||||||
ButtonSize.Small -> PaddingValues(horizontal = 16.dp, vertical = 5.dp)
|
ButtonSize.Small -> PaddingValues(horizontal = 16.dp, vertical = 5.dp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val colors = if (LocalBuildMeta.current.isEnterpriseBuild) {
|
val colors = gradientActionColors()
|
||||||
listOf(
|
|
||||||
ElementTheme.colors.textActionAccent,
|
|
||||||
ElementTheme.colors.textActionAccent,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
gradientActionColors()
|
|
||||||
}
|
|
||||||
|
|
||||||
val shaderBrush = remember(colors) {
|
val shaderBrush = remember(colors) {
|
||||||
object : ShaderBrush() {
|
object : ShaderBrush() {
|
||||||
override fun createShader(size: Size): Shader {
|
override fun createShader(size: Size): Shader {
|
||||||
|
|
|
||||||
|
|
@ -15,13 +15,10 @@ import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Brush
|
import androidx.compose.ui.graphics.Brush
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.unit.dp
|
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.colors.gradientSubtleColors
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
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
|
* 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
|
@Composable
|
||||||
fun Modifier.backgroundVerticalGradient(
|
fun Modifier.backgroundVerticalGradient(
|
||||||
isVisible: Boolean = true,
|
isVisible: Boolean = true,
|
||||||
isEnterpriseBuild: Boolean = LocalBuildMeta.current.isEnterpriseBuild,
|
|
||||||
): Modifier {
|
): Modifier {
|
||||||
if (!isVisible) return this
|
if (!isVisible) return this
|
||||||
return background(
|
return background(
|
||||||
brush = Brush.verticalGradient(
|
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
|
@PreviewsDayNight
|
||||||
@Composable
|
@Composable
|
||||||
internal fun BackgroundVerticalGradientPreview() = ElementPreview {
|
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
|
@PreviewsDayNight
|
||||||
@Composable
|
@Composable
|
||||||
internal fun BackgroundVerticalGradientDisabledPreview() = ElementPreview {
|
internal fun BackgroundVerticalGradientDisabledPreview() = ElementPreview {
|
||||||
|
|
|
||||||
|
|
@ -70,8 +70,8 @@ fun ElementThemeApp(
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
val compoundLight = remember { enterpriseService.semanticColorsLight() }
|
val compoundLight by enterpriseService.semanticColorsLight()
|
||||||
val compoundDark = remember { enterpriseService.semanticColorsDark() }
|
val compoundDark by enterpriseService.semanticColorsDark()
|
||||||
CompositionLocalProvider(
|
CompositionLocalProvider(
|
||||||
LocalBuildMeta provides buildMeta,
|
LocalBuildMeta provides buildMeta,
|
||||||
) {
|
) {
|
||||||
|
|
|
||||||
|
|
@ -79,12 +79,10 @@ class KonsistPreviewTest {
|
||||||
"AsyncIndicatorFailurePreview",
|
"AsyncIndicatorFailurePreview",
|
||||||
"AsyncIndicatorLoadingPreview",
|
"AsyncIndicatorLoadingPreview",
|
||||||
"BackgroundVerticalGradientDisabledPreview",
|
"BackgroundVerticalGradientDisabledPreview",
|
||||||
"BackgroundVerticalGradientEnterprisePreview",
|
|
||||||
"BackgroundVerticalGradientPreview",
|
"BackgroundVerticalGradientPreview",
|
||||||
"ColorAliasesPreview",
|
"ColorAliasesPreview",
|
||||||
"DefaultRoomListTopBarMultiAccountPreview",
|
"DefaultRoomListTopBarMultiAccountPreview",
|
||||||
"DefaultRoomListTopBarWithIndicatorPreview",
|
"DefaultRoomListTopBarWithIndicatorPreview",
|
||||||
"FocusedEventEnterprisePreview",
|
|
||||||
"FocusedEventPreview",
|
"FocusedEventPreview",
|
||||||
"GradientFloatingActionButtonCircleShapePreview",
|
"GradientFloatingActionButtonCircleShapePreview",
|
||||||
"HeaderFooterPageScrollablePreview",
|
"HeaderFooterPageScrollablePreview",
|
||||||
|
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
|
||||||
oid sha256:6349c9b5e5697b05e8c12896cf5e230edfe09330323c4b1658dfd84d3ebdaa38
|
|
||||||
size 9664
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
|
||||||
oid sha256:34bfc1aa893118f9e5f3da63896fcf85f946f0072c460d7f8a4f1d5722b6ec17
|
|
||||||
size 9075
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:77847b759a0795c7c4f6ed529c0d20ffa060983c06196061f3ff61171774d1ab
|
||||||
|
size 39412
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:dea0cb97b7f8363fb4581da232c2ec6cd5c7ad17a9dcbf5339b275f590e53a8e
|
||||||
|
size 38794
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:d78695ba9a688d64996fb13ffa27f2870b613141c3bc4ca9ae565fc97de00e1e
|
oid sha256:45e0eed913d8987940b6745620e229c8ce1659f2603b3bc4a1d19d6753742f6d
|
||||||
size 10522
|
size 12394
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:35b75a3b70d0b8b5d1279b99ba715150ecf8a8d64f8961c69394a7f7d6292d18
|
oid sha256:98302375a306d4a396888cdbaecf358e246f25dcc5e300a2548b84d6fb9c4974
|
||||||
size 10485
|
size 11745
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:454d022bb76f1469ee071bac71d0b2a9061ed2f97390abe591ba17dfb194b98f
|
oid sha256:52ddea2d8936364fe4b8fcf9c140830cd4354db214cc43f737eaab6070b84c6d
|
||||||
size 10576
|
size 12713
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
version https://git-lfs.github.com/spec/v1
|
||||||
oid sha256:3c39f527f535195124f0720f598df65665559e91fd6f2dbf35e17f9e71c25f6d
|
oid sha256:dc4d9958f19470f82c43cc7cc2eb6ae4c0b2ec954a66bd654c76759780c0ae22
|
||||||
size 10533
|
size 11873
|
||||||
|
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
|
||||||
oid sha256:f6f581db727c01b084bc60ddb19cfe34e1f097cb91e6ac675c7721021f1d48c4
|
|
||||||
size 8446
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
version https://git-lfs.github.com/spec/v1
|
|
||||||
oid sha256:ad28a3e8f78ccd0263eff3c3d42f506ea4ae54eea88fe81849eac71fbaf0dd05
|
|
||||||
size 8458
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue