Skip to content

Commit 4380af5

Browse files
committed
Update timestamp and add pronouns
1 parent ab0790a commit 4380af5

File tree

6 files changed

+236
-33
lines changed

6 files changed

+236
-33
lines changed

app/src/main/java/xyz/wingio/dimett/rest/dto/user/User.kt

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,21 @@ import kotlinx.datetime.Instant
55
import kotlinx.serialization.SerialName
66
import kotlinx.serialization.Serializable
77
import xyz.wingio.dimett.rest.dto.CustomEmoji
8+
import xyz.wingio.dimett.utils.plain
9+
10+
/**
11+
* Regex to match the typical pronoun format (he/him, she/her, they/them, etc.)
12+
*/
13+
private val pronounsRegex = (
14+
"(?:" +
15+
"[\\w:]+" + // Word characters and `:` for emotes
16+
"(?:" +
17+
"\\/[\\w:]+" + // Word characters and `:` for emotes with a preceding `/`
18+
")+" + // Allow for more than 2
19+
"|" +
20+
"any(?: pronouns)?" + // Special use case for people without a preference
21+
")"
22+
).toRegex(RegexOption.IGNORE_CASE)
823

924
// https://docs.joinmastodon.org/entities/Account
1025
@Stable
@@ -36,4 +51,20 @@ data class User(
3651
@SerialName("followers_count") val followers: Int,
3752
@SerialName("following_count") val following: Int,
3853
@SerialName("mute_expires_at") val muteExpiration: Instant? = null
39-
)
54+
) {
55+
56+
/**
57+
* Finds a pronouns field that matches [pronounsRegex]
58+
*
59+
* NOT FROM THE API
60+
*/
61+
val pronouns = fields
62+
.firstOrNull {
63+
it.name.lowercase() == "pronouns"
64+
}
65+
?.let {
66+
pronounsRegex.find(it.value.plain)
67+
}
68+
?.value?.lowercase()
69+
70+
}

app/src/main/java/xyz/wingio/dimett/ui/widgets/posts/Post.kt

Lines changed: 39 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
package xyz.wingio.dimett.ui.widgets.posts
22

3-
import android.text.format.DateUtils
43
import androidx.compose.foundation.layout.Arrangement
54
import androidx.compose.foundation.layout.Column
5+
import androidx.compose.foundation.layout.Row
66
import androidx.compose.foundation.layout.fillMaxWidth
77
import androidx.compose.foundation.layout.padding
88
import androidx.compose.material.icons.Icons
@@ -11,11 +11,10 @@ import androidx.compose.material3.LocalContentColor
1111
import androidx.compose.material3.MaterialTheme
1212
import androidx.compose.material3.Surface
1313
import androidx.compose.runtime.Composable
14-
import androidx.compose.runtime.remember
14+
import androidx.compose.ui.Alignment
1515
import androidx.compose.ui.Modifier
1616
import androidx.compose.ui.platform.LocalContext
1717
import androidx.compose.ui.res.stringResource
18-
import androidx.compose.ui.text.style.TextAlign
1918
import androidx.compose.ui.unit.dp
2019
import xyz.wingio.dimett.R
2120
import xyz.wingio.dimett.ast.DefaultSyntakts
@@ -47,6 +46,8 @@ import xyz.wingio.dimett.utils.toMentionMap
4746
@Suppress("LocalVariableName")
4847
fun Post(
4948
post: Post,
49+
showPronouns: Boolean = true, // TODO: Make a setting
50+
showVisibility: Boolean = true, // TODO: Make a setting
5051
onAvatarClick: (String) -> Unit = {},
5152
onMentionClick: (String) -> Unit = {},
5253
onHashtagClick: (String) -> Unit = {},
@@ -57,29 +58,12 @@ fun Post(
5758
) {
5859
val ctx = LocalContext.current
5960
val _post = post.boosted ?: post // The actually displayed post, not the same if its a boost
60-
val timeString = remember(post.createdAt) {
61-
DateUtils.getRelativeTimeSpanString(
62-
/* time = */ post.createdAt.toEpochMilliseconds(),
63-
/* now = */ System.currentTimeMillis(),
64-
/* minResolution = */ 0L,
65-
/* flags = */ DateUtils.FORMAT_ABBREV_ALL
66-
).toString()
67-
}
6861

6962
Surface(
7063
modifier = Modifier.fillMaxWidth(),
7164
tonalElevation = 1.dp,
7265
shadowElevation = 3.dp
7366
) {
74-
Text(
75-
text = timeString,
76-
style = MaterialTheme.typography.labelSmall,
77-
color = LocalContentColor.current.copy(alpha = 0.5f),
78-
textAlign = TextAlign.End,
79-
modifier = Modifier
80-
.fillMaxWidth()
81-
.padding(18.dp)
82-
)
8367
Column(
8468
verticalArrangement = Arrangement.spacedBy(16.dp),
8569
modifier = Modifier
@@ -97,14 +81,41 @@ fun Post(
9781
)
9882
}
9983

100-
PostAuthor(
101-
avatarUrl = _post.author.avatar,
102-
displayName = _post.author.displayName,
103-
acct = _post.author.acct,
104-
emojis = _post.author.emojis.toEmojiMap(),
105-
bot = _post.author.bot,
106-
onAvatarClick = { onAvatarClick(_post.author.id) }
107-
)
84+
Row(
85+
verticalAlignment = Alignment.CenterVertically,
86+
horizontalArrangement = Arrangement.spacedBy(10.dp),
87+
modifier = Modifier.fillMaxWidth()
88+
) {
89+
PostAuthor(
90+
avatarUrl = _post.author.avatar,
91+
displayName = _post.author.displayName,
92+
acct = _post.author.acct,
93+
emojis = _post.author.emojis.toEmojiMap(),
94+
bot = _post.author.bot,
95+
onAvatarClick = { onAvatarClick(_post.author.id) },
96+
modifier = Modifier.weight(0.75f)
97+
)
98+
99+
Column(
100+
horizontalAlignment = Alignment.End,
101+
verticalArrangement = Arrangement.spacedBy(3.5.dp),
102+
modifier = Modifier
103+
.weight(0.25f)
104+
) {
105+
PostTimestamp(
106+
createdAt = _post.createdAt,
107+
visibility = _post.visibility,
108+
showVisibility = showVisibility
109+
)
110+
111+
if (showPronouns) {
112+
PostPronouns(
113+
pronouns = _post.author.pronouns,
114+
emoji = _post.author.emojis
115+
)
116+
}
117+
}
118+
}
108119

109120
_post.userRepliedTo?.let { repliedTo ->
110121
_post.mentions.firstOrNull {

app/src/main/java/xyz/wingio/dimett/ui/widgets/posts/PostAuthor.kt

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import androidx.compose.ui.Alignment
1616
import androidx.compose.ui.Modifier
1717
import androidx.compose.ui.draw.clip
1818
import androidx.compose.ui.res.stringResource
19+
import androidx.compose.ui.text.style.TextOverflow
1920
import androidx.compose.ui.unit.dp
2021
import coil.compose.AsyncImage
2122
import xyz.wingio.dimett.R
@@ -41,11 +42,13 @@ fun PostAuthor(
4142
acct: String,
4243
emojis: Map<String, String>,
4344
bot: Boolean,
45+
modifier: Modifier = Modifier,
4446
onAvatarClick: () -> Unit = {}
4547
) {
4648
Row(
4749
verticalAlignment = Alignment.CenterVertically,
48-
horizontalArrangement = Arrangement.spacedBy(12.dp)
50+
horizontalArrangement = Arrangement.spacedBy(12.dp),
51+
modifier = modifier
4952
) {
5053
BadgedItem(badge = {
5154
if (bot) {
@@ -68,13 +71,17 @@ fun PostAuthor(
6871

6972
Column {
7073
Text(
71-
text = EmojiSyntakts.render(displayName, emojis, emptyMap()),
72-
style = MaterialTheme.typography.labelLarge
74+
text = EmojiSyntakts.render(displayName, emojis),
75+
style = MaterialTheme.typography.labelLarge,
76+
overflow = TextOverflow.Ellipsis,
77+
maxLines = 1
7378
)
7479
Text(
7580
text = "@$acct",
7681
style = MaterialTheme.typography.labelMedium,
77-
color = LocalContentColor.current.copy(alpha = 0.5f)
82+
color = LocalContentColor.current.copy(alpha = 0.5f),
83+
overflow = TextOverflow.Ellipsis,
84+
maxLines = 1
7885
)
7986
}
8087
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package xyz.wingio.dimett.ui.widgets.posts
2+
3+
import androidx.compose.foundation.layout.Arrangement
4+
import androidx.compose.foundation.layout.Row
5+
import androidx.compose.foundation.layout.size
6+
import androidx.compose.material.icons.Icons
7+
import androidx.compose.material.icons.filled.Person
8+
import androidx.compose.material3.Icon
9+
import androidx.compose.material3.LocalContentColor
10+
import androidx.compose.material3.MaterialTheme
11+
import androidx.compose.runtime.Composable
12+
import androidx.compose.ui.Alignment
13+
import androidx.compose.ui.Modifier
14+
import androidx.compose.ui.graphics.Color
15+
import androidx.compose.ui.res.stringResource
16+
import androidx.compose.ui.text.style.TextAlign
17+
import androidx.compose.ui.text.style.TextOverflow
18+
import androidx.compose.ui.unit.dp
19+
import xyz.wingio.dimett.R
20+
import xyz.wingio.dimett.ast.EmojiSyntakts
21+
import xyz.wingio.dimett.ast.render
22+
import xyz.wingio.dimett.rest.dto.CustomEmoji
23+
import xyz.wingio.dimett.ui.components.Text
24+
import xyz.wingio.dimett.utils.toEmojiMap
25+
26+
/**
27+
* Displays the users pronouns
28+
*
29+
* @param pronouns The users pronouns
30+
* @param emoji Emoji used in the users profile, possibly used for their pronouns
31+
* @param modifier The [Modifier] used for this component
32+
* @param color Content color for the icon and text
33+
*/
34+
@Composable
35+
fun PostPronouns(
36+
pronouns: String?,
37+
emoji: List<CustomEmoji>,
38+
modifier: Modifier = Modifier,
39+
color: Color = LocalContentColor.current.copy(alpha = 0.5f)
40+
) {
41+
pronouns?.let {
42+
Row(
43+
horizontalArrangement = Arrangement.spacedBy(5.5.dp, Alignment.End),
44+
verticalAlignment = Alignment.CenterVertically,
45+
modifier = modifier
46+
) {
47+
Text(
48+
text = EmojiSyntakts.render(pronouns, emojiMap = emoji.toEmojiMap()),
49+
style = MaterialTheme.typography.labelSmall,
50+
color = color,
51+
maxLines = 1,
52+
overflow = TextOverflow.Ellipsis,
53+
textAlign = TextAlign.End,
54+
modifier = Modifier.weight(1f)
55+
)
56+
57+
Icon(
58+
imageVector = Icons.Filled.Person,
59+
contentDescription = stringResource(R.string.cd_pronouns),
60+
tint = color,
61+
modifier = Modifier.size(13.dp)
62+
)
63+
}
64+
}
65+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package xyz.wingio.dimett.ui.widgets.posts
2+
3+
import android.text.format.DateUtils
4+
import androidx.compose.foundation.layout.Arrangement
5+
import androidx.compose.foundation.layout.Row
6+
import androidx.compose.foundation.layout.size
7+
import androidx.compose.material.icons.Icons
8+
import androidx.compose.material.icons.outlined.Lock
9+
import androidx.compose.material.icons.outlined.Mail
10+
import androidx.compose.material.icons.outlined.Public
11+
import androidx.compose.material.icons.outlined.VpnLock
12+
import androidx.compose.material3.Icon
13+
import androidx.compose.material3.LocalContentColor
14+
import androidx.compose.material3.MaterialTheme
15+
import androidx.compose.material3.Text
16+
import androidx.compose.runtime.Composable
17+
import androidx.compose.runtime.remember
18+
import androidx.compose.ui.Alignment
19+
import androidx.compose.ui.Modifier
20+
import androidx.compose.ui.graphics.Color
21+
import androidx.compose.ui.res.stringResource
22+
import androidx.compose.ui.unit.dp
23+
import kotlinx.datetime.Instant
24+
import xyz.wingio.dimett.R
25+
import xyz.wingio.dimett.rest.dto.post.Post
26+
27+
/**
28+
* Displays a relative timestamp along with the posts visibility
29+
*
30+
* @param createdAt Timestamp for the posts creation
31+
* @param visibility The visibility level of the post
32+
* @param modifier The [Modifier] for this component
33+
* @param color Content color for the icon and text
34+
* @param showVisibility Whether or not to show an icon for the posts [visibility]
35+
*/
36+
@Composable
37+
fun PostTimestamp(
38+
createdAt: Instant,
39+
visibility: Post.Visibility,
40+
modifier: Modifier = Modifier,
41+
color: Color = LocalContentColor.current.copy(alpha = 0.5f),
42+
showVisibility: Boolean = true,
43+
) {
44+
Row(
45+
horizontalArrangement = Arrangement.spacedBy(5.5.dp),
46+
verticalAlignment = Alignment.CenterVertically,
47+
modifier = modifier
48+
) {
49+
val (privacyIcon, privacyIconLabelRes) = remember(visibility) {
50+
when (visibility) {
51+
Post.Visibility.PUBLIC,
52+
Post.Visibility.LOCAL -> Icons.Outlined.Public to R.string.cd_privacy_public
53+
Post.Visibility.PRIVATE -> Icons.Outlined.Lock to R.string.cd_privacy_private
54+
Post.Visibility.DIRECT -> Icons.Outlined.Mail to R.string.cd_privacy_direct
55+
Post.Visibility.UNLISTED -> Icons.Outlined.VpnLock to R.string.cd_privacy_unlisted
56+
}
57+
}
58+
59+
val timeString = remember(createdAt) {
60+
DateUtils.getRelativeTimeSpanString(
61+
/* time = */ createdAt.toEpochMilliseconds(),
62+
/* now = */ System.currentTimeMillis(),
63+
/* minResolution = */ 0L,
64+
/* flags = */ DateUtils.FORMAT_ABBREV_ALL
65+
).toString()
66+
}
67+
68+
Text(
69+
text = timeString,
70+
style = MaterialTheme.typography.labelSmall,
71+
color = color
72+
)
73+
74+
if (showVisibility) {
75+
Icon(
76+
imageVector = privacyIcon,
77+
contentDescription = stringResource(privacyIconLabelRes),
78+
tint = color,
79+
modifier = Modifier.size(13.dp)
80+
)
81+
}
82+
}
83+
}

app/src/main/res/values/strings.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@
2727
<string name="cd_play">Play</string>
2828
<string name="cd_pause">Pause</string>
2929
<string name="cd_thinking_emoji">Thinking Emoji</string>
30+
<string name="cd_pronouns">Pronouns</string>
31+
32+
<string name="cd_privacy_public">Public</string>
33+
<string name="cd_privacy_unlisted">Unlisted</string>
34+
<string name="cd_privacy_private">Private</string>
35+
<string name="cd_privacy_direct">Direct</string>
3036

3137
<string name="post_user_boosted">%1$s boosted</string>
3238
<string name="post_replying_to">Replying to **[@%1$s]{onUserClick}**</string>

0 commit comments

Comments
 (0)