feat(healthchat): drop Element links, labs, bug reports and location sharing

Bloom gradient fades the brand blue instead of Compound's green.
This commit is contained in:
Sedat Qaja
2026-10-20 13:39:31 +02:00
parent 9df5d95732
commit 67dadb4571
9 changed files with 64 additions and 25 deletions
+4 -10
View File
@@ -22,19 +22,13 @@ android {
defaultConfig { defaultConfig {
buildConfigFieldStr( buildConfigFieldStr(
name = "URL_POLICY", name = "URL_POLICY",
value = if (isEnterpriseBuild) { // HealthChat fork: a configured value wins for every build type, not only enterprise ones.
BuildTimeConfig.URL_POLICY ?: "" value = BuildTimeConfig.URL_POLICY ?: if (isEnterpriseBuild) "" else "https://element.io/cookie-policy",
} else {
"https://element.io/cookie-policy"
},
) )
buildConfigFieldStr( buildConfigFieldStr(
name = "BUG_REPORT_URL", name = "BUG_REPORT_URL",
value = if (isEnterpriseBuild) { // HealthChat fork: a configured value wins for every build type, not only enterprise ones.
BuildTimeConfig.BUG_REPORT_URL ?: "" value = BuildTimeConfig.BUG_REPORT_URL ?: if (isEnterpriseBuild) "" else "https://rageshakes.element.io/api/submit",
} else {
"https://rageshakes.element.io/api/submit"
},
) )
buildConfigFieldStr( buildConfigFieldStr(
name = "BUG_REPORT_APP_NAME", name = "BUG_REPORT_APP_NAME",
@@ -9,9 +9,10 @@
package io.element.android.appconfig package io.element.android.appconfig
object LearnMoreConfig { object LearnMoreConfig {
const val ENCRYPTION_URL: String = "https://element.io/help#encryption" // HealthChat fork: every "learn more" link points at the HealthChat site instead of element.io/help.
const val DEVICE_VERIFICATION_URL: String = "https://element.io/help#encryption-device-verification" const val ENCRYPTION_URL: String = "https://www.healthchat.ch"
const val SECURE_BACKUP_URL: String = "https://element.io/help#encryption5" const val DEVICE_VERIFICATION_URL: String = "https://www.healthchat.ch"
const val IDENTITY_CHANGE_URL: String = "https://element.io/help#encryption18" const val SECURE_BACKUP_URL: String = "https://www.healthchat.ch"
const val HISTORY_VISIBLE_URL: String = "https://element.io/en/help#e2ee-history-sharing" const val IDENTITY_CHANGE_URL: String = "https://www.healthchat.ch"
const val HISTORY_VISIBLE_URL: String = "https://www.healthchat.ch"
} }
@@ -12,6 +12,7 @@ import androidx.compose.ui.graphics.Color
import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.ContributesBinding import dev.zacsweers.metro.ContributesBinding
import io.element.android.compound.colors.SemanticColorsLightDark import io.element.android.compound.colors.SemanticColorsLightDark
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
import io.element.android.libraries.androidutils.json.JsonProvider import io.element.android.libraries.androidutils.json.JsonProvider
@@ -64,7 +65,7 @@ class DefaultEnterpriseService(
} }
override fun semanticColorsFlow(sessionId: SessionId?): Flow<SemanticColorsLightDark> { override fun semanticColorsFlow(sessionId: SessionId?): Flow<SemanticColorsLightDark> {
return flowOf(SemanticColorsLightDark.default) return flowOf(healthChatSemanticColors)
} }
override fun firebasePushGateway(): String? = null override fun firebasePushGateway(): String? = null
@@ -77,6 +78,27 @@ class DefaultEnterpriseService(
override fun getNoisyNotificationChannelId(sessionId: SessionId): String? = null override fun getNoisyNotificationChannelId(sessionId: SessionId): String? = null
} }
/** HealthChat brand blue, the launcher background colour. */
private val healthChatBrandBlue = Color(0xFF12325C)
/**
* The subtle gradient (home screen bloom, highlighted timeline item) fades the brand blue out at the
* opacities Compound uses for its green stops; stop 6 stays transparent. Every other token, including the
* accent colours, is Compound's default: only the bloom changes.
*/
private fun SemanticColors.withHealthChatBloom() = copy(
gradientSubtleStop1 = healthChatBrandBlue.copy(alpha = 0.41f),
gradientSubtleStop2 = healthChatBrandBlue.copy(alpha = 0.23f),
gradientSubtleStop3 = healthChatBrandBlue.copy(alpha = 0.11f),
gradientSubtleStop4 = healthChatBrandBlue.copy(alpha = 0.06f),
gradientSubtleStop5 = healthChatBrandBlue.copy(alpha = 0.03f),
)
internal val healthChatSemanticColors = SemanticColorsLightDark(
light = SemanticColorsLightDark.default.light.withHealthChatBloom(),
dark = SemanticColorsLightDark.default.dark.withHealthChatBloom(),
)
/** /**
* HealthChat is locked to its own homeserver: this is the only account provider users can sign in to. * HealthChat is locked to its own homeserver: this is the only account provider users can sign in to.
*/ */
@@ -8,6 +8,7 @@
package io.element.android.features.enterprise.impl package io.element.android.features.enterprise.impl
import androidx.compose.ui.graphics.Color
import app.cash.turbine.test import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import io.element.android.compound.colors.SemanticColorsLightDark import io.element.android.compound.colors.SemanticColorsLightDark
@@ -23,6 +24,7 @@ import io.element.android.libraries.matrix.test.accountprovider.anAccountProvide
import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.lambda.lambdaRecorder
import io.element.android.tests.testutils.lambda.value import io.element.android.tests.testutils.lambda.value
import io.element.android.tests.testutils.testCoroutineDispatchers import io.element.android.tests.testutils.testCoroutineDispatchers
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
@@ -61,11 +63,22 @@ class DefaultEnterpriseServiceTest {
val defaultEnterpriseService = createDefaultEnterpriseService() val defaultEnterpriseService = createDefaultEnterpriseService()
defaultEnterpriseService.semanticColorsFlow(null).test { defaultEnterpriseService.semanticColorsFlow(null).test {
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState).isEqualTo(SemanticColorsLightDark.default) assertThat(initialState).isEqualTo(healthChatSemanticColors)
awaitComplete() awaitComplete()
} }
} }
@Test
fun `semanticColorsFlow only changes the subtle gradient to the HealthChat blue`() = runTest {
val colors = createDefaultEnterpriseService().semanticColorsFlow(null).first()
val default = SemanticColorsLightDark.default
assertThat(colors.light.gradientSubtleStop1).isEqualTo(Color(0xFF12325C).copy(alpha = 0.41f))
assertThat(colors.dark.gradientSubtleStop5).isEqualTo(Color(0xFF12325C).copy(alpha = 0.03f))
assertThat(colors.light.gradientSubtleStop6).isEqualTo(default.light.gradientSubtleStop6)
assertThat(colors.light.bgAccentRest).isEqualTo(default.light.bgAccentRest)
assertThat(colors.dark.bgAccentRest).isEqualTo(default.dark.bgAccentRest)
}
@Test @Test
fun `brandColorsFlow always emits null`() = runTest { fun `brandColorsFlow always emits null`() = runTest {
val defaultEnterpriseService = createDefaultEnterpriseService() val defaultEnterpriseService = createDefaultEnterpriseService()
@@ -81,7 +94,7 @@ class DefaultEnterpriseServiceTest {
val defaultEnterpriseService = createDefaultEnterpriseService() val defaultEnterpriseService = createDefaultEnterpriseService()
defaultEnterpriseService.semanticColorsFlow(A_SESSION_ID).test { defaultEnterpriseService.semanticColorsFlow(A_SESSION_ID).test {
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState).isEqualTo(SemanticColorsLightDark.default) assertThat(initialState).isEqualTo(healthChatSemanticColors)
awaitComplete() awaitComplete()
} }
} }
@@ -17,6 +17,7 @@ import io.element.android.features.location.api.buildTimeMapTilerConfig
import io.element.android.libraries.di.SessionScope import io.element.android.libraries.di.SessionScope
import io.element.android.libraries.di.annotations.SessionCoroutineScope import io.element.android.libraries.di.annotations.SessionCoroutineScope
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flow
@@ -35,4 +36,7 @@ class DefaultLocationService(
started = SharingStarted.Eagerly, started = SharingStarted.Eagerly,
initialValue = buildTimeMapTilerConfig(), initialValue = buildTimeMapTilerConfig(),
) )
// HealthChat fork: location sharing is not offered, even if the homeserver advertises a map provider.
override fun isServiceAvailable(): StateFlow<Boolean> = MutableStateFlow(false)
} }
@@ -29,7 +29,7 @@ class DefaultLocationServiceTest {
) )
runCurrent() runCurrent()
assertThat(locationService.mapTilerConfig.value).isEqualTo(buildTimeMapTilerConfig()) assertThat(locationService.mapTilerConfig.value).isEqualTo(buildTimeMapTilerConfig())
assertThat(locationService.isServiceAvailable().value).isEqualTo(buildTimeMapTilerConfig() != null) assertThat(locationService.isServiceAvailable().value).isFalse()
} }
@Test @Test
@@ -39,7 +39,7 @@ class DefaultLocationServiceTest {
) )
runCurrent() runCurrent()
assertThat(locationService.mapTilerConfig.value).isEqualTo(buildTimeMapTilerConfig()) assertThat(locationService.mapTilerConfig.value).isEqualTo(buildTimeMapTilerConfig())
assertThat(locationService.isServiceAvailable().value).isEqualTo(buildTimeMapTilerConfig() != null) assertThat(locationService.isServiceAvailable().value).isFalse()
} }
@Test @Test
@@ -50,7 +50,8 @@ class DefaultLocationServiceTest {
) )
runCurrent() runCurrent()
assertThat(locationService.mapTilerConfig.value).isEqualTo(config) assertThat(locationService.mapTilerConfig.value).isEqualTo(config)
assertThat(locationService.isServiceAvailable().value).isTrue() // HealthChat fork: the service is never available, even with a homeserver config.
assertThat(locationService.isServiceAvailable().value).isFalse()
} }
private fun TestScope.createDefaultLocationService( private fun TestScope.createDefaultLocationService(
@@ -131,7 +131,8 @@ class PreferencesRootPresenter(
val snackbarMessage by snackbarDispatcher.collectSnackbarMessageAsState() val snackbarMessage by snackbarDispatcher.collectSnackbarMessageAsState()
val hasAnalyticsProviders = remember { analyticsService.getAvailableAnalyticsProviders().isNotEmpty() } val hasAnalyticsProviders = remember { analyticsService.getAvailableAnalyticsProviders().isNotEmpty() }
val showLabsItem = remember { featureFlagService.getAvailableFeatures(isInLabs = true).isNotEmpty() } // HealthChat fork: upstream experiments are not offered to HealthChat users.
val showLabsItem = false
val showDeveloperSettings by showDeveloperSettingsProvider.showDeveloperSettings.collectAsState() val showDeveloperSettings by showDeveloperSettingsProvider.showDeveloperSettings.collectAsState()
@@ -198,7 +198,8 @@ class PreferencesRootPresenterTest {
} }
), ),
).test { ).test {
assertThat(awaitItem().showLabsItem).isTrue() // HealthChat fork: labs are never offered, whatever the feature flags say.
assertThat(awaitItem().showLabsItem).isFalse()
cancelAndIgnoreRemainingEvents() cancelAndIgnoreRemainingEvents()
} }
} }
@@ -23,7 +23,7 @@ object BuildTimeConfig {
// or a subdomain of it. // or a subdomain of it.
val URL_WEBSITE: String? = "https://healthchat.ch" val URL_WEBSITE: String? = "https://healthchat.ch"
val URL_LOGO: String? = "https://www.healthchat.ch/favicon.ico" val URL_LOGO: String? = "https://www.healthchat.ch/favicon.ico"
val URL_COPYRIGHT: String? = null val URL_COPYRIGHT: String? = "https://www.healthchat.ch"
val URL_ACCEPTABLE_USE: String? = "https://www.healthchat.ch/datenschutz" val URL_ACCEPTABLE_USE: String? = "https://www.healthchat.ch/datenschutz"
val URL_PRIVACY: String? = "https://www.healthchat.ch/datenschutz" val URL_PRIVACY: String? = "https://www.healthchat.ch/datenschutz"
val URL_POLICY: String? = "https://www.healthchat.ch/datenschutz" val URL_POLICY: String? = "https://www.healthchat.ch/datenschutz"
@@ -35,7 +35,9 @@ object BuildTimeConfig {
val SERVICES_POSTHOG_APIKEY: String? = null val SERVICES_POSTHOG_APIKEY: String? = null
val SERVICES_SENTRY_DSN: String? = null val SERVICES_SENTRY_DSN: String? = null
val SERVICES_SENTRY_DSN_RUST: String? = null val SERVICES_SENTRY_DSN_RUST: String? = null
val BUG_REPORT_URL: String? = null // Empty on purpose: HealthChat runs no rageshake server, and an empty URL hides every
// "report a problem" entry (settings, home menu, crash prompt).
val BUG_REPORT_URL: String? = ""
val BUG_REPORT_APP_NAME: String? = null val BUG_REPORT_APP_NAME: String? = null
const val PUSH_CONFIG_INCLUDE_FIREBASE: Boolean = true const val PUSH_CONFIG_INCLUDE_FIREBASE: Boolean = true
const val PUSH_CONFIG_INCLUDE_UNIFIED_PUSH: Boolean = true const val PUSH_CONFIG_INCLUDE_UNIFIED_PUSH: Boolean = true