Skip to content

Commit 9c01204

Browse files
authored
Merge pull request #5333 from nextcloud/feature/3074/improveThreads4
add thread icons, improve fonts for threads overview
2 parents 87e0a72 + 24073bf commit 9c01204

5 files changed

Lines changed: 171 additions & 121 deletions

File tree

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* Nextcloud Talk - Android Client
3+
*
4+
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors
5+
* SPDX-FileCopyrightText: 2025 Marcel Hibbe <dev@mhibbe.de>
6+
* SPDX-License-Identifier: GPL-3.0-or-later
7+
*/
8+
9+
package com.nextcloud.talk.utils
10+
11+
import android.graphics.Color
12+
import org.junit.Assert
13+
import org.junit.Test
14+
15+
class ColorGeneratorTest {
16+
17+
@Test
18+
fun testUsernameToColor() {
19+
usernameToColorHexHelper("", "#0082c9")
20+
usernameToColorHexHelper(",", "#1e78c1")
21+
usernameToColorHexHelper(".", "#c98879")
22+
usernameToColorHexHelper("admin", "#d09e6d")
23+
usernameToColorHexHelper("123e4567-e89b-12d3-a456-426614174000", "#bc5c91")
24+
usernameToColorHexHelper("Akeel Robertson", "#9750a4")
25+
usernameToColorHexHelper("Brayden Truong", "#d09e6d")
26+
usernameToColorHexHelper("Daphne Roy", "#9750a4")
27+
usernameToColorHexHelper("Ellena Wright Frederic Conway", "#c37285")
28+
usernameToColorHexHelper("Gianluca Hills", "#d6b461")
29+
usernameToColorHexHelper("Haseeb Stephens", "#d6b461")
30+
usernameToColorHexHelper("Idris Mac", "#9750a4")
31+
usernameToColorHexHelper("Kristi Fisher", "#0082c9")
32+
usernameToColorHexHelper("Lillian Wall", "#bc5c91")
33+
usernameToColorHexHelper("Lorelai Taylor", "#ddcb55")
34+
usernameToColorHexHelper("Madina Knight", "#9750a4")
35+
usernameToColorHexHelper("Meeting", "#c98879")
36+
usernameToColorHexHelper("Private Circle", "#c37285")
37+
usernameToColorHexHelper("Rae Hope", "#795aab")
38+
usernameToColorHexHelper("Santiago Singleton", "#bc5c91")
39+
usernameToColorHexHelper("Sid Combs", "#d09e6d")
40+
usernameToColorHexHelper("TestCircle", "#499aa2")
41+
usernameToColorHexHelper("Tom Mörtel", "#248eb5")
42+
usernameToColorHexHelper("Vivienne Jacobs", "#1e78c1")
43+
usernameToColorHexHelper("Zaki Cortes", "#6ea68f")
44+
usernameToColorHexHelper("a user", "#5b64b3")
45+
usernameToColorHexHelper("admin@cloud.example.com", "#9750a4")
46+
usernameToColorHexHelper("another user", "#ddcb55")
47+
usernameToColorHexHelper("asd", "#248eb5")
48+
usernameToColorHexHelper("bar", "#0082c9")
49+
usernameToColorHexHelper("foo", "#d09e6d")
50+
usernameToColorHexHelper("wasd", "#b6469d")
51+
usernameToColorHexHelper("مرحبا بالعالم", "#c98879")
52+
usernameToColorHexHelper("🙈", "#b6469d")
53+
}
54+
55+
private fun usernameToColorHexHelper(username: String, expectedHexColor: String) {
56+
val userColorInt = ColorGenerator.usernameToColor(username) // returns Int
57+
val userHexColor = intToHex(userColorInt)
58+
59+
Assert.assertEquals(expectedHexColor.lowercase(), userHexColor.lowercase())
60+
}
61+
62+
private fun intToHex(colorInt: Int): String {
63+
val r = Color.red(colorInt)
64+
val g = Color.green(colorInt)
65+
val b = Color.blue(colorInt)
66+
return String.format("#%02x%02x%02x", r, g, b)
67+
}
68+
}

