Skip to content
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

Feature/compose cleanup #1543

Merged
merged 24 commits into from
Jul 18, 2024
Merged

Feature/compose cleanup #1543

merged 24 commits into from
Jul 18, 2024

Conversation

ThomasSession
Copy link
Collaborator

Clean up work on our Compose classes.
Working on theming and overall standardising of UI elements.
Applying UI fixes where found.

Using the proper color for danger as opposed to one hardcoded color
Reusing BlackAlpha40
Using the right delete icon in settings
Updated the typography in an composition local so it can be accessed from anyehere in compose and matching the figma declarations.
Unifying spacing values and reusing common ones
Identified spacing issues and inconsistencies in design and figma
Discussed with QA to make sure the 'new message' screen should  indeed behave as the other screens and use disabled state instead of disappearing
Re-using our new radio buttons in disappearing messages
Tweaking UI to match designs
onView(withId(R.id.displayNameEditText)).perform(ViewActions.typeText("test-user123"))
onView(withId(R.id.registerButton)).perform(ViewActions.click())
objectFromDesc(R.string.displayNameEnter).click()
device.pressKeyCode(65)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add a comment like "Enter the dummy name 'ABC'" (is it sad that I remember some ASCII codes? Yes it is, haha)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is code from the onboarding PR. I suggest looking at the commits instead to see the work from this PR.

@@ -5,7 +5,7 @@ import network.loki.messenger.libsession_util.util.ExpiryMode
import org.thoughtcrime.securesms.ui.GetString
import org.thoughtcrime.securesms.ui.RadioOption

typealias ExpiryOptionsCard = OptionsCard<ExpiryMode>
typealias ExpiryOptionsCard = OptionsCardData<ExpiryMode>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cool, maybe suffix the typealias too?

* Some behaviour is hardcoded to cater for legacy usage of people with themes already set
* But future themes will be picked and set directly from the "Appearance" screen
*/
@Composable

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function is only @Composable because of the call to isSystemInDarkTheme().

Perhaps extract everything else to a non-composable? or use val lightDarkColors: StateFlow<LightDarkColors> from this PR

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is always called from Compose so there is no issue with it being Composable. You do need to know what the system is currently set to isSystemInDarkTheme in addition to the preferences chosen by the user.
It can be cleaned up once we have the sharedPref code if that manages to help.

val LocalColors = compositionLocalOf <ThemeColors> { ClassicDark() }
val LocalType = compositionLocalOf { sessionTypography }

var selectedTheme: ThemeColors? = null

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

top-level var is probably a code smell.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I don't love this but wanted something temporary while we get a root composable.
Maybe I can tweak the sharedPrefs flow once they're added in. Though I wouldn't move the compose data outside the app module where it belongs.

}

