-
-
Notifications
You must be signed in to change notification settings - Fork 2.7k
feat(account-setting): add account monogram customization part 3 #10104
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
wmontwe
merged 25 commits into
thunderbird:main
from
wmontwe:feat/9031/add-account-monogram-customization-part-3
Nov 20, 2025
Merged
Changes from all commits
Commits
Show all changes
25 commits
Select commit
Hold shift + click to select a range
4378c98
docs: add documentation for Outcome
wmontwe dd3e629
refactor: change feature:account:core module to kmp
wmontwe 4c6e31f
feat(account): add getAll to AccountProfileRepository
wmontwe 62f0a7c
feat(account): inject account colors
wmontwe 13823c5
refactor(account-setting): use injected account colors to remove lega…
wmontwe a7ae92f
refactor(account-setting): rename SettingsError to AccountSettingError
wmontwe 72c46f9
refactor(account-setting): change update use case to command pattern
wmontwe 8e7a819
feat(account-setting): add account name and avatar validators
wmontwe e4169cf
feat(core-validation): add integer input field
wmontwe 894d67d
refactor(account-setting): change the init to launchIn
wmontwe 5ff50b1
refactor(account-setting): remove GeneralPreference and move settings…
wmontwe 18b35e6
feat(account-settind): add account avatar monogram customization
wmontwe 540a994
fix(core-setting): item should expand to full width
wmontwe 03b4061
refactor(account-avatar): rename AccountAvatar to Avatar and move to …
wmontwe 19c08ce
feat(account-avatar): add account avatar with monogram support
wmontwe e99ef9a
refactor(account-storage): rename AccountAvatarDataMapper to AvatarDa…
wmontwe 2bbab70
feat(drawer): add avatar support
wmontwe e90a209
feat(account-settings): add transform to SettingValue.Text to allow c…
wmontwe cb1155f
feat(account-settig): add account name and monogram validation support
wmontwe 5068539
refactor(account-avatar): rename profile indicator to avatar
wmontwe b8036f8
feat(account-setting): add description for the avatar selection and m…
wmontwe 08e32ec
refactor(account-settings): rename GetGeneralSettings to GetAccountPr…
wmontwe 23b25f3
feat(account-settings): add link to RFC for account name validation
wmontwe 678d4c0
refactor(account-settings): remove redundant text transform
wmontwe c9cd5ee
chore(build): change :feature:account:core to new kmp plugin
wmontwe File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
33 changes: 16 additions & 17 deletions
33
app-common/src/main/kotlin/net/thunderbird/app/common/account/AccountColorPicker.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,27 +1,26 @@ | ||
| package net.thunderbird.app.common.account | ||
|
|
||
| import android.content.res.Resources | ||
| import app.k9mail.core.ui.legacy.theme2.common.R | ||
| import net.thunderbird.core.android.account.LegacyAccountDtoManager | ||
| import kotlinx.collections.immutable.ImmutableList | ||
| import kotlinx.coroutines.flow.first | ||
| import net.thunderbird.feature.account.profile.AccountProfileRepository | ||
|
|
||
| internal class AccountColorPicker( | ||
| private val accountManager: LegacyAccountDtoManager, | ||
| private val resources: Resources, | ||
| private val repository: AccountProfileRepository, | ||
| private val accountColors: ImmutableList<Int>, | ||
| ) { | ||
| fun pickColor(): Int { | ||
| val accounts = accountManager.getAccounts() | ||
| val usedAccountColors = accounts.map { it.chipColor }.toSet() | ||
| val accountColors = resources.getIntArray(R.array.account_colors).toList() | ||
| suspend fun pickColor(): Int { | ||
| val profiles = repository.getAll().first() | ||
| val usedCounts = profiles.groupingBy { it.color }.eachCount() | ||
|
|
||
| val availableColors = accountColors - usedAccountColors | ||
| if (availableColors.isEmpty()) { | ||
| return accountColors.random() | ||
| val minCount = accountColors.minOf { usedCounts[it] ?: 0 } | ||
| val candidates = accountColors.filter { | ||
| (usedCounts[it] ?: 0) == minCount | ||
| } | ||
|
|
||
| val defaultAccountColors = resources.getIntArray(R.array.default_account_colors) | ||
| return availableColors.shuffled().minByOrNull { color -> | ||
| val index = defaultAccountColors.indexOf(color) | ||
| if (index != -1) index else defaultAccountColors.size | ||
| } ?: error("availableColors must not be empty") | ||
| return if (candidates.isNotEmpty()) { | ||
| candidates.shuffled().first() | ||
| } else { | ||
| accountColors.shuffled().first() | ||
| } | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
165 changes: 165 additions & 0 deletions
165
app-common/src/test/kotlin/net/thunderbird/app/common/account/AccountColorPickerTest.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,165 @@ | ||
| package net.thunderbird.app.common.account | ||
|
|
||
| import assertk.assertThat | ||
| import assertk.assertions.isEqualTo | ||
| import assertk.assertions.isOneOf | ||
| import kotlin.test.Test | ||
| import kotlinx.collections.immutable.persistentListOf | ||
| import kotlinx.coroutines.flow.MutableStateFlow | ||
| import kotlinx.coroutines.test.runTest | ||
| import net.thunderbird.app.common.account.data.FakeAccountProfileRepository | ||
| import net.thunderbird.feature.account.AccountIdFactory | ||
| import net.thunderbird.feature.account.avatar.Avatar | ||
| import net.thunderbird.feature.account.profile.AccountProfile | ||
|
|
||
| class AccountColorPickerTest { | ||
|
|
||
| @Test | ||
| fun `should pick random color when none used`() = runTest { | ||
| // Arrange | ||
| val profiles: MutableStateFlow<List<AccountProfile>> = MutableStateFlow(emptyList()) | ||
| val testSubject = AccountColorPicker( | ||
| repository = FakeAccountProfileRepository(profiles), | ||
| accountColors = ACCOUNT_COLORS, | ||
| ) | ||
|
|
||
| // Act | ||
| val result = testSubject.pickColor() | ||
|
|
||
| // Assert | ||
| assertThat(result).isOneOf(COLOR_RED, COLOR_GREEN, COLOR_BLUE) | ||
| } | ||
|
|
||
| @Test | ||
| fun `should pick one of the available colors when some are used`() = runTest { | ||
| // Arrange | ||
| val profiles: MutableStateFlow<List<AccountProfile>> = MutableStateFlow( | ||
| listOf( | ||
| ACCOUNT_PROFILE_GREEN_1, | ||
| ), | ||
| ) | ||
| val testSubject = AccountColorPicker( | ||
| repository = FakeAccountProfileRepository(profiles), | ||
| accountColors = ACCOUNT_COLORS, | ||
| ) | ||
|
|
||
| // Act | ||
| val result = testSubject.pickColor() | ||
|
|
||
| // Assert | ||
| assertThat(result).isOneOf(COLOR_RED, COLOR_BLUE) | ||
| } | ||
|
|
||
| @Test | ||
| fun `should pick last available color when others are used`() = runTest { | ||
| // Arrange | ||
| val profiles: MutableStateFlow<List<AccountProfile>> = MutableStateFlow( | ||
| listOf( | ||
| ACCOUNT_PROFILE_RED_1, | ||
| ACCOUNT_PROFILE_GREEN_1, | ||
| ), | ||
| ) | ||
| val testSubject = AccountColorPicker( | ||
| repository = FakeAccountProfileRepository(profiles), | ||
| accountColors = ACCOUNT_COLORS, | ||
| ) | ||
|
|
||
| // Act | ||
| val result = testSubject.pickColor() | ||
|
|
||
| // Assert | ||
| assertThat(result).isEqualTo(COLOR_BLUE) | ||
| } | ||
|
|
||
| @Test | ||
| fun `should pick random color when all colors are used equally`() = runTest { | ||
| // Arrange | ||
| val profiles: MutableStateFlow<List<AccountProfile>> = MutableStateFlow( | ||
| listOf( | ||
| ACCOUNT_PROFILE_RED_1, | ||
| ACCOUNT_PROFILE_GREEN_1, | ||
| ACCOUNT_PROFILE_BLUE_1, | ||
| ), | ||
| ) | ||
| val testSubject = AccountColorPicker( | ||
| repository = FakeAccountProfileRepository(profiles), | ||
| accountColors = ACCOUNT_COLORS, | ||
| ) | ||
|
|
||
| // Act | ||
| val result = testSubject.pickColor() | ||
|
|
||
| // Assert | ||
| assertThat(result).isOneOf(COLOR_RED, COLOR_GREEN, COLOR_BLUE) | ||
| } | ||
|
|
||
| @Test | ||
| fun `should pick from least used colors when colors are used multiple times`() = runTest { | ||
| // Arrange | ||
| val profiles: MutableStateFlow<List<AccountProfile>> = MutableStateFlow( | ||
| listOf( | ||
| ACCOUNT_PROFILE_RED_1, | ||
| ACCOUNT_PROFILE_RED_2, | ||
| ACCOUNT_PROFILE_GREEN_1, | ||
| ACCOUNT_PROFILE_GREEN_2, | ||
| ACCOUNT_PROFILE_BLUE_1, | ||
| ), | ||
| ) | ||
| val testSubject = AccountColorPicker( | ||
| repository = FakeAccountProfileRepository(profiles), | ||
| accountColors = ACCOUNT_COLORS, | ||
| ) | ||
|
|
||
| // Act | ||
| val result = testSubject.pickColor() | ||
|
|
||
| // Assert | ||
| assertThat(result).isEqualTo(COLOR_BLUE) | ||
| } | ||
|
|
||
| private companion object { | ||
| const val COLOR_RED = 0xFF0000 | ||
| const val COLOR_GREEN = 0x00FF00 | ||
| const val COLOR_BLUE = 0x0000FF | ||
|
|
||
| val ACCOUNT_COLORS = persistentListOf( | ||
| COLOR_RED, | ||
| COLOR_GREEN, | ||
| COLOR_BLUE, | ||
| ) | ||
|
|
||
| val ACCOUNT_PROFILE_RED_1 = AccountProfile( | ||
| id = AccountIdFactory.create(), | ||
| name = "Account Red 1", | ||
| color = COLOR_RED, | ||
| avatar = Avatar.Icon(name = "icon1"), | ||
| ) | ||
| val ACCOUNT_PROFILE_RED_2 = AccountProfile( | ||
| id = AccountIdFactory.create(), | ||
| name = "Account Red 2", | ||
| color = COLOR_RED, | ||
| avatar = Avatar.Icon(name = "icon4"), | ||
| ) | ||
|
|
||
| val ACCOUNT_PROFILE_GREEN_1 = AccountProfile( | ||
| id = AccountIdFactory.create(), | ||
| name = "Account Green 1", | ||
| color = COLOR_GREEN, | ||
| avatar = Avatar.Icon(name = "icon2"), | ||
| ) | ||
|
|
||
| val ACCOUNT_PROFILE_GREEN_2 = AccountProfile( | ||
| id = AccountIdFactory.create(), | ||
| name = "Account Green 2", | ||
| color = COLOR_GREEN, | ||
| avatar = Avatar.Icon(name = "icon5"), | ||
| ) | ||
|
|
||
| val ACCOUNT_PROFILE_BLUE_1 = AccountProfile( | ||
| id = AccountIdFactory.create(), | ||
| name = "Account Blue 1", | ||
| color = COLOR_BLUE, | ||
| avatar = Avatar.Icon(name = "icon3"), | ||
| ) | ||
| } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.