app/src/main/java/com/nextcloud/talk/call/components/ParticipantTile.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ fun ParticipantTile(
5050
modifier: Modifier = Modifier,
5151
isVoiceOnlyCall: Boolean
5252
) {
53-
val colorInt = ColorGenerator.shared.usernameToColor(participantUiState.nick)
53+
val colorInt = ColorGenerator.usernameToColor(participantUiState.nick)
5454

5555
BoxWithConstraints(
5656
modifier = modifier

app/src/main/java/com/nextcloud/talk/threadsoverview/ThreadsOverviewActivity.kt

Lines changed: 4 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ import androidx.compose.runtime.collectAsState
3333
import androidx.compose.runtime.getValue
3434
import androidx.compose.ui.Alignment
3535
import androidx.compose.ui.Modifier
36-
import androidx.compose.ui.platform.LocalContext
3736
import androidx.compose.ui.res.colorResource
3837
import androidx.compose.ui.res.pluralStringResource
3938
import androidx.compose.ui.res.stringResource
@@ -48,13 +47,11 @@ import com.nextcloud.talk.chat.ChatActivity
4847
import com.nextcloud.talk.chat.ChatActivity.Companion.TAG
4948
import com.nextcloud.talk.components.ColoredStatusBar
5049
import com.nextcloud.talk.components.StandardAppBar
51-
import com.nextcloud.talk.contacts.loadImage
5250
import com.nextcloud.talk.data.database.mappers.asModel
5351
import com.nextcloud.talk.models.json.threads.ThreadInfo
5452
import com.nextcloud.talk.threadsoverview.components.ThreadRow
5553
import com.nextcloud.talk.threadsoverview.viewmodels.ThreadsOverviewViewModel
5654
import com.nextcloud.talk.users.UserManager
57-
import com.nextcloud.talk.utils.ApiUtils
5855
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN
5956
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_THREAD_ID
6057
import javax.inject.Inject
@@ -118,8 +115,7 @@ class ThreadsOverviewActivity : BaseActivity() {
118115
roomToken,
119116
onThreadClick = { roomToken, threadId ->
120117
navigateToChatActivity(roomToken, threadId)
121-
},
122-
threadsOverviewViewModel
118+
}
123119
)
124120
}
125121
}
@@ -152,8 +148,7 @@ class ThreadsOverviewActivity : BaseActivity() {
152148
fun ThreadsOverviewScreen(
153149
uiState: ThreadsOverviewViewModel.ThreadsListUiState,
154150
roomToken: String,
155-
onThreadClick: (roomToken: String, threadId: Int) -> Unit,
156-
threadsOverviewViewModel: ThreadsOverviewViewModel
151+
onThreadClick: (roomToken: String, threadId: Int) -> Unit
157152
) {
158153
when (val state = uiState) {
159154
is ThreadsOverviewViewModel.ThreadsListUiState.None -> {
@@ -164,8 +159,7 @@ fun ThreadsOverviewScreen(
164159
ThreadsList(
165160
threads = state.threadsList!!,
166161
roomToken = roomToken,
167-
onThreadClick = onThreadClick,
168-
threadsOverviewViewModel
162+
onThreadClick = onThreadClick
169163
)
170164
}
171165

@@ -180,11 +174,9 @@ fun ThreadsOverviewScreen(
180174
fun ThreadsList(
181175
threads: List<ThreadInfo>,
182176
roomToken: String,
183-
onThreadClick: (roomToken: String, threadId: Int) -> Unit,
184-
threadsOverviewViewModel: ThreadsOverviewViewModel
177+
onThreadClick: (roomToken: String, threadId: Int) -> Unit
185178
) {
186179
val space = ' '
187-
val context = LocalContext.current
188180
if (threads.isEmpty()) {
189181
Box(
190182
modifier = Modifier
@@ -206,14 +198,6 @@ fun ThreadsList(
206198
items = threads,
207199
key = { threadInfo -> threadInfo.thread!!.id }
208200
) { threadInfo ->
209-
val imageUri = ApiUtils.getUrlForAvatar(
210-
threadsOverviewViewModel.currentUser.baseUrl,
211-
threadInfo.first?.actorId,
212-
true
213-
)
214-
val errorPlaceholderImage: Int = R.drawable.account_circle_96dp
215-
val imageRequest = loadImage(imageUri, context, errorPlaceholderImage)
216-
217201
val messageJson = threadInfo.last ?: threadInfo.first
218202
val messageModel = messageJson?.asModel()
219203

@@ -229,7 +213,6 @@ fun ThreadsList(
229213
secondLineTitle = messageModel?.actorDisplayName?.substringBefore(space)?.let { "$it:" }.orEmpty(),
230214
secondLine = messageModel?.text.orEmpty(),
231215
date = getLastActivityDate(threadInfo), // replace with value from api when available
232-
imageRequest = imageRequest,
233216
onClick = onThreadClick
234217
)
235218
}

app/src/main/java/com/nextcloud/talk/threadsoverview/components/ThreadRow.kt

Lines changed: 61 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
/*
22
* Nextcloud Talk - Android Client
33
*
4-
* SPDX-FileCopyrightText: 2025 Your Name <your@email.com>
4+
* SPDX-FileCopyrightText: 2025 Marcel Hibbe <dev@mhibbe.de>
55
* SPDX-License-Identifier: GPL-3.0-or-later
66
*/
77

88
package com.nextcloud.talk.threadsoverview.components
99

10+
import android.content.res.Configuration
11+
import androidx.compose.foundation.background
1012
import androidx.compose.foundation.clickable
13+
import androidx.compose.foundation.layout.Box
1114
import androidx.compose.foundation.layout.Column
1215
import androidx.compose.foundation.layout.Row
1316
import androidx.compose.foundation.layout.Spacer
@@ -16,19 +19,23 @@ import androidx.compose.foundation.layout.height
1619
import androidx.compose.foundation.layout.padding
1720
import androidx.compose.foundation.layout.size
1821
import androidx.compose.foundation.layout.width
22+
import androidx.compose.foundation.shape.CircleShape
23+
import androidx.compose.material3.Icon
1924
import androidx.compose.material3.MaterialTheme
2025
import androidx.compose.material3.Text
2126
import androidx.compose.runtime.Composable
2227
import androidx.compose.ui.Alignment
2328
import androidx.compose.ui.Modifier
24-
import androidx.compose.ui.res.stringResource
29+
import androidx.compose.ui.draw.clip
30+
import androidx.compose.ui.graphics.Color
31+
import androidx.compose.ui.graphics.vector.ImageVector
32+
import androidx.compose.ui.res.vectorResource
2533
import androidx.compose.ui.text.font.FontWeight
2634
import androidx.compose.ui.text.style.TextOverflow
2735
import androidx.compose.ui.tooling.preview.Preview
2836
import androidx.compose.ui.unit.dp
29-
import coil.compose.AsyncImage
30-
import coil.request.ImageRequest
3137
import com.nextcloud.talk.R
38+
import com.nextcloud.talk.utils.ColorGenerator
3239

3340
@Suppress("LongParameterList", "Detekt.LongMethod")
3441
@Composable
@@ -40,7 +47,6 @@ fun ThreadRow(
4047
secondLine: String,
4148
numReplies: String,
4249
date: String,
43-
imageRequest: ImageRequest?,
4450
onClick: ((String, Int) -> Unit?)?
4551
) {
4652
Row(
@@ -52,11 +58,7 @@ fun ThreadRow(
5258
.padding(vertical = 8.dp, horizontal = 8.dp),
5359
verticalAlignment = Alignment.Companion.CenterVertically
5460
) {
55-
AsyncImage(
56-
model = imageRequest,
57-
contentDescription = stringResource(R.string.user_avatar),
58-
modifier = Modifier.Companion.size(48.dp)
59-
)
61+
ThreadsIcon(title)
6062

6163
Spacer(modifier = Modifier.Companion.width(12.dp))
6264

@@ -67,7 +69,7 @@ fun ThreadRow(
6769
text = title,
6870
style = MaterialTheme.typography.titleSmall,
6971
color = MaterialTheme.colorScheme.onSurfaceVariant,
70-
fontWeight = FontWeight.Thin,
72+
fontWeight = FontWeight.Medium,
7173
maxLines = 1,
7274
overflow = TextOverflow.Companion.Ellipsis
7375
)
@@ -87,7 +89,7 @@ fun ThreadRow(
8789
Text(
8890
text = secondLineTitle,
8991
style = MaterialTheme.typography.titleMedium,
90-
fontWeight = FontWeight.Normal,
92+
fontWeight = FontWeight.Medium,
9193
maxLines = 1,
9294
overflow = TextOverflow.Companion.Ellipsis
9395
)
@@ -96,7 +98,8 @@ fun ThreadRow(
9698
modifier = Modifier.Companion.weight(1f),
9799
text = secondLine,
98100
style = MaterialTheme.typography.titleMedium,
99-
fontWeight = FontWeight.Thin,
101+
fontWeight = FontWeight.Normal,
102+
color = MaterialTheme.colorScheme.outline,
100103
maxLines = 1,
101104
overflow = TextOverflow.Companion.Ellipsis
102105
)
@@ -113,19 +116,56 @@ fun ThreadRow(
113116
}
114117
}
115118

119+
@Composable
120+
fun ThreadsIcon(title: String) {
121+
val baseColorInt = ColorGenerator.usernameToColor(title)
122+
123+
Box(
124+
modifier = Modifier
125+
.size(48.dp)
126+
.clip(CircleShape)
127+
.background(Color(baseColorInt).copy(alpha = 0.1f)),
128+
contentAlignment = Alignment.Center
129+
) {
130+
Icon(
131+
imageVector = ImageVector.vectorResource(R.drawable.outline_forum_24),
132+
contentDescription = null,
133+
tint = Color(baseColorInt).copy(alpha = 0.9f),
134+
modifier = Modifier.size(32.dp)
135+
)
136+
}
137+
}
138+
116139
@Preview
117140
@Composable
118141
fun ThreadRowPreview() {
119142
ThreadRow(
120143
roomToken = "1234",
121144
threadId = 123,
122-
title = "title",
145+
title = "title1",
146+
secondLine = "last message",
147+
secondLineTitle = "Mia:",
148+
numReplies = "12 replies",
149+
date = "14 sec ago",
150+
onClick = null
151+
)
152+
}
153+
154+
@Preview(
155+
name = "Dark Mode",
156+
uiMode = Configuration.UI_MODE_NIGHT_YES
157+
)
158+
@Composable
159+
fun ThreadRowPreviewDark() {
160+
ThreadRow(
161+
roomToken = "1234",
162+
threadId = 123,
163+
title = "title2",
123164
secondLine = "last message",
124165
secondLineTitle = "Mia:",
125166
numReplies = "12 replies",
126167
date = "14 sec ago",
127-
onClick = null,
128-
imageRequest = null
168+
onClick = null
129169
)
130170
}
131171

@@ -135,13 +175,12 @@ fun ThreadRowUnreadMessagePreview() {
135175
ThreadRow(
136176
roomToken = "1234",
137177
threadId = 123,
138-
title = "title",
178+
title = "title3",
139179
secondLine = "last message",
140180
secondLineTitle = "Mia:",
141181
numReplies = "12 replies",
142182
date = "14 sec ago",
143-
onClick = null,
144-
imageRequest = null
183+
onClick = null
145184
)
146185
}
147186

@@ -151,13 +190,12 @@ fun ThreadRowMentionPreview() {
151190
ThreadRow(
152191
roomToken = "1234",
153192
threadId = 123,
154-
title = "title",
193+
title = "title3",
155194
secondLine = "last message",
156195
secondLineTitle = "Mia:",
157196
numReplies = "12 replies",
158197
date = "14 sec ago",
159-
onClick = null,
160-
imageRequest = null
198+
onClick = null
161199
)
162200
}
163201

@@ -172,7 +210,6 @@ fun ThreadRowDirectMentionPreview() {
172210
secondLineTitle = "Mia:",
173211
numReplies = "12 replies",
174212
date = "14 sec ago",
175-
onClick = null,
176-
imageRequest = null
213+
onClick = null
177214
)
178215
}

0 commit comments

Comments
 (0)