fun setNewFollowSystemSettings(followSystemSettings: Boolean) {
prefs.setFollowSystemSettings(followSystemSettings)
_uiState.value = prefs.themeState()

// force compose to refresh its style reference
selectedTheme = null

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This won't force currently composed layouts to refresh, it'll just refresh on a subsequent call.

So this Activity (and all open activities in the back stack) won't update.

Maybe this PR will help.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In a fully compose setup the value will come from a stateflow, directly passed to the root composable.
For now it works, even with the back stack, as going back to a previous fragment/activity calls the composable again which will check the value.
I'm a bit wary of moving all our composable theming in a module where it doesn't belong...

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh yeah... EmptyView is recomposed when you return to HomeActivity, I don't know why, I don't know if it's reliable.

Yeah, we don't have to move compose components. We can keep compose in this module, We'd just probably need to make another object... or just @Provides this StateFlow<Theme> with Dagger.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The currently open activity won't update though. It doesn't have any compose now...

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once we move to a fully composed screen the theme will be passed via a Flow so this won't be an issue anymore.
If we change this screen first before changing the full app we can make that flow available and collected at the root composable here to be applied.

Copy link

@alansley alansley left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All looking fair and reasonable from my perspective - have added a couple of comments / suggestions for you to address as you see fit.

if (mnemonic.isEmpty()) throw DecodingError.InputTooShort
if (words.isEmpty()) throw DecodingError.InputTooShort
// Check preconditions
if (words.size < 13) throw DecodingError.InputTooShort

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we also add a check for if the mneumonic words is > 13 (i.e., additional superflous words)?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an onboarding comment > @bemusementpark

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ummm... It's technically possibly valid

@@ -138,6 +138,9 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
// region Lifecycle
override fun onCreate(savedInstanceState: Bundle?, isReady: Boolean) {
super.onCreate(savedInstanceState, isReady)

if (!isTaskRoot) { finish(); return }

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potentially add a comment like "Prevent multiple instances of the app running at once"?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an onboarding comment > @bemusementpark

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it's worth a comment, as yeah, it's a lot more subtle than your suggestion ^^ :/

@ThomasSession ThomasSession marked this pull request as ready for review July 17, 2024 05:00
Comment on lines +52 to +58
state.cards.forEachIndexed { index, option ->
OptionsCard(option, callbacks)

// add spacing if not the last item
if(index != state.cards.lastIndex){
Spacer(modifier = Modifier.height(LocalDimensions.current.spacing))
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
state.cards.forEachIndexed { index, option ->
OptionsCard(option, callbacks)
// add spacing if not the last item
if(index != state.cards.lastIndex){
Spacer(modifier = Modifier.height(LocalDimensions.current.spacing))
}
state.cards.forEachIndexed { index, option ->
if (index != 0) {
Spacer(modifier = Modifier.height(LocalDimensions.current.spacing))
}
OptionsCard(option, callbacks)

Column(modifier = Modifier.background(LocalColors.current.backgroundSecondary)) {
Column(modifier = Modifier.background(
LocalColors.current.backgroundSecondary,
shape = MaterialTheme.shapes.small

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't a small shape, it's a bottom sheet, it's large.

We shouldn't just use this because it has the same value.

We could do our own Shapes object, but probably overkill atm, so just some extra val BottomSheetShape?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It isn't large, it is the rounded corner value that we want, which in this case matches the small value that we have defined in the Theme

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I'm just saying it's a coincidence, that our theme at the moment uses the same shape for small and bottom sheets.

import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter

interface ThemeColors {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the inspiration for the name change?

We still have LocalColors, which is longer than I like 😂 and the idiom is to name that the same as the class it provides.

If it's just because of the clash in this file, we could use a typealias MatColors = material.Colors or whatever.

And then yeah, we can add those courtesies...

Colors {
    companion object {
        @Composable val primary get() = LocalColors.current.primary

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I didn't think it was worth having yet another clashing class with the name Color. There are already enough of these around...

Copy link

@bemusementpark bemusementpark Jul 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it actually clashes, anywhere except the one function in this file.

}

fun setNewFollowSystemSettings(followSystemSettings: Boolean) {
prefs.setFollowSystemSettings(followSystemSettings)
_uiState.value = prefs.themeState()

// force compose to refresh its style reference
selectedTheme = null

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The currently open activity won't update though. It doesn't have any compose now...

Button(onClick, ButtonType.Outline(LocalColors.current.primaryButtonFill), modifier, enabled, content = content)
}

@Composable fun SlimOutlineButton(onClick: () -> Unit, modifier: Modifier = Modifier, color: Color = LocalColors.current.text, enabled: Boolean = true, content: @Composable RowScope.() -> Unit) {
@Composable fun SlimOutlineButton(modifier: Modifier = Modifier, color: Color = LocalColors.current.text, enabled: Boolean = true, onClick: () -> Unit, content: @Composable RowScope.() -> Unit) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably makes sense to follow the order of the base component unless there's a good reason, no?

@Composable
fun Button(
    onClick: () -> Unit,
    modifier: Modifier = Modifier,
    enabled: Boolean = true,
    shape: Shape = ButtonDefaults.shape,
    colors: ButtonColors = ButtonDefaults.buttonColors(),
    elevation: ButtonElevation? = ButtonDefaults.buttonElevation(),
    border: BorderStroke? = null,
    contentPadding: PaddingValues = ButtonDefaults.ContentPadding,
    interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
    content: @Composable RowScope.() -> Unit
) {

I think I had the other method with onClick at the end so you could use kotlin-trailing-lambda syntax, because there was no content but we could change that one, as it's probably confusing to have a trailing-lambda that is a click listener when people expect it to be @Compasable.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would have been great indeed but you matched all the other signature a different way so this was the only one that wasn't like the others.
Ideally we'd change all of them to match the original Button but that was quicker to at least have all our composable be the same...

Comment on lines +21 to +52
fun TextSecurePreferences.getComposeTheme(): ThemeColors {
val selectedTheme = getThemeStyle()

// get the chosen primary color from the preferences
val selectedPrimary = primaryColor()

// create a theme set with the appropriate primary
val colorSet = when(selectedTheme){
TextSecurePreferences.OCEAN_DARK,
TextSecurePreferences.OCEAN_LIGHT -> ThemeColorSet(
light = OceanLight(selectedPrimary),
dark = OceanDark(selectedPrimary)
)

else -> ThemeColorSet(
light = ClassicLight(selectedPrimary),
dark = ClassicDark(selectedPrimary)
)
}

// deliver the right set from the light/dark mode chosen
val theme = when{
getFollowSystemSettings() -> if(isSystemInDarkTheme()) colorSet.dark else colorSet.light

selectedTheme == TextSecurePreferences.CLASSIC_LIGHT ||
selectedTheme == TextSecurePreferences.OCEAN_LIGHT -> colorSet.light

else -> colorSet.dark
}

return theme
}
Copy link

@bemusementpark bemusementpark Jul 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps simpler if we either delete ColorSet, or defer some responsibility to it.

Suggested change
fun TextSecurePreferences.getComposeTheme(): ThemeColors {
val selectedTheme = getThemeStyle()
// get the chosen primary color from the preferences
val selectedPrimary = primaryColor()
// create a theme set with the appropriate primary
val colorSet = when(selectedTheme){
TextSecurePreferences.OCEAN_DARK,
TextSecurePreferences.OCEAN_LIGHT -> ThemeColorSet(
light = OceanLight(selectedPrimary),
dark = OceanDark(selectedPrimary)
)
else -> ThemeColorSet(
light = ClassicLight(selectedPrimary),
dark = ClassicDark(selectedPrimary)
)
}
// deliver the right set from the light/dark mode chosen
val theme = when{
getFollowSystemSettings() -> if(isSystemInDarkTheme()) colorSet.dark else colorSet.light
selectedTheme == TextSecurePreferences.CLASSIC_LIGHT ||
selectedTheme == TextSecurePreferences.OCEAN_LIGHT -> colorSet.light
else -> colorSet.dark
}
return theme
}
@Composable
fun TextSecurePreferences.getComposeTheme(): ThemeColors {
val selectedTheme = getThemeStyle()
// get the chosen primary color from the preferences
val selectedPrimary = primaryColor()
// create a theme set with the appropriate primary
return (if (getFollowSystemSettings()) when (selectedTheme) {
OCEAN_DARK -> ::OceanDark
OCEAN_LIGHT -> ::OceanLight
CLASSIC_LIGHT -> ::ClassicLight
else -> ::ClassicDark
} else when (selectedTheme) {
OCEAN_DARK, OCEAN_LIGHT -> if (isSystemInDarkTheme()) ::OceanDark else ::OceanLight
else -> if (isSystemInDarkTheme()) ::ClassicDark else ::ClassicLight
})(selectedPrimary)
}

@ThomasSession ThomasSession merged commit 9e2b24f into dev Jul 18, 2024
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants