-
Notifications
You must be signed in to change notification settings - Fork 552
WIP : Set user status #7094
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
Open
ganfra
wants to merge
31
commits into
develop
Choose a base branch
from
feature/fga/set_user_status
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+952
−36
Open
WIP : Set user status #7094
Changes from all commits
Commits
Show all changes
31 commits
Select commit
Hold shift + click to select a range
bad32cb
Add UserStatus and DisplayedStatus types to matrix API
ganfra 5697c96
Add KDoc to InCall.callJoinedTs and clearUserStatus
ganfra 742902e
Add setUserStatus/clearUserStatus stubs to FakeMatrixClient
ganfra f4c79be
Stub setUserStatus/clearUserStatus in-memory in RustMatrixClient
ganfra 42966b3
Add user status string resources and PredefinedUserStatus enum
ganfra 1640adc
Fix thread-safety and dispatcher consistency in user status stubs
ganfra eca45b5
Add UserStatusState and UserStatusEvent
ganfra 0528b78
Add UserStatusPresenter with full test coverage
ganfra af5a237
Add UserStatusView composables with previews
ganfra 6c34d64
Wire UserStatusPresenter and UserStatusRow into PreferencesRoot
ganfra 7309e49
Wire UserStatusPresenter and UserStatusRow into PreferencesRoot
ganfra eeaaac2
Move custom status emoji/text into CustomInput state (MVI)
ganfra 73a8a3d
Use TextFieldState in CustomInput state for proper MVI text handling
ganfra 57617a3
Add TextFieldState overload to design system TextField, use it in Use…
ganfra 167c1ce
Add clear button in custom status text field, toggle Cancel/Save on t…
ganfra 889fd08
Fix button width jitter using rememberTextMeasurer to derive stable m…
ganfra 078b63f
Move preview cases to UserStatusStateProvider, use @PreviewParameter …
ganfra 2a0e416
Match row heights: add vertical padding to plain text rows to align w…
ganfra 553a478
Rename Event
ganfra 1cfd3e4
Improve UI/UX for setting status
ganfra dd24e14
Gate user status behind UserStatus feature flag
ganfra d29dad4
Extract custom status text max length into a constant
ganfra 061fd82
Clip avatar to a circle on the user status screen
ganfra be872e6
Move user status section below multi-account in preferences
ganfra 4a143c1
Format code
ganfra 29b8068
Address quality issues on user status
ganfra f9a8acf
Update screenshots
ElementBot bfe2169
Tweak some UserStatusView ui
ganfra 301f930
Update screenshots
ElementBot d25362d
Merge branch 'develop' into feature/fga/set_user_status
ganfra fad8dce
Address PR review comments
ganfra 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
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
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
21 changes: 21 additions & 0 deletions
21
...lin/io/element/android/features/preferences/impl/userstatus/UserStatusBindingContainer.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,21 @@ | ||
| /* | ||
| * Copyright (c) 2026 Element Creations 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.features.preferences.impl.userstatus | ||
|
|
||
| import dev.zacsweers.metro.BindingContainer | ||
| import dev.zacsweers.metro.Binds | ||
| import dev.zacsweers.metro.ContributesTo | ||
| import io.element.android.libraries.architecture.Presenter | ||
| import io.element.android.libraries.di.SessionScope | ||
|
|
||
| @ContributesTo(SessionScope::class) | ||
| @BindingContainer | ||
| interface UserStatusBindingContainer { | ||
|
Check warning on line 18 in features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/userstatus/UserStatusBindingContainer.kt
|
||
| @Binds | ||
| fun bindUserStatusPresenter(presenter: UserStatusPresenter): Presenter<UserStatusState> | ||
| } | ||
20 changes: 20 additions & 0 deletions
20
...rc/main/kotlin/io/element/android/features/preferences/impl/userstatus/UserStatusEvent.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,20 @@ | ||
| /* | ||
| * Copyright (c) 2026 Element Creations 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.features.preferences.impl.userstatus | ||
|
|
||
| import io.element.android.libraries.matrix.api.user.UserStatus | ||
|
|
||
| sealed interface UserStatusEvent { | ||
| data object OpenPicker : UserStatusEvent | ||
| data object DismissPicker : UserStatusEvent | ||
| data class SetStatus(val status: UserStatus) : UserStatusEvent | ||
| data object OpenCustomInput : UserStatusEvent | ||
| data object CancelCustomInput : UserStatusEvent | ||
| data class UpdateCustomEmoji(val emoji: String) : UserStatusEvent | ||
| data object ClearStatus : UserStatusEvent | ||
| } |
75 changes: 75 additions & 0 deletions
75
...ain/kotlin/io/element/android/features/preferences/impl/userstatus/UserStatusPresenter.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,75 @@ | ||
| /* | ||
| * Copyright (c) 2026 Element Creations 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.features.preferences.impl.userstatus | ||
|
|
||
| import androidx.compose.foundation.text.input.clearText | ||
| import androidx.compose.foundation.text.input.rememberTextFieldState | ||
| import androidx.compose.foundation.text.input.setTextAndPlaceCursorAtEnd | ||
| import androidx.compose.runtime.Composable | ||
| import androidx.compose.runtime.collectAsState | ||
| import androidx.compose.runtime.getValue | ||
| import androidx.compose.runtime.mutableStateOf | ||
| import androidx.compose.runtime.remember | ||
| import androidx.compose.runtime.rememberCoroutineScope | ||
| import androidx.compose.runtime.setValue | ||
| import dev.zacsweers.metro.Inject | ||
| import io.element.android.libraries.architecture.Presenter | ||
| import io.element.android.libraries.matrix.api.MatrixClient | ||
| import kotlinx.coroutines.launch | ||
|
|
||
| @Inject | ||
| class UserStatusPresenter( | ||
| private val matrixClient: MatrixClient, | ||
| ) : Presenter<UserStatusState> { | ||
| @Composable | ||
| override fun present(): UserStatusState { | ||
| val userProfile by matrixClient.userProfile.collectAsState() | ||
| var pickerState by remember { mutableStateOf<UserStatusPickerState>(UserStatusPickerState.Hidden) } | ||
| val customTextFieldState = rememberTextFieldState() | ||
| val coroutineScope = rememberCoroutineScope() | ||
|
|
||
| fun handleEvent(event: UserStatusEvent) { | ||
| when (event) { | ||
| UserStatusEvent.OpenPicker -> pickerState = UserStatusPickerState.ShowingPicker | ||
| UserStatusEvent.DismissPicker -> pickerState = UserStatusPickerState.Hidden | ||
| UserStatusEvent.OpenCustomInput -> { | ||
| val raw = userProfile.rawStatus | ||
| if (raw != null) { | ||
| customTextFieldState.setTextAndPlaceCursorAtEnd(raw.text) | ||
| } else { | ||
| customTextFieldState.clearText() | ||
| } | ||
| pickerState = UserStatusPickerState.CustomInput( | ||
| emoji = raw?.emoji ?: "😀", | ||
| textFieldState = customTextFieldState, | ||
| ) | ||
| } | ||
| is UserStatusEvent.SetStatus -> { | ||
| pickerState = UserStatusPickerState.Hidden | ||
| coroutineScope.launch { matrixClient.setUserStatus(event.status) } | ||
| } | ||
| UserStatusEvent.ClearStatus -> { | ||
| pickerState = UserStatusPickerState.Hidden | ||
| coroutineScope.launch { matrixClient.clearUserStatus() } | ||
| } | ||
| UserStatusEvent.CancelCustomInput -> pickerState = UserStatusPickerState.Hidden | ||
| is UserStatusEvent.UpdateCustomEmoji -> { | ||
| val current = pickerState as? UserStatusPickerState.CustomInput ?: return | ||
| pickerState = current.copy(emoji = event.emoji) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return UserStatusState( | ||
| displayedStatus = userProfile.displayedStatus, | ||
| rawStatus = userProfile.rawStatus, | ||
| pickerState = pickerState, | ||
| eventSink = ::handleEvent, | ||
| ) | ||
| } | ||
| } |
40 changes: 40 additions & 0 deletions
40
...rc/main/kotlin/io/element/android/features/preferences/impl/userstatus/UserStatusState.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,40 @@ | ||
| /* | ||
| * Copyright (c) 2026 Element Creations 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.features.preferences.impl.userstatus | ||
|
|
||
| import androidx.annotation.StringRes | ||
| import androidx.compose.foundation.text.input.TextFieldState | ||
| import androidx.compose.runtime.Immutable | ||
| import io.element.android.features.preferences.impl.R | ||
| import io.element.android.libraries.matrix.api.user.DisplayedStatus | ||
| import io.element.android.libraries.matrix.api.user.UserStatus | ||
|
|
||
| data class UserStatusState( | ||
| val displayedStatus: DisplayedStatus?, | ||
| val rawStatus: UserStatus?, | ||
| val pickerState: UserStatusPickerState, | ||
| val eventSink: (UserStatusEvent) -> Unit, | ||
| ) | ||
|
|
||
| @Immutable | ||
| sealed interface UserStatusPickerState { | ||
| data object Hidden : UserStatusPickerState | ||
| data object ShowingPicker : UserStatusPickerState | ||
| data class CustomInput( | ||
| val emoji: String, | ||
| val textFieldState: TextFieldState, | ||
| ) : UserStatusPickerState | ||
| } | ||
|
|
||
| enum class PredefinedUserStatus(val emoji: String, @StringRes val labelRes: Int) { | ||
| IN_A_MEETING("💬", R.string.common_user_status_in_a_meeting), | ||
| FOCUS_TIME("💡", R.string.common_user_status_focus_time), | ||
| ON_THE_ROAD("🚙", R.string.common_user_status_on_the_road), | ||
| BE_RIGHT_BACK("☕", R.string.common_user_status_be_right_back), | ||
| AWAY("🌴", R.string.common_user_status_away), | ||
| } |
41 changes: 41 additions & 0 deletions
41
...kotlin/io/element/android/features/preferences/impl/userstatus/UserStatusStateProvider.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,41 @@ | ||
| /* | ||
| * Copyright (c) 2026 Element Creations 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.features.preferences.impl.userstatus | ||
|
|
||
| import androidx.compose.foundation.text.input.TextFieldState | ||
| import androidx.compose.ui.tooling.preview.PreviewParameterProvider | ||
| import io.element.android.libraries.matrix.api.user.DisplayedStatus | ||
| import io.element.android.libraries.matrix.api.user.UserStatus | ||
|
|
||
| internal class UserStatusStateProvider : PreviewParameterProvider<UserStatusState> { | ||
| override val values get() = sequenceOf( | ||
| aUserStatusState(), | ||
| aUserStatusState(displayedStatus = DisplayedStatus.UserSet(UserStatus("🌴", "Away"))), | ||
| aUserStatusState(displayedStatus = DisplayedStatus.InCall(callJoinedTs = 0L)), | ||
| aUserStatusState(pickerState = UserStatusPickerState.ShowingPicker), | ||
| aUserStatusState( | ||
| displayedStatus = DisplayedStatus.UserSet(UserStatus("🌴", "Away")), | ||
| rawStatus = UserStatus("🌴", "Away"), | ||
| pickerState = UserStatusPickerState.ShowingPicker, | ||
| ), | ||
| aUserStatusState(pickerState = UserStatusPickerState.CustomInput(emoji = "😀", textFieldState = TextFieldState())), | ||
| aUserStatusState(pickerState = UserStatusPickerState.CustomInput(emoji = "🚀", textFieldState = TextFieldState("Working on something"))), | ||
| ) | ||
| } | ||
|
|
||
| fun aUserStatusState( | ||
| displayedStatus: DisplayedStatus? = null, | ||
| rawStatus: UserStatus? = null, | ||
| pickerState: UserStatusPickerState = UserStatusPickerState.Hidden, | ||
| eventSink: (UserStatusEvent) -> Unit = {}, | ||
| ) = UserStatusState( | ||
| displayedStatus = displayedStatus, | ||
| rawStatus = rawStatus, | ||
| pickerState = pickerState, | ||
| eventSink = eventSink, | ||
| ) |
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Our current convention is to name this class
UserStatusModule. But maybe we should change the convention (probably tight to the old DI API) and rename the other interfaces at some point.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I don't have strong opinion, I can use
Moduleand we make the change later on the whole codebaseThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's keep it like this then, and rename the other ones separately