A cheap attempt at a native Bluesky client for Android
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

SkeetView: renamed from SkeetRowView

geesawra 00ac89c0 0e810e34

+380 -380
+1 -1
app/src/main/java/industries/geesawra/jerryno/ShowSkeets.kt
··· 42 42 ) { 43 43 viewModel.uiState.skeets.forEach { skeet -> 44 44 item(key = skeet.cid.cid) { 45 - SkeetRowView(viewModel = viewModel, skeet = skeet) 45 + SkeetView(viewModel = viewModel, skeet = skeet) 46 46 } 47 47 } 48 48
+379 -379
app/src/main/java/industries/geesawra/jerryno/SkeetRowView.kt app/src/main/java/industries/geesawra/jerryno/SkeetView.kt
··· 1 - package industries.geesawra.jerryno 2 - 3 - import androidx.browser.customtabs.CustomTabsIntent 4 - import androidx.compose.foundation.clickable 5 - import androidx.compose.foundation.layout.Arrangement 6 - import androidx.compose.foundation.layout.Column 7 - import androidx.compose.foundation.layout.Row 8 - import androidx.compose.foundation.layout.fillMaxSize 9 - import androidx.compose.foundation.layout.fillMaxWidth 10 - import androidx.compose.foundation.layout.height 11 - import androidx.compose.foundation.layout.heightIn 12 - import androidx.compose.foundation.layout.padding 13 - import androidx.compose.foundation.layout.size 14 - import androidx.compose.foundation.layout.sizeIn 15 - import androidx.compose.foundation.shape.CircleShape 16 - import androidx.compose.material3.Card 17 - import androidx.compose.material3.FilterChip 18 - import androidx.compose.material3.HorizontalDivider 19 - import androidx.compose.material3.MaterialTheme 20 - import androidx.compose.material3.OutlinedCard 21 - import androidx.compose.material3.Surface 22 - import androidx.compose.material3.Text 23 - import androidx.compose.runtime.Composable 24 - import androidx.compose.ui.Alignment 25 - import androidx.compose.ui.Modifier 26 - import androidx.compose.ui.draw.clip 27 - import androidx.compose.ui.layout.ContentScale 28 - import androidx.compose.ui.platform.LocalContext 29 - import androidx.compose.ui.text.font.FontWeight 30 - import androidx.compose.ui.unit.dp 31 - import androidx.core.net.toUri 32 - import androidx.media3.common.MimeTypes 33 - import app.bsky.embed.RecordView 34 - import app.bsky.embed.RecordViewRecordUnion 35 - import app.bsky.feed.FeedViewPostReasonUnion 36 - import app.bsky.feed.PostViewEmbedUnion 37 - import app.bsky.feed.ReplyRefParentUnion 38 - import coil3.compose.AsyncImage 39 - import coil3.request.ImageRequest 40 - import coil3.request.crossfade 41 - import industries.geesawra.jerryno.datalayer.SkeetData 42 - import industries.geesawra.jerryno.datalayer.TimelineViewModel 43 - import io.sanghun.compose.video.RepeatMode 44 - import io.sanghun.compose.video.VideoPlayer 45 - import io.sanghun.compose.video.controller.VideoPlayerControllerConfig 46 - import io.sanghun.compose.video.uri.VideoPlayerMediaItem 47 - 48 - @Composable 49 - fun SkeetRowView( 50 - modifier: Modifier = Modifier, 51 - viewModel: TimelineViewModel, 52 - skeet: SkeetData, 53 - nested: Boolean = false 54 - ) { 55 - val likes = skeet.likes 56 - val reposts = skeet.reposts 57 - val replies = skeet.replies 58 - val minSize = 55.dp 59 - 60 - Surface( 61 - color = MaterialTheme.colorScheme.surface, 62 - modifier = modifier.padding(start = 16.dp, end = 16.dp) 63 - ) { 64 - Row( 65 - verticalAlignment = Alignment.Top, 66 - horizontalArrangement = Arrangement.Start, 67 - modifier = Modifier.fillMaxWidth() 68 - ) { 69 - Column( 70 - modifier = Modifier 71 - .weight(1f) 72 - .sizeIn(minHeight = minSize), 73 - ) { 74 - 75 - Row( 76 - modifier = Modifier 77 - .fillMaxSize() 78 - .padding(bottom = 8.dp), 79 - verticalAlignment = Alignment.CenterVertically 80 - ) { 81 - AsyncImage( 82 - model = ImageRequest.Builder(LocalContext.current) 83 - .data(skeet.authorAvatarURL) 84 - .crossfade(true) 85 - .build(), 86 - contentDescription = "Avatar", 87 - modifier = Modifier 88 - .size(minSize) 89 - .clip(CircleShape) 90 - ) 91 - 92 - SkeetHeader(modifier = Modifier.padding(start = 16.dp), skeet = skeet) 93 - } 94 - 95 - SkeetContent(viewModel, skeet, nested) 96 - 97 - if (!nested) { 98 - TimelinePostActionsView( 99 - modifier = Modifier 100 - .fillMaxWidth(), 101 - timelineViewModel = viewModel, 102 - replies = replies, 103 - likes = likes, 104 - reposts = reposts, 105 - postUrl = skeet.shareURL(), 106 - uri = skeet.uri, 107 - cid = skeet.cid, 108 - reposted = skeet.didRepost, 109 - liked = skeet.didLike, 110 - ) 111 - 112 - HorizontalDivider( 113 - color = MaterialTheme.colorScheme.outlineVariant 114 - ) 115 - } 116 - } 117 - 118 - } 119 - } 120 - } 121 - 122 - @Composable 123 - private fun SkeetContent( 124 - timelineViewModel: TimelineViewModel, 125 - skeet: SkeetData, 126 - nested: Boolean = false 127 - ) { 128 - val context = LocalContext.current 129 - 130 - Text( 131 - text = skeet.content, 132 - color = MaterialTheme.colorScheme.onSurface, 133 - style = MaterialTheme.typography.bodyLarge, 134 - ) 135 - 136 - if (skeet.embed == null) { 137 - return 138 - } 139 - 140 - val embed = skeet.embed 141 - 142 - when (embed) { 143 - is PostViewEmbedUnion.ImagesView -> { 144 - val img = embed.value.images 145 - 146 - Card( 147 - modifier = Modifier 148 - .fillMaxWidth() 149 - .padding(top = 8.dp, bottom = 8.dp), 150 - ) { 151 - PostImageGallery( 152 - modifier = Modifier 153 - .fillMaxSize() 154 - .padding(8.dp), 155 - images = img.map { 156 - Image(url = it.thumb.uri, alt = it.alt) 157 - }, 158 - ) 159 - } 160 - } 161 - 162 - is PostViewEmbedUnion.VideoView -> { 163 - Card( 164 - modifier = Modifier 165 - .heightIn(max = 500.dp) 166 - .fillMaxWidth() 167 - .padding(top = 8.dp, bottom = 8.dp), 168 - ) { 169 - VideoPlayer( 170 - mediaItems = listOf( 171 - VideoPlayerMediaItem.NetworkMediaItem( 172 - url = embed.value.playlist.uri, 173 - mimeType = MimeTypes.APPLICATION_M3U8, 174 - ) 175 - ), 176 - handleLifecycle = false, 177 - autoPlay = false, 178 - usePlayerController = true, 179 - enablePip = false, 180 - handleAudioFocus = true, 181 - controllerConfig = VideoPlayerControllerConfig( 182 - showSpeedAndPitchOverlay = false, 183 - showSubtitleButton = false, 184 - showCurrentTimeAndTotalTime = true, 185 - showBufferingProgress = false, 186 - showForwardIncrementButton = true, 187 - showBackwardIncrementButton = true, 188 - showBackTrackButton = false, 189 - showNextTrackButton = false, 190 - showRepeatModeButton = true, 191 - controllerShowTimeMilliSeconds = 5_000, 192 - controllerAutoShow = true, 193 - showFullScreenButton = true, 194 - ), 195 - volume = 0.5f, // volume 0.0f to 1.0f 196 - repeatMode = RepeatMode.NONE, // or RepeatMode.ALL, RepeatMode.ONE 197 - modifier = Modifier 198 - .fillMaxSize() 199 - .padding(8.dp), 200 - ) 201 - } 202 - } 203 - 204 - is PostViewEmbedUnion.ExternalView -> { 205 - val ev = embed.value.external 206 - 207 - OutlinedCard( 208 - modifier = Modifier 209 - .padding(top = 8.dp) 210 - ) { 211 - Column( 212 - modifier = Modifier 213 - .fillMaxSize() 214 - .clickable { 215 - val customTabsIntent = CustomTabsIntent.Builder() 216 - .setShowTitle(true) 217 - .setUrlBarHidingEnabled(true) 218 - .build() 219 - customTabsIntent.launchUrl(context, ev.uri.uri.toUri()) 220 - } 221 - ) { 222 - ev.thumb?.let { 223 - AsyncImage( 224 - model = ImageRequest.Builder(LocalContext.current) 225 - .data(it.uri) 226 - .crossfade(true) 227 - .build(), 228 - contentScale = ContentScale.Crop, 229 - alignment = Alignment.Center, 230 - contentDescription = "External link thumbnail", 231 - modifier = Modifier 232 - .height(180.dp) 233 - .fillMaxWidth() 234 - ) 235 - 236 - HorizontalDivider( 237 - color = MaterialTheme.colorScheme.outlineVariant, 238 - ) 239 - } 240 - Text( 241 - text = ev.title, 242 - style = MaterialTheme.typography.titleSmall, 243 - fontWeight = FontWeight.Bold, 244 - modifier = Modifier 245 - .fillMaxSize() 246 - .padding(top = 8.dp, bottom = 4.dp, start = 8.dp, end = 8.dp), 247 - maxLines = 3 248 - ) 249 - Text( 250 - text = ev.description, 251 - style = MaterialTheme.typography.bodyMedium, 252 - modifier = Modifier 253 - .fillMaxSize() 254 - .padding(bottom = 8.dp, start = 8.dp, end = 8.dp), 255 - maxLines = 8 256 - ) 257 - } 258 - 259 - } 260 - } 261 - 262 - is PostViewEmbedUnion.RecordView -> run { 263 - if (nested) { 264 - return@run 265 - } 266 - 267 - OutlinedCard( 268 - modifier = Modifier.padding(top = 8.dp) 269 - ) { 270 - RecordView( 271 - modifier = Modifier.padding(top = 8.dp, bottom = 8.dp), 272 - timelineViewModel, 273 - embed.value 274 - ) 275 - } 276 - } 277 - 278 - else -> {} 279 - } 280 - 281 - } 282 - 283 - @Composable 284 - private fun RecordView( 285 - modifier: Modifier = Modifier, 286 - viewModel: TimelineViewModel, 287 - rv: RecordView 288 - ) { 289 - val rv = rv.record 290 - when (rv) { 291 - is RecordViewRecordUnion.ViewRecord -> { 292 - SkeetRowView(modifier, viewModel, SkeetData.fromRecordView(rv.value), nested = true) 293 - } 294 - 295 - else -> {} 296 - } 297 - } 298 - 299 - @Composable 300 - private fun SkeetHeader(skeet: SkeetData, modifier: Modifier = Modifier) { 301 - val authorName = skeet.authorName ?: skeet.authorHandle.handle 302 - 303 - Column(modifier = modifier) { 304 - skeet.reason?.let { 305 - it 306 - when (it) { 307 - is FeedViewPostReasonUnion.ReasonRepost -> { 308 - Text( 309 - text = "Reposted by ${it.value.by.displayName ?: it.value.by.handle.toString()}", 310 - color = MaterialTheme.colorScheme.onSurfaceVariant, 311 - style = MaterialTheme.typography.labelMedium, 312 - modifier = Modifier 313 - .fillMaxSize() 314 - .padding(bottom = 4.dp), 315 - fontWeight = FontWeight.Bold 316 - ) 317 - } 318 - 319 - else -> {} 320 - } 321 - } 322 - 323 - Text( 324 - text = authorName, 325 - color = MaterialTheme.colorScheme.onSurface, 326 - style = MaterialTheme.typography.labelLarge, 327 - fontWeight = FontWeight.Bold 328 - ) 329 - 330 - Text( 331 - text = "@" + skeet.authorHandle, 332 - color = MaterialTheme.colorScheme.onSurfaceVariant, 333 - style = MaterialTheme.typography.bodySmall, 334 - modifier = Modifier 335 - .padding(top = 4.dp), 336 - ) 337 - 338 - skeet.authorLabels.forEach { 339 - it.neg?.let { it -> 340 - if (!it) { 341 - return@forEach 342 - } 343 - } 344 - if (it.`val`.startsWith("!")) { 345 - return@forEach 346 - } 347 - 348 - FilterChip( 349 - leadingIcon = { 350 - }, 351 - enabled = true, 352 - onClick = {}, 353 - selected = true, 354 - label = { 355 - Text(text = it.`val`) 356 - } 357 - ) 358 - } 359 - 360 - skeet.reply?.let { 361 - it 362 - val parent = it.parent 363 - when (parent) { 364 - is ReplyRefParentUnion.PostView -> { 365 - Text( 366 - text = "In reply to ${parent.value.author.displayName ?: parent.value.author.handle.toString()}", 367 - color = MaterialTheme.colorScheme.onSurfaceVariant, 368 - style = MaterialTheme.typography.labelSmall, 369 - modifier = Modifier 370 - .fillMaxSize() 371 - .padding(top = 4.dp), 372 - ) 373 - } 374 - 375 - else -> {} 376 - } 377 - } 378 - } 379 - } 1 + package industries.geesawra.jerryno 2 + 3 + import androidx.browser.customtabs.CustomTabsIntent 4 + import androidx.compose.foundation.clickable 5 + import androidx.compose.foundation.layout.Arrangement 6 + import androidx.compose.foundation.layout.Column 7 + import androidx.compose.foundation.layout.Row 8 + import androidx.compose.foundation.layout.fillMaxSize 9 + import androidx.compose.foundation.layout.fillMaxWidth 10 + import androidx.compose.foundation.layout.height 11 + import androidx.compose.foundation.layout.heightIn 12 + import androidx.compose.foundation.layout.padding 13 + import androidx.compose.foundation.layout.size 14 + import androidx.compose.foundation.layout.sizeIn 15 + import androidx.compose.foundation.shape.CircleShape 16 + import androidx.compose.material3.Card 17 + import androidx.compose.material3.FilterChip 18 + import androidx.compose.material3.HorizontalDivider 19 + import androidx.compose.material3.MaterialTheme 20 + import androidx.compose.material3.OutlinedCard 21 + import androidx.compose.material3.Surface 22 + import androidx.compose.material3.Text 23 + import androidx.compose.runtime.Composable 24 + import androidx.compose.ui.Alignment 25 + import androidx.compose.ui.Modifier 26 + import androidx.compose.ui.draw.clip 27 + import androidx.compose.ui.layout.ContentScale 28 + import androidx.compose.ui.platform.LocalContext 29 + import androidx.compose.ui.text.font.FontWeight 30 + import androidx.compose.ui.unit.dp 31 + import androidx.core.net.toUri 32 + import androidx.media3.common.MimeTypes 33 + import app.bsky.embed.RecordView 34 + import app.bsky.embed.RecordViewRecordUnion 35 + import app.bsky.feed.FeedViewPostReasonUnion 36 + import app.bsky.feed.PostViewEmbedUnion 37 + import app.bsky.feed.ReplyRefParentUnion 38 + import coil3.compose.AsyncImage 39 + import coil3.request.ImageRequest 40 + import coil3.request.crossfade 41 + import industries.geesawra.jerryno.datalayer.SkeetData 42 + import industries.geesawra.jerryno.datalayer.TimelineViewModel 43 + import io.sanghun.compose.video.RepeatMode 44 + import io.sanghun.compose.video.VideoPlayer 45 + import io.sanghun.compose.video.controller.VideoPlayerControllerConfig 46 + import io.sanghun.compose.video.uri.VideoPlayerMediaItem 47 + 48 + @Composable 49 + fun SkeetView( 50 + modifier: Modifier = Modifier, 51 + viewModel: TimelineViewModel, 52 + skeet: SkeetData, 53 + nested: Boolean = false 54 + ) { 55 + val likes = skeet.likes 56 + val reposts = skeet.reposts 57 + val replies = skeet.replies 58 + val minSize = 55.dp 59 + 60 + Surface( 61 + color = MaterialTheme.colorScheme.surface, 62 + modifier = modifier.padding(start = 16.dp, end = 16.dp) 63 + ) { 64 + Row( 65 + verticalAlignment = Alignment.Top, 66 + horizontalArrangement = Arrangement.Start, 67 + modifier = Modifier.fillMaxWidth() 68 + ) { 69 + Column( 70 + modifier = Modifier 71 + .weight(1f) 72 + .sizeIn(minHeight = minSize), 73 + ) { 74 + 75 + Row( 76 + modifier = Modifier 77 + .fillMaxSize() 78 + .padding(bottom = 8.dp), 79 + verticalAlignment = Alignment.CenterVertically 80 + ) { 81 + AsyncImage( 82 + model = ImageRequest.Builder(LocalContext.current) 83 + .data(skeet.authorAvatarURL) 84 + .crossfade(true) 85 + .build(), 86 + contentDescription = "Avatar", 87 + modifier = Modifier 88 + .size(minSize) 89 + .clip(CircleShape) 90 + ) 91 + 92 + SkeetHeader(modifier = Modifier.padding(start = 16.dp), skeet = skeet) 93 + } 94 + 95 + SkeetContent(viewModel, skeet, nested) 96 + 97 + if (!nested) { 98 + TimelinePostActionsView( 99 + modifier = Modifier 100 + .fillMaxWidth(), 101 + timelineViewModel = viewModel, 102 + replies = replies, 103 + likes = likes, 104 + reposts = reposts, 105 + postUrl = skeet.shareURL(), 106 + uri = skeet.uri, 107 + cid = skeet.cid, 108 + reposted = skeet.didRepost, 109 + liked = skeet.didLike, 110 + ) 111 + 112 + HorizontalDivider( 113 + color = MaterialTheme.colorScheme.outlineVariant 114 + ) 115 + } 116 + } 117 + 118 + } 119 + } 120 + } 121 + 122 + @Composable 123 + private fun SkeetContent( 124 + timelineViewModel: TimelineViewModel, 125 + skeet: SkeetData, 126 + nested: Boolean = false 127 + ) { 128 + val context = LocalContext.current 129 + 130 + Text( 131 + text = skeet.content, 132 + color = MaterialTheme.colorScheme.onSurface, 133 + style = MaterialTheme.typography.bodyLarge, 134 + ) 135 + 136 + if (skeet.embed == null) { 137 + return 138 + } 139 + 140 + val embed = skeet.embed 141 + 142 + when (embed) { 143 + is PostViewEmbedUnion.ImagesView -> { 144 + val img = embed.value.images 145 + 146 + Card( 147 + modifier = Modifier 148 + .fillMaxWidth() 149 + .padding(top = 8.dp, bottom = 8.dp), 150 + ) { 151 + PostImageGallery( 152 + modifier = Modifier 153 + .fillMaxSize() 154 + .padding(8.dp), 155 + images = img.map { 156 + Image(url = it.thumb.uri, alt = it.alt) 157 + }, 158 + ) 159 + } 160 + } 161 + 162 + is PostViewEmbedUnion.VideoView -> { 163 + Card( 164 + modifier = Modifier 165 + .heightIn(max = 500.dp) 166 + .fillMaxWidth() 167 + .padding(top = 8.dp, bottom = 8.dp), 168 + ) { 169 + VideoPlayer( 170 + mediaItems = listOf( 171 + VideoPlayerMediaItem.NetworkMediaItem( 172 + url = embed.value.playlist.uri, 173 + mimeType = MimeTypes.APPLICATION_M3U8, 174 + ) 175 + ), 176 + handleLifecycle = false, 177 + autoPlay = false, 178 + usePlayerController = true, 179 + enablePip = false, 180 + handleAudioFocus = true, 181 + controllerConfig = VideoPlayerControllerConfig( 182 + showSpeedAndPitchOverlay = false, 183 + showSubtitleButton = false, 184 + showCurrentTimeAndTotalTime = true, 185 + showBufferingProgress = false, 186 + showForwardIncrementButton = true, 187 + showBackwardIncrementButton = true, 188 + showBackTrackButton = false, 189 + showNextTrackButton = false, 190 + showRepeatModeButton = true, 191 + controllerShowTimeMilliSeconds = 5_000, 192 + controllerAutoShow = true, 193 + showFullScreenButton = true, 194 + ), 195 + volume = 0.5f, // volume 0.0f to 1.0f 196 + repeatMode = RepeatMode.NONE, // or RepeatMode.ALL, RepeatMode.ONE 197 + modifier = Modifier 198 + .fillMaxSize() 199 + .padding(8.dp), 200 + ) 201 + } 202 + } 203 + 204 + is PostViewEmbedUnion.ExternalView -> { 205 + val ev = embed.value.external 206 + 207 + OutlinedCard( 208 + modifier = Modifier 209 + .padding(top = 8.dp) 210 + ) { 211 + Column( 212 + modifier = Modifier 213 + .fillMaxSize() 214 + .clickable { 215 + val customTabsIntent = CustomTabsIntent.Builder() 216 + .setShowTitle(true) 217 + .setUrlBarHidingEnabled(true) 218 + .build() 219 + customTabsIntent.launchUrl(context, ev.uri.uri.toUri()) 220 + } 221 + ) { 222 + ev.thumb?.let { 223 + AsyncImage( 224 + model = ImageRequest.Builder(LocalContext.current) 225 + .data(it.uri) 226 + .crossfade(true) 227 + .build(), 228 + contentScale = ContentScale.Crop, 229 + alignment = Alignment.Center, 230 + contentDescription = "External link thumbnail", 231 + modifier = Modifier 232 + .height(180.dp) 233 + .fillMaxWidth() 234 + ) 235 + 236 + HorizontalDivider( 237 + color = MaterialTheme.colorScheme.outlineVariant, 238 + ) 239 + } 240 + Text( 241 + text = ev.title, 242 + style = MaterialTheme.typography.titleSmall, 243 + fontWeight = FontWeight.Bold, 244 + modifier = Modifier 245 + .fillMaxSize() 246 + .padding(top = 8.dp, bottom = 4.dp, start = 8.dp, end = 8.dp), 247 + maxLines = 3 248 + ) 249 + Text( 250 + text = ev.description, 251 + style = MaterialTheme.typography.bodyMedium, 252 + modifier = Modifier 253 + .fillMaxSize() 254 + .padding(bottom = 8.dp, start = 8.dp, end = 8.dp), 255 + maxLines = 8 256 + ) 257 + } 258 + 259 + } 260 + } 261 + 262 + is PostViewEmbedUnion.RecordView -> run { 263 + if (nested) { 264 + return@run 265 + } 266 + 267 + OutlinedCard( 268 + modifier = Modifier.padding(top = 8.dp) 269 + ) { 270 + RecordView( 271 + modifier = Modifier.padding(top = 8.dp, bottom = 8.dp), 272 + timelineViewModel, 273 + embed.value 274 + ) 275 + } 276 + } 277 + 278 + else -> {} 279 + } 280 + 281 + } 282 + 283 + @Composable 284 + private fun RecordView( 285 + modifier: Modifier = Modifier, 286 + viewModel: TimelineViewModel, 287 + rv: RecordView 288 + ) { 289 + val rv = rv.record 290 + when (rv) { 291 + is RecordViewRecordUnion.ViewRecord -> { 292 + SkeetView(modifier, viewModel, SkeetData.fromRecordView(rv.value), nested = true) 293 + } 294 + 295 + else -> {} 296 + } 297 + } 298 + 299 + @Composable 300 + private fun SkeetHeader(skeet: SkeetData, modifier: Modifier = Modifier) { 301 + val authorName = skeet.authorName ?: skeet.authorHandle.handle 302 + 303 + Column(modifier = modifier) { 304 + skeet.reason?.let { 305 + it 306 + when (it) { 307 + is FeedViewPostReasonUnion.ReasonRepost -> { 308 + Text( 309 + text = "Reposted by ${it.value.by.displayName ?: it.value.by.handle.toString()}", 310 + color = MaterialTheme.colorScheme.onSurfaceVariant, 311 + style = MaterialTheme.typography.labelMedium, 312 + modifier = Modifier 313 + .fillMaxSize() 314 + .padding(bottom = 4.dp), 315 + fontWeight = FontWeight.Bold 316 + ) 317 + } 318 + 319 + else -> {} 320 + } 321 + } 322 + 323 + Text( 324 + text = authorName, 325 + color = MaterialTheme.colorScheme.onSurface, 326 + style = MaterialTheme.typography.labelLarge, 327 + fontWeight = FontWeight.Bold 328 + ) 329 + 330 + Text( 331 + text = "@" + skeet.authorHandle, 332 + color = MaterialTheme.colorScheme.onSurfaceVariant, 333 + style = MaterialTheme.typography.bodySmall, 334 + modifier = Modifier 335 + .padding(top = 4.dp), 336 + ) 337 + 338 + skeet.authorLabels.forEach { 339 + it.neg?.let { it -> 340 + if (!it) { 341 + return@forEach 342 + } 343 + } 344 + if (it.`val`.startsWith("!")) { 345 + return@forEach 346 + } 347 + 348 + FilterChip( 349 + leadingIcon = { 350 + }, 351 + enabled = true, 352 + onClick = {}, 353 + selected = true, 354 + label = { 355 + Text(text = it.`val`) 356 + } 357 + ) 358 + } 359 + 360 + skeet.reply?.let { 361 + it 362 + val parent = it.parent 363 + when (parent) { 364 + is ReplyRefParentUnion.PostView -> { 365 + Text( 366 + text = "In reply to ${parent.value.author.displayName ?: parent.value.author.handle.toString()}", 367 + color = MaterialTheme.colorScheme.onSurfaceVariant, 368 + style = MaterialTheme.typography.labelSmall, 369 + modifier = Modifier 370 + .fillMaxSize() 371 + .padding(top = 4.dp), 372 + ) 373 + } 374 + 375 + else -> {} 376 + } 377 + } 378 + } 379 + }