Skip to content

Commit 8ad17b8

Browse files
committed
WIP reaction list viewing
1 parent 517c9a4 commit 8ad17b8

5 files changed

Lines changed: 134 additions & 22 deletions

File tree

shared/src/commonMain/kotlin/site/remlit/snowdrop/App.kt

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,11 +101,16 @@ data class ProfileRoute(val id: String) : Destination(6)
101101
@Serializable
102102
data class ThreadRoute(val id: String) : Destination(7)
103103
@Serializable
104+
data class StatusInteractionDetailRoute(
105+
val id: String,
106+
val type: InteractionViewType
107+
) : Destination(9)
108+
@Serializable
104109
data class ComposeRoute(
105110
val inReplyToId: String? = null,
106111
val cw: String = "",
107112
val content: String = ""
108-
) : Destination(8)
113+
) : Destination(9)
109114

110115
@Serializable
111116
object SettingsRoute : Destination(100)
@@ -322,6 +327,10 @@ fun App() = safe {
322327
val args = it.toRoute<ThreadRoute>()
323328
ThreadView(args.id)
324329
}
330+
composable<StatusInteractionDetailRoute> {
331+
val args = it.toRoute<StatusInteractionDetailRoute>()
332+
StatusInteractionDetailView(args.id, args.type)
333+
}
325334
composable<ProfileRoute> {
326335
val args = it.toRoute<ProfileRoute>()
327336
ProfileView(args.id)
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package site.remlit.snowdrop.api.statuses
2+
3+
import com.russhwolf.settings.ExperimentalSettingsApi
4+
import io.ktor.client.request.get
5+
import io.ktor.client.request.header
6+
import io.ktor.client.request.parameter
7+
import site.remlit.snowdrop.model.ApiResponse
8+
import site.remlit.snowdrop.model.Reaction
9+
import site.remlit.snowdrop.util.config.endOfRequest
10+
import site.remlit.snowdrop.util.config.httpClient
11+
import site.remlit.snowdrop.util.getCurrentAccountHost
12+
import site.remlit.snowdrop.util.getCurrentAccountId
13+
import site.remlit.snowdrop.util.safeApiRequest
14+
import site.remlit.snowdrop.util.settings
15+
16+
@OptIn(ExperimentalSettingsApi::class)
17+
suspend fun getStatusReactions(
18+
id: String,
19+
20+
limit: Int = 45,
21+
emoji: String? = null,
22+
maxId: String? = null,
23+
minId: String? = null,
24+
sinceId: String? = null,
25+
offset: Int? = null,
26+
): ApiResponse<List<Reaction>> = safeApiRequest {
27+
val accountId = getCurrentAccountId()
28+
val host = getCurrentAccountHost()
29+
val token = settings.getString("account_${accountId}_token", "")
30+
31+
val req = httpClient.get("https://$host/api/v1/statuses/$id/reactions") {
32+
header("Authorization", "Bearer $token")
33+
34+
parameter("limit", limit)
35+
36+
if (emoji != null) parameter("emoji", emoji)
37+
if (maxId != null) parameter("max_id", maxId)
38+
if (sinceId != null) parameter("since_id", sinceId)
39+
if (offset != null) parameter("offset", offset)
40+
if (minId != null) parameter("min_id", minId)
41+
}
42+
43+
endOfRequest(req)
44+
}

shared/src/commonMain/kotlin/site/remlit/snowdrop/component/Status.kt

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import androidx.navigation.toRoute
4444
import com.russhwolf.settings.ExperimentalSettingsApi
4545
import org.jetbrains.compose.resources.painterResource
4646
import site.remlit.snowdrop.ProfileRoute
47+
import site.remlit.snowdrop.StatusInteractionDetailRoute
4748
import site.remlit.snowdrop.ThreadRoute
4849
import site.remlit.snowdrop.api.statuses.favouriteStatus
4950
import site.remlit.snowdrop.api.statuses.reblogStatus
@@ -65,6 +66,8 @@ import site.remlit.snowdrop.util.settings
6566
import site.remlit.snowdrop.util.extension.toFormatShort
6667
import site.remlit.snowdrop.util.extension.toRelativeString
6768
import site.remlit.snowdrop.util.getFeature
69+
import site.remlit.snowdrop.view.InteractionViewType
70+
import site.remlit.snowdrop.view.StatusInteractionDetailView
6871
import snowdrop.shared.generated.resources.Res
6972
import snowdrop.shared.generated.resources.icon_add_24px
7073
import snowdrop.shared.generated.resources.icon_bookmark_24px
@@ -508,15 +511,19 @@ fun Status(status: Status) {
508511
leadingIcon = {
509512
Icon(painterResource(Res.drawable.icon_repeat_24px), null)
510513
},
511-
onClick = { }
514+
onClick = {
515+
navHandler.navigate(StatusInteractionDetailRoute(realStatus.id, InteractionViewType.Boost))
516+
}
512517
)
513518

514519
DropdownMenuItem(
515520
text = { Text("Show likes") },
516521
leadingIcon = {
517522
Icon(painterResource(Res.drawable.icon_star_24px), null)
518523
},
519-
onClick = { }
524+
onClick = {
525+
navHandler.navigate(StatusInteractionDetailRoute(realStatus.id, InteractionViewType.Like))
526+
}
520527
)
521528

522529
if (getFeature("reactions"))
@@ -525,7 +532,9 @@ fun Status(status: Status) {
525532
leadingIcon = {
526533
Icon(painterResource(Res.drawable.icon_star_24px), null)
527534
},
528-
onClick = { }
535+
onClick = {
536+
navHandler.navigate(StatusInteractionDetailRoute(realStatus.id, InteractionViewType.Reaction))
537+
}
529538
)
530539

531540
HorizontalDivider()
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package site.remlit.snowdrop.model
2+
3+
import kotlinx.serialization.SerialName
4+
import kotlinx.serialization.Serializable
5+
6+
@Serializable
7+
data class Reaction(
8+
val id: String,
9+
val name: String,
10+
val url: String? = null,
11+
@SerialName("static_url")
12+
val staticUrl: String? = null,
13+
val account: Account
14+
)

shared/src/commonMain/kotlin/site/remlit/snowdrop/view/StatusInteractionDetailView.kt

Lines changed: 54 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@ package site.remlit.snowdrop.view
33
import androidx.compose.foundation.layout.Arrangement
44
import androidx.compose.foundation.layout.Column
55
import androidx.compose.foundation.layout.Row
6+
import androidx.compose.foundation.layout.padding
67
import androidx.compose.foundation.lazy.LazyColumn
78
import androidx.compose.material3.HorizontalDivider
89
import androidx.compose.material3.Icon
910
import androidx.compose.material3.IconButton
1011
import androidx.compose.material3.MaterialTheme
1112
import androidx.compose.material3.PrimaryScrollableTabRow
13+
import androidx.compose.material3.Tab
1214
import androidx.compose.material3.Text
1315
import androidx.compose.material3.TopAppBar
1416
import androidx.compose.runtime.Composable
@@ -17,14 +19,21 @@ import androidx.compose.runtime.mutableStateListOf
1719
import androidx.compose.runtime.mutableStateOf
1820
import androidx.compose.runtime.remember
1921
import androidx.compose.runtime.setValue
22+
import androidx.compose.ui.Modifier
23+
import androidx.compose.ui.text.font.FontWeight
2024
import androidx.compose.ui.unit.dp
25+
import io.ktor.websocket.Frame
2126
import org.jetbrains.compose.resources.painterResource
2227
import site.remlit.snowdrop.api.statuses.getStatusFavouritedBy
28+
import site.remlit.snowdrop.api.statuses.getStatusReactions
2329
import site.remlit.snowdrop.api.statuses.getStatusRebloggedBy
2430
import site.remlit.snowdrop.component.Avatar
31+
import site.remlit.snowdrop.component.Emoji
2532
import site.remlit.snowdrop.component.ViewSurface
2633
import site.remlit.snowdrop.model.Account
2734
import site.remlit.snowdrop.model.ApiResponse
35+
import site.remlit.snowdrop.model.Emoji
36+
import site.remlit.snowdrop.model.Reaction
2837
import site.remlit.snowdrop.util.LocalNavController
2938
import site.remlit.snowdrop.util.SnackbarController
3039
import site.remlit.snowdrop.util.bgIO
@@ -43,6 +52,25 @@ fun StatusInteractionDetailView(
4352
val navHandler = LocalNavController.current
4453
val snackbarHandler = SnackbarController.current
4554

55+
@Composable
56+
fun AccountRow(account: Account) {
57+
Row(
58+
modifier = Modifier.padding(10.dp),
59+
horizontalArrangement = Arrangement.spacedBy(10.dp)
60+
) {
61+
Avatar(account)
62+
63+
Column {
64+
Text(account.displayName ?: account.username, fontWeight = FontWeight.Medium)
65+
Text("@${account.acct}")
66+
}
67+
}
68+
HorizontalDivider(
69+
thickness = 1.dp,
70+
color = MaterialTheme.colorScheme.surfaceContainer
71+
)
72+
}
73+
4674
TopAppBar(
4775
navigationIcon = {
4876
IconButton(onClick = { navHandler.popBackStack() }) {
@@ -76,34 +104,42 @@ fun StatusInteractionDetailView(
76104
// todo: scrolling
77105
LazyColumn {
78106
accounts.forEach { account ->
79-
item {
80-
Row(
81-
horizontalArrangement = Arrangement.spacedBy(10.dp)
82-
) {
83-
Avatar(account)
84-
85-
Column {
86-
Text(account.displayName ?: account.username)
87-
Text("@${account.acct}")
88-
}
89-
}
90-
HorizontalDivider(
91-
thickness = 1.dp,
92-
color = MaterialTheme.colorScheme.surfaceContainer
93-
)
94-
}
107+
item { AccountRow(account) }
95108
}
96109
}
97110
}
98111
// todo: implement this!
99112
InteractionViewType.Reaction -> {
100113
var currentTab by remember { mutableStateOf(0) }
114+
val reactions = remember { mutableStateListOf<Reaction>() }
115+
bgIO {
116+
val res = getStatusReactions(id)
117+
if (res.error || res.response == null) {
118+
res.handleError(snackbarController = snackbarHandler)
119+
return@bgIO
120+
}
121+
reactions.addAll(res.response)
122+
}
101123

124+
// todo: scrolling
102125
PrimaryScrollableTabRow(currentTab) {
103-
126+
reactions.forEach {
127+
val i = reactions.indexOf(it)
128+
Tab(
129+
selected = currentTab == i,
130+
onClick = { currentTab = i },
131+
text = {
132+
if (it.url != null) {
133+
Emoji(Emoji(it.name, it.staticUrl ?: it.url, it.url))
134+
} else Text(it.name)
135+
}
136+
)
137+
}
104138
}
105139
LazyColumn {
106-
140+
reactions.filter { reactions.indexOf(it) == currentTab }.forEach { reaction ->
141+
item { AccountRow(reaction.account) }
142+
}
107143
}
108144
}
109145
}

0 commit comments

Comments
 (0)