Skip to content

Commit d7becd9

Browse files
authored
feat: add dynamic accent color system and dark mode improvements [WPB-21145] (#4393)
1 parent a32c583 commit d7becd9

File tree

28 files changed

+697
-250
lines changed

28 files changed

+697
-250
lines changed

app/src/androidTest/kotlin/com/wire/android/ui/WireTestTheme.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import androidx.compose.runtime.Composable
2222
import androidx.compose.runtime.CompositionLocalProvider
2323
import androidx.compose.runtime.remember
2424
import com.wire.android.ui.common.snackbar.LocalSnackbarHostState
25+
import com.wire.android.ui.theme.Accent
2526
import com.wire.android.ui.theme.DefaultWireFixedColorScheme
2627
import com.wire.android.ui.theme.WireColorScheme
2728
import com.wire.android.ui.theme.WireColorSchemeTypes
@@ -47,6 +48,7 @@ fun WireTestTheme(
4748
wireFixedColorScheme,
4849
wireTypography,
4950
wireDimensions,
51+
Accent.Unknown,
5052
content
5153
)
5254
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Wire
3+
* Copyright (C) 2024 Wire Swiss GmbH
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program. If not, see http://www.gnu.org/licenses/.
17+
*/
18+
19+
package com.wire.android.di
20+
21+
import com.wire.kalium.logic.CoreLogic
22+
import com.wire.kalium.logic.data.user.UserId
23+
import com.wire.kalium.logic.feature.user.ObserveSelfUserUseCase
24+
import dagger.assisted.Assisted
25+
import dagger.assisted.AssistedFactory
26+
import dagger.assisted.AssistedInject
27+
28+
class ObserveSelfUserUseCaseProvider @AssistedInject constructor(
29+
@KaliumCoreLogic private val coreLogic: CoreLogic,
30+
@Assisted private val userId: UserId
31+
) {
32+
val observeSelfUser: ObserveSelfUserUseCase
33+
get() = coreLogic.getSessionScope(userId).users.observeSelfUser
34+
35+
@AssistedFactory
36+
interface Factory {
37+
fun create(userId: UserId): ObserveSelfUserUseCaseProvider
38+
}
39+
}

app/src/main/kotlin/com/wire/android/ui/WireActivity.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,7 @@ class WireActivity : AppCompatActivity() {
258258
LocalSnackbarHostState provides snackbarHostState,
259259
LocalActivity provides this
260260
) {
261-
WireTheme {
261+
WireTheme(accent = viewModel.globalAppState.userAccent) {
262262
val navigator = rememberNavigator(
263263
finish = this@WireActivity::finish,
264264
isAllowedToNavigate = { navigationCommand ->

app/src/main/kotlin/com/wire/android/ui/WireActivityViewModel.kt

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import com.wire.android.di.IsProfileQRCodeEnabledUseCaseProvider
3333
import com.wire.android.di.KaliumCoreLogic
3434
import com.wire.android.di.ObserveIfE2EIRequiredDuringLoginUseCaseProvider
3535
import com.wire.android.di.ObserveScreenshotCensoringConfigUseCaseProvider
36+
import com.wire.android.di.ObserveSelfUserUseCaseProvider
3637
import com.wire.android.di.ObserveSyncStateUseCaseProvider
3738
import com.wire.android.feature.AccountSwitchUseCase
3839
import com.wire.android.feature.SwitchAccountActions
@@ -45,6 +46,7 @@ import com.wire.android.ui.common.dialogs.CustomServerDetailsDialogState
4546
import com.wire.android.ui.common.dialogs.CustomServerDialogState
4647
import com.wire.android.ui.common.dialogs.CustomServerNoNetworkDialogState
4748
import com.wire.android.ui.joinConversation.JoinConversationViaCodeState
49+
import com.wire.android.ui.theme.Accent
4850
import com.wire.android.ui.theme.ThemeOption
4951
import com.wire.android.util.CurrentScreen
5052
import com.wire.android.util.CurrentScreenManager
@@ -87,6 +89,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
8789
import kotlinx.coroutines.flow.SharedFlow
8890
import kotlinx.coroutines.flow.SharingStarted
8991
import kotlinx.coroutines.flow.StateFlow
92+
import kotlinx.coroutines.flow.collectLatest
9093
import kotlinx.coroutines.flow.distinctUntilChanged
9194
import kotlinx.coroutines.flow.first
9295
import kotlinx.coroutines.flow.flatMapLatest
@@ -124,6 +127,7 @@ class WireActivityViewModel @Inject constructor(
124127
private val observeIfE2EIRequiredDuringLoginUseCaseProviderFactory: ObserveIfE2EIRequiredDuringLoginUseCaseProvider.Factory,
125128
private val workManager: Lazy<WorkManager>,
126129
private val isProfileQRCodeEnabledFactory: IsProfileQRCodeEnabledUseCaseProvider.Factory,
130+
private val observeSelfUserFactory: ObserveSelfUserUseCaseProvider.Factory,
127131
) : ActionsViewModel<WireActivityViewAction>() {
128132

129133
var globalAppState: GlobalAppState by mutableStateOf(GlobalAppState())
@@ -154,6 +158,7 @@ class WireActivityViewModel @Inject constructor(
154158
observeNewClientState()
155159
observeScreenshotCensoringConfigState()
156160
observeAppThemeState()
161+
observeSelectedAccent()
157162
observeLogoutState()
158163
resetNewRegistrationAnalyticsState()
159164
}
@@ -173,6 +178,22 @@ class WireActivityViewModel @Inject constructor(
173178
}
174179
}
175180

181+
private fun observeSelectedAccent() {
182+
viewModelScope.launch(dispatchers.io()) {
183+
observeCurrentValidUserId.flatMapLatest {
184+
it?.let {
185+
observeSelfUserFactory.create(it).observeSelfUser().map { user ->
186+
Accent.fromAccentId(user.accentId)
187+
}
188+
} ?: flowOf(Accent.Unknown)
189+
}
190+
.distinctUntilChanged()
191+
.collectLatest {
192+
globalAppState = globalAppState.copy(userAccent = it)
193+
}
194+
}
195+
}
196+
176197
private fun observeSyncState() {
177198
viewModelScope.launch(dispatchers.io()) {
178199
observeCurrentValidUserId
@@ -606,7 +627,8 @@ data class GlobalAppState(
606627
val conversationJoinedDialog: JoinConversationViaCodeState? = null,
607628
val newClientDialog: NewClientsData? = null,
608629
val screenshotCensoringEnabled: Boolean = true,
609-
val themeOption: ThemeOption = ThemeOption.SYSTEM
630+
val themeOption: ThemeOption = ThemeOption.SYSTEM,
631+
val userAccent: Accent = Accent.Unknown
610632
)
611633

612634
enum class InitialAppState {

app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationScreen.kt

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -917,6 +917,11 @@ private fun ConversationScreen(
917917
Box(modifier = Modifier) {
918918
// only here we will use normal Scaffold because of specific behaviour of message composer
919919
Scaffold(
920+
contentColor = if (conversationInfoViewState.isBubbleUiEnabled) {
921+
colorsScheme().primary
922+
} else {
923+
colorsScheme().background
924+
},
920925
topBar = {
921926
Column {
922927
ConversationScreenTopAppBar(
@@ -1260,7 +1265,13 @@ fun MessageList(
12601265
contentAlignment = Alignment.BottomEnd,
12611266
modifier = modifier
12621267
.fillMaxSize()
1263-
.background(color = colorsScheme().surfaceContainerLow),
1268+
.background(
1269+
color = if (isBubbleUiEnabled) {
1270+
colorsScheme().bubblesBackground
1271+
} else {
1272+
colorsScheme().surfaceContainerLow
1273+
}
1274+
),
12641275
content = {
12651276
LazyColumn(
12661277
state = lazyListState,

app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationTopAppBar.kt

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import androidx.compose.runtime.Composable
3737
import androidx.compose.ui.Alignment
3838
import androidx.compose.ui.Modifier
3939
import androidx.compose.ui.draw.clip
40+
import androidx.compose.ui.graphics.Color
4041
import androidx.compose.ui.res.painterResource
4142
import androidx.compose.ui.res.stringResource
4243
import androidx.compose.ui.text.style.TextOverflow
@@ -82,7 +83,8 @@ fun ConversationScreenTopAppBar(
8283
onJoinCallButtonClick: () -> Unit,
8384
onAudioPermissionPermanentlyDenied: () -> Unit,
8485
isInteractionEnabled: Boolean,
85-
isDropDownEnabled: Boolean = false
86+
isDropDownEnabled: Boolean = false,
87+
containerColor: Color? = null
8688
) {
8789
val featureVisibilityFlags = LocalFeatureVisibilityFlags.current
8890
ConversationScreenTopAppBarContent(
@@ -96,7 +98,8 @@ fun ConversationScreenTopAppBar(
9698
onJoinCallButtonClick = onJoinCallButtonClick,
9799
onAudioPermissionPermanentlyDenied = onAudioPermissionPermanentlyDenied,
98100
isInteractionEnabled = isInteractionEnabled,
99-
isSearchEnabled = featureVisibilityFlags.ConversationSearchIcon
101+
isSearchEnabled = featureVisibilityFlags.ConversationSearchIcon,
102+
containerColor = containerColor
100103
)
101104
}
102105

@@ -113,7 +116,8 @@ private fun ConversationScreenTopAppBarContent(
113116
onAudioPermissionPermanentlyDenied: () -> Unit,
114117
isInteractionEnabled: Boolean,
115118
isSearchEnabled: Boolean,
116-
isDropDownEnabled: Boolean = false
119+
isDropDownEnabled: Boolean = false,
120+
containerColor: Color? = null
117121
) {
118122
TopAppBar(
119123
title = {
@@ -191,7 +195,7 @@ private fun ConversationScreenTopAppBarContent(
191195
}
192196
},
193197
colors = TopAppBarDefaults.centerAlignedTopAppBarColors(
194-
containerColor = MaterialTheme.colorScheme.background,
198+
containerColor = containerColor ?: MaterialTheme.colorScheme.background,
195199
titleContentColor = MaterialTheme.colorScheme.onBackground,
196200
actionIconContentColor = MaterialTheme.colorScheme.onBackground,
197201
navigationIconContentColor = MaterialTheme.colorScheme.onBackground

0 commit comments

Comments
 (0)