Skip to content

Commit

Permalink
Merge pull request #136 from Automattic/hamorillo/126-skeleton-i2
Browse files Browse the repository at this point in the history
`gravatar-ui`- Skeleton (Ghost UI) second iteration
  • Loading branch information
hamorillo authored Apr 26, 2024
2 parents ca4bc51 + a5d12ba commit 727ae3f
Show file tree
Hide file tree
Showing 11 changed files with 230 additions and 66 deletions.
16 changes: 4 additions & 12 deletions demo-app/src/main/java/com/gravatar/demoapp/ui/DemoGravatarApp.kt
Original file line number Diff line number Diff line change
Expand Up @@ -156,24 +156,16 @@ private fun ProfileCards(profileState: UserProfileState?, error: String) {
.fillMaxWidth()
.padding(24.dp)
// Show the profile card if we got a result and there is no error and it's not loading
if ((profileState is UserProfileState.Loaded) && error.isEmpty()) {
(profileState as? UserProfileState.Loaded)?.let {
ProfileCard(it.userProfile, defaultModifier)
Spacer(modifier = Modifier.height(16.dp))
}
}
if (error.isEmpty()) {
profileState?.let {
ProfileCard(it, defaultModifier)
Spacer(modifier = Modifier.height(16.dp))
MiniProfileCard(it, defaultModifier)
Spacer(modifier = Modifier.height(16.dp))
}
}
if ((profileState is UserProfileState.Loaded) && error.isEmpty()) {
(profileState as? UserProfileState.Loaded)?.let {
LargeProfile(it.userProfile, defaultModifier)
LargeProfile(it, defaultModifier)
Spacer(modifier = Modifier.height(16.dp))
LargeProfileSummary(
it.userProfile,
it,
Modifier
.padding(8.dp)
.background(MaterialTheme.colorScheme.surfaceContainer)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.gravatar.ui.components

import android.content.res.Configuration
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
Expand Down Expand Up @@ -32,27 +33,39 @@ import com.gravatar.ui.components.atomic.ViewProfileButton
*/
@Composable
public fun LargeProfile(profile: UserProfile, modifier: Modifier = Modifier) {
LargeProfile(state = UserProfileState.Loaded(profile), modifier = modifier)
}

/**
* [LargeProfile] is a composable that displays a user's profile card.
* Given a [UserProfileState], it displays a [LargeProfile] or the skeleton if it's in a loading state.
*
* @param state The user's profile state
* @param modifier Composable modifier
*/
@Composable
public fun LargeProfile(state: UserProfileState, modifier: Modifier = Modifier) {
GravatarTheme {
Column(
modifier = modifier,
) {
Avatar(
profile = profile,
state = state,
size = 132.dp,
modifier = Modifier.clip(CircleShape),
)
DisplayName(profile, modifier = Modifier.padding(top = 16.dp))
UserInfo(profile)
AboutMe(profile, modifier = Modifier.padding(top = 8.dp))
DisplayName(state, modifier = Modifier.padding(top = 16.dp))
UserInfo(state)
AboutMe(state, modifier = Modifier.padding(top = 8.dp))
Row(
modifier = Modifier
.fillMaxWidth()
.padding(top = 4.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
SocialIconRow(profile, maxIcons = 4)
ViewProfileButton(profile, Modifier.padding(0.dp))
SocialIconRow(state, maxIcons = 4)
ViewProfileButton(state, Modifier.padding(0.dp))
}
}
}
Expand Down Expand Up @@ -81,3 +94,10 @@ private fun LargeProfilePreview() {
),
)
}

@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
public fun DisplayNamePreview() {
LoadingToLoadedStatePreview { LargeProfile(it) }
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.gravatar.ui.components

import android.content.res.Configuration
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
Expand Down Expand Up @@ -30,25 +31,37 @@ import com.gravatar.ui.components.atomic.ViewProfileButton
*/
@Composable
public fun LargeProfileSummary(profile: UserProfile, modifier: Modifier = Modifier) {
LargeProfileSummary(UserProfileState.Loaded(profile), modifier)
}

/**
* [LargeProfileSummary] is a composable that displays a user's profile in a resumed way.
* Given a [UserProfileState], it displays a [LargeProfileSummary] or the skeleton if it's in a loading state.
*
* @param state The user's profile state
* @param modifier Composable modifier
*/
@Composable
public fun LargeProfileSummary(state: UserProfileState, modifier: Modifier = Modifier) {
GravatarTheme {
Column(
modifier = modifier,
horizontalAlignment = Alignment.CenterHorizontally,
) {
Avatar(
profile = profile,
state = state,
size = 132.dp,
modifier = Modifier.clip(CircleShape),
)
DisplayName(profile, modifier = Modifier.padding(top = 16.dp))
DisplayName(state, modifier = Modifier.padding(top = 16.dp))
UserInfo(
profile,
state,
textStyle = MaterialTheme.typography.bodyMedium.copy(
color = MaterialTheme.colorScheme.outline,
textAlign = TextAlign.Center,
),
)
ViewProfileButton(profile, Modifier.padding(0.dp), inlineContent = null)
ViewProfileButton(state, Modifier.padding(0.dp), inlineContent = null)
}
}
}
Expand Down Expand Up @@ -78,3 +91,10 @@ private fun LargeProfileSummaryPreview() {
)
}
}

@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
public fun LargeProfileLoadingPreview() {
LoadingToLoadedStatePreview { LargeProfileSummary(it) }
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,12 @@ import android.content.res.Configuration.UI_MODE_NIGHT_NO
import android.content.res.Configuration.UI_MODE_NIGHT_YES
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.text.font.FontWeight
Expand All @@ -28,7 +21,6 @@ import com.gravatar.ui.components.atomic.Avatar
import com.gravatar.ui.components.atomic.DisplayName
import com.gravatar.ui.components.atomic.Location
import com.gravatar.ui.components.atomic.ViewProfileButton
import kotlinx.coroutines.delay

/**
* [MiniProfileCard] is a composable that displays a mini profile card.
Expand All @@ -46,7 +38,7 @@ public fun MiniProfileCard(profile: UserProfile, modifier: Modifier = Modifier)
* [MiniProfileCard] is a composable that displays a mini profile card.
* Given a [UserProfile], it displays a mini profile card using the other atomic components provided within the SDK.
*
* @param state The user's profile loading state
* @param state The user's profile state
* @param modifier Composable modifier
*/
@Composable
Expand Down Expand Up @@ -99,20 +91,5 @@ private fun MiniProfileCardPreview() {
@Preview(uiMode = UI_MODE_NIGHT_YES)
@Composable
private fun MiniProfileCardLoadingPreview() {
var state: UserProfileState by remember { mutableStateOf(UserProfileState.Loading) }
LaunchedEffect(key1 = state) {
delay(5000)
state = UserProfileState.Loaded(
UserProfile(
"4539566a0223b11d28fc47c864336fa27b8fe49b5f85180178c9e3813e910d6a",
displayName = "John Doe",
currentLocation = "Crac'h, France",
),
)
}
GravatarTheme {
Surface(Modifier.fillMaxWidth()) {
MiniProfileCard(state)
}
}
LoadingToLoadedStatePreview { MiniProfileCard(it) }
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.gravatar.ui.components

import android.content.res.Configuration
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
Expand Down Expand Up @@ -36,6 +37,18 @@ import com.gravatar.ui.components.atomic.ViewProfileButton
*/
@Composable
public fun ProfileCard(profile: UserProfile, modifier: Modifier = Modifier) {
ProfileCard(state = UserProfileState.Loaded(profile), modifier = modifier)
}

/**
* [ProfileCard] is a composable that displays a user's profile card.
* Given a [UserProfileState], it displays a [ProfileCard] or the skeleton if it's in a loading state.
*
* @param state The user's profile state
* @param modifier Composable modifier
*/
@Composable
public fun ProfileCard(state: UserProfileState, modifier: Modifier = Modifier) {
GravatarTheme {
Column(
modifier = modifier,
Expand All @@ -44,28 +57,28 @@ public fun ProfileCard(profile: UserProfile, modifier: Modifier = Modifier) {
) {
Row {
Avatar(
profile = profile,
state = state,
size = 72.dp,
modifier = Modifier.clip(CircleShape),
)
Column(modifier = Modifier.padding(14.dp, 0.dp, 0.dp, 0.dp)) {
DisplayName(
profile,
state,
textStyle = MaterialTheme.typography.titleMedium.copy(fontWeight = FontWeight.Bold),
)
UserInfo(profile)
UserInfo(state)
}
}
Spacer(modifier = Modifier.height(16.dp))
AboutMe(profile)
AboutMe(state)
Spacer(modifier = Modifier.height(4.dp))
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
SocialIconRow(profile, maxIcons = 4)
ViewProfileButton(profile, Modifier.padding(0.dp))
SocialIconRow(state, maxIcons = 4)
ViewProfileButton(state, Modifier.padding(0.dp))
}
}
}
Expand Down Expand Up @@ -94,3 +107,10 @@ private fun ProfileCardPreview() {
),
)
}

@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun ProfileCardLoadingPreview() {
LoadingToLoadedStatePreview { ProfileCard(it) }
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import com.gravatar.api.models.Account
import com.gravatar.api.models.Email
import com.gravatar.api.models.UserProfile
import com.gravatar.ui.GravatarTheme
import com.gravatar.ui.components.UserProfileState.Loaded
Expand Down Expand Up @@ -42,9 +44,24 @@ public fun LoadingToLoadedStatePreview(composable: @Composable (state: UserProfi
delay(5000)
state = Loaded(
UserProfile(
"4539566a0223b11d28fc47c864336fa27b8fe49b5f85180178c9e3813e910d6a",
hash = "4539566a0223b11d28fc47c864336fa27b8fe49b5f85180178c9e3813e910d6a",
displayName = "John Doe",
preferredUsername = "ddoe",
jobTitle = "Farmer",
company = "Farmers United",
currentLocation = "Crac'h, France",
pronouns = "They/Them",
accounts = listOf(
Account(name = "Mastodon", url = "https://example.com", shortname = "mastodon"),
Account(name = "Tumblr", url = "https://example.com", shortname = "tumblr"),
// Invalid url, should be ignored:
Account(name = "TikTok", url = "example.com", shortname = "tiktok"),
Account(name = "WordPress", url = "https://example.com", shortname = "wordpress"),
Account(name = "GitHub", url = "https://example.com", shortname = "github"),
),
aboutMe = "I'm a farmer, I love to code. I ride my bicycle to work. One apple a day keeps the " +
"doctor away. This about me description is quite long, this is good for testing.",
emails = listOf(Email(primary = true, value = "[email protected]")),
),
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.gravatar.ui.components.atomic

import android.content.res.Configuration
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
Expand All @@ -8,6 +9,9 @@ import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import com.gravatar.api.models.UserProfile
import com.gravatar.ui.TextSkeletonEffect
import com.gravatar.ui.components.LoadingToLoadedStatePreview
import com.gravatar.ui.components.UserProfileState

/**
* [AboutMe] is a composable that displays a user's about me description.
Expand All @@ -29,6 +33,34 @@ public fun AboutMe(
content(profile.aboutMe.orEmpty(), modifier)
}

/**
* [AboutMe] is a composable that displays a user's about me description.
*
* @param state The user's profile loading state
* @param modifier Composable modifier
* @param textStyle The style to apply to the default text content
* @param content Composable to display the user's about me description
*/
@Composable
public fun AboutMe(
state: UserProfileState,
modifier: Modifier = Modifier,
textStyle: TextStyle = MaterialTheme.typography.bodyMedium,
content: @Composable ((String, Modifier) -> Unit) = { userInfo, contentModifier ->
AboutMeDefaultContent(userInfo, textStyle, contentModifier)
},
) {
when (state) {
is UserProfileState.Loading -> {
TextSkeletonEffect(textStyle = textStyle)
}

is UserProfileState.Loaded -> {
AboutMe(state.userProfile, modifier, textStyle, content)
}
}
}

@Composable
private fun AboutMeDefaultContent(userInfo: String, textStyle: TextStyle, modifier: Modifier) = Text(
userInfo,
Expand All @@ -49,3 +81,10 @@ private fun AboutMePreview() {
),
)
}

@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun LocationStatePreview() {
LoadingToLoadedStatePreview { AboutMe(it) }
}
Loading

0 comments on commit 727ae3f

Please sign in to comment.