tangled
alpha
login
or
join now
geesawra.industries
/
jerry-no
8
fork
atom
A cheap attempt at a native Bluesky client for Android
8
fork
atom
overview
issues
pulls
pipelines
SkeetView: show raw labels
geesawra.industries
5 months ago
99929229
16b64610
0/1
build.yaml
timeout
7m 17s
+93
-20
3 changed files
expand all
collapse all
unified
split
app
src
main
java
industries
geesawra
monarch
SkeetView.kt
datalayer
Bluesky.kt
TimelineViewModel.kt
+27
-17
app/src/main/java/industries/geesawra/monarch/SkeetView.kt
···
9
9
import androidx.compose.foundation.clickable
10
10
import androidx.compose.foundation.layout.Arrangement
11
11
import androidx.compose.foundation.layout.Column
12
12
+
import androidx.compose.foundation.layout.ExperimentalLayoutApi
13
13
+
import androidx.compose.foundation.layout.FlowRow
12
14
import androidx.compose.foundation.layout.Row
13
15
import androidx.compose.foundation.layout.fillMaxSize
14
16
import androidx.compose.foundation.layout.fillMaxWidth
···
19
21
import androidx.compose.foundation.layout.sizeIn
20
22
import androidx.compose.foundation.shape.CircleShape
21
23
import androidx.compose.material3.Card
22
22
-
import androidx.compose.material3.FilterChip
23
24
import androidx.compose.material3.HorizontalDivider
24
25
import androidx.compose.material3.MaterialTheme
25
26
import androidx.compose.material3.OutlinedCard
···
32
33
import androidx.compose.ui.graphics.Color
33
34
import androidx.compose.ui.layout.ContentScale
34
35
import androidx.compose.ui.platform.LocalContext
36
36
+
import androidx.compose.ui.text.TextStyle
35
37
import androidx.compose.ui.text.font.FontWeight
36
38
import androidx.compose.ui.text.style.TextAlign
37
39
import androidx.compose.ui.unit.dp
40
40
+
import androidx.compose.ui.unit.sp
38
41
import androidx.core.net.toUri
39
42
import androidx.media3.common.MimeTypes
40
43
import app.bsky.embed.ExternalViewExternal
···
393
396
}
394
397
}
395
398
399
399
+
@OptIn(ExperimentalLayoutApi::class)
396
400
@Composable
397
401
private fun SkeetHeader(skeet: SkeetData, modifier: Modifier = Modifier) {
398
402
val authorName = skeet.authorName ?: (skeet.authorHandle?.handle ?: "")
···
430
434
style = MaterialTheme.typography.bodySmall,
431
435
)
432
436
433
433
-
skeet.authorLabels.forEach {
434
434
-
it.neg?.let { it ->
435
435
-
if (!it) {
437
437
+
FlowRow(
438
438
+
horizontalArrangement = Arrangement.spacedBy(4.dp),
439
439
+
modifier = Modifier.padding(top = 4.dp)
440
440
+
) {
441
441
+
skeet.authorLabels.forEach {
442
442
+
it.neg?.let { it ->
443
443
+
if (!it) {
444
444
+
return@forEach
445
445
+
}
446
446
+
}
447
447
+
if (it.`val`.startsWith("!")) {
436
448
return@forEach
437
449
}
438
438
-
}
439
439
-
if (it.`val`.startsWith("!")) {
440
440
-
return@forEach
441
441
-
}
442
450
443
443
-
FilterChip(
444
444
-
leadingIcon = {
445
445
-
},
446
446
-
enabled = true,
447
447
-
onClick = {},
448
448
-
selected = true,
449
449
-
label = {
450
450
-
Text(text = it.`val`)
451
451
+
Card(
452
452
+
modifier = Modifier.padding(end = 4.dp, bottom = 4.dp),
453
453
+
shape = CircleShape
454
454
+
) {
455
455
+
Text(
456
456
+
modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp),
457
457
+
text = it.`val`,
458
458
+
style = TextStyle(fontSize = 12.sp),
459
459
+
)
451
460
}
452
452
-
)
461
461
+
}
453
462
}
463
463
+
454
464
455
465
skeet.reply?.let {
456
466
it
+65
-2
app/src/main/java/industries/geesawra/monarch/datalayer/Bluesky.kt
···
28
28
import app.bsky.feed.PostEmbedUnion
29
29
import app.bsky.feed.PostReplyRef
30
30
import app.bsky.feed.Repost
31
31
+
import app.bsky.labeler.GetServicesQueryParams
32
32
+
import app.bsky.labeler.GetServicesResponse
33
33
+
import app.bsky.labeler.GetServicesResponseViewUnion
31
34
import app.bsky.notification.ListNotificationsQueryParams
32
35
import app.bsky.notification.ListNotificationsResponse
33
36
import app.bsky.video.GetJobStatusQueryParams
···
292
295
val message: String?,
293
296
)
294
297
295
295
-
private suspend fun refreshIfNeeded(pdsURL: String, token: SessionData): Result<Unit> {
298
298
+
private suspend fun refreshIfNeeded(
299
299
+
pdsURL: String,
300
300
+
token: SessionData,
301
301
+
labelers: List<String>? = listOf()
302
302
+
): Result<Unit> {
296
303
return runCatching {
297
304
val httpClient = HttpClient(OkHttp) {
298
305
defaultRequest {
299
306
url(pdsURL)
307
307
+
labelers?.let {
308
308
+
headers["atproto-accept-labelers"] = labelers.joinToString()
309
309
+
}
300
310
}
301
311
install(HttpTimeout) {
302
312
requestTimeoutMillis = 15000
···
314
324
}
315
325
316
326
when (gs.status) {
317
317
-
318
327
HttpStatusCode.OK -> run {
319
328
this.session = token
320
329
val tokens =
···
411
420
412
421
this.pdsURL = pdsURL
413
422
423
423
+
val labelers = this.subscribedLabelers().getOrThrow().keys.mapNotNull { it?.did }
424
424
+
425
425
+
refreshIfNeeded(pdsURL, sessionData, labelers).onFailure {
426
426
+
createMutex.unlock()
427
427
+
return Result.failure(it)
428
428
+
}
429
429
+
414
430
createMutex.unlock()
415
431
}
416
432
}
···
734
750
).requireResponse()
735
751
736
752
return Result.success(resp.feeds)
753
753
+
}
754
754
+
}
755
755
+
756
756
+
suspend fun subscribedLabelers(): Result<Map<Did?, GetServicesResponseViewUnion.LabelerViewDetailed?>> {
757
757
+
return runCatching {
758
758
+
val prefs = client!!.getPreferences().requireResponse()
759
759
+
val labelers = (prefs.preferences.first {
760
760
+
when (it) {
761
761
+
is PreferencesUnion.LabelersPref -> true
762
762
+
else -> false
763
763
+
}
764
764
+
} as PreferencesUnion.LabelersPref).value.labelers.map { it.did }.toMutableList()
765
765
+
766
766
+
val otherOnes = (prefs.preferences.filter {
767
767
+
when (it) {
768
768
+
is PreferencesUnion.ContentLabelPref -> true
769
769
+
else -> false
770
770
+
}
771
771
+
}.map { it as PreferencesUnion.ContentLabelPref }).forEach {
772
772
+
it.value.labelerDid?.let { did ->
773
773
+
labelers += did
774
774
+
}
775
775
+
}
776
776
+
777
777
+
val res = client!!.getServices(
778
778
+
GetServicesQueryParams(
779
779
+
detailed = true,
780
780
+
dids = labelers
781
781
+
)
782
782
+
)
783
783
+
784
784
+
val asd = when (res) {
785
785
+
is AtpResponse.Failure<*> -> return Result.failure(Exception("Failed to fetch subscribed labelers: ${res.error}"))
786
786
+
is AtpResponse.Success<GetServicesResponse> -> {
787
787
+
res
788
788
+
}
789
789
+
}
790
790
+
791
791
+
val kek = asd.response.views.associate {
792
792
+
when (it) {
793
793
+
is GetServicesResponseViewUnion.LabelerView -> it.value.uri.did() to null
794
794
+
is GetServicesResponseViewUnion.LabelerViewDetailed -> it.value.uri.did() to it
795
795
+
is GetServicesResponseViewUnion.Unknown -> null to null
796
796
+
}
797
797
+
}.filter { it.value != null && it.key != null }
798
798
+
799
799
+
return Result.success(kek)
737
800
}
738
801
}
739
802
+1
-1
app/src/main/java/industries/geesawra/monarch/datalayer/TimelineViewModel.kt
···
244
244
}
245
245
}
246
246
}
247
247
-
247
247
+
248
248
fun selectFeed(uri: String, displayName: String, avatar: String?, then: () -> Unit = {}) {
249
249
uiState = uiState.copy(
250
250
selectedFeed = uri,