A cheap attempt at a native Bluesky client for Android

*: proto-thread view

needs more work, committing and pushing since i might've solved some performance issues w/lists

+94 -47
+11 -9
app/src/main/java/industries/geesawra/monarch/NotificationsView.kt
··· 8 8 import androidx.compose.foundation.layout.width 9 9 import androidx.compose.foundation.lazy.LazyColumn 10 10 import androidx.compose.foundation.lazy.LazyListState 11 + import androidx.compose.foundation.lazy.items 11 12 import androidx.compose.material3.Card 12 13 import androidx.compose.material3.CircularProgressIndicator 13 14 import androidx.compose.runtime.Composable ··· 40 41 userScrollEnabled = isScrollEnabled, 41 42 verticalArrangement = Arrangement.spacedBy(16.dp), 42 43 ) { 43 - viewModel.uiState.notifications.forEach { notif -> 44 - item(notif.createdAt()) { 45 - Card { 46 - RenderNotification( 47 - viewModel = viewModel, 48 - notification = notif, 49 - onReplyTap = onReplyTap 50 - ) 51 - } 44 + items( 45 + items = viewModel.uiState.notifications, 46 + key = { it.createdAt() } 47 + ) { notif -> 48 + Card { 49 + RenderNotification( 50 + viewModel = viewModel, 51 + notification = notif, 52 + onReplyTap = onReplyTap 53 + ) 52 54 } 53 55 } 54 56
+21 -3
app/src/main/java/industries/geesawra/monarch/ShowSkeets.kt
··· 70 70 viewModel = viewModel, 71 71 skeet = it, 72 72 onReplyTap = onReplyTap, 73 - inThread = true 73 + inThread = true, 74 + // onShowThread = { 75 + // if (onSeeMoreTap != null) { 76 + // viewModel.setThread(root) 77 + // onSeeMoreTap(root) 78 + // } 79 + // } 74 80 ) 75 81 } 76 82 ··· 99 105 viewModel = viewModel, 100 106 skeet = it, 101 107 onReplyTap = onReplyTap, 102 - inThread = true 108 + inThread = true, 109 + // onShowThread = { 110 + // if (onSeeMoreTap != null) { 111 + // viewModel.setThread(parent) 112 + // onSeeMoreTap(parent) 113 + // } 114 + // } 103 115 ) 104 116 } 105 117 } ··· 110 122 viewModel = viewModel, 111 123 skeet = skeet, 112 124 onReplyTap = onReplyTap, 113 - showInReplyTo = parent == null 125 + showInReplyTo = parent == null, 126 + // onShowThread = { 127 + // if (onSeeMoreTap != null) { 128 + // viewModel.setThread(skeet) 129 + // onSeeMoreTap(skeet) 130 + // } 131 + // } 114 132 ) 115 133 } 116 134 }
+5 -1
app/src/main/java/industries/geesawra/monarch/SkeetView.kt
··· 69 69 nested: Boolean = false, 70 70 disableEmbeds: Boolean = false, 71 71 inThread: Boolean = false, 72 - showInReplyTo: Boolean = true 72 + showInReplyTo: Boolean = true, 73 + onShowThread: (SkeetData) -> Unit = {}, 73 74 ) { 74 75 if (skeet.blocked) { 75 76 ConditionalCard(text = "Blocked :(", wrapWithCard = !nested) ··· 89 90 modifier 90 91 .padding(top = 8.dp, start = 16.dp, end = 16.dp) 91 92 .background(Color.Transparent) 93 + .clickable { 94 + onShowThread(skeet) 95 + } 92 96 ) { 93 97 Row( 94 98 verticalAlignment = Alignment.Top,
+14 -10
app/src/main/java/industries/geesawra/monarch/ThreadView.kt
··· 1 1 package industries.geesawra.monarch 2 2 3 3 import android.util.Log 4 + import androidx.compose.foundation.layout.Column 4 5 import androidx.compose.foundation.layout.fillMaxSize 5 6 import androidx.compose.foundation.layout.padding 6 7 import androidx.compose.material3.ExperimentalMaterial3Api ··· 70 71 } 71 72 } 72 73 73 - timelineViewModel.uiState.currentlyShownThread.fastForEach { threadView -> 74 - ShowSkeets( 75 - modifier = Modifier.padding(padding), 76 - viewModel = timelineViewModel, 77 - isScrollEnabled = true, 78 - data = threadView, 79 - shouldFetchMoreData = false, 80 - isShowingThread = true, 81 - ) 74 + Column( 75 + modifier = Modifier.padding(padding) 76 + ) { 77 + timelineViewModel.uiState.currentlyShownThread.fastForEach { threadView -> 78 + ShowSkeets( 79 + viewModel = timelineViewModel, 80 + isScrollEnabled = true, 81 + data = threadView, 82 + shouldFetchMoreData = false, 83 + isShowingThread = true, 84 + ) 82 85 83 - HorizontalDivider() 86 + HorizontalDivider() 87 + } 84 88 } 85 89 } 86 90 }
+43 -24
app/src/main/java/industries/geesawra/monarch/datalayer/TimelineViewModel.kt
··· 506 506 uiState = uiState.copy(currentlyShownThread = listOf(listOf(tappedElement))) 507 507 } 508 508 509 - private fun getAllThreads(thread: GetPostThreadResponseThreadUnion): List<List<SkeetData>> { 509 + private fun getAllThreads( 510 + thread: GetPostThreadResponseThreadUnion, 511 + startingLevel: Int = 0 512 + ): List<List<SkeetData>> { 510 513 if (thread !is GetPostThreadResponseThreadUnion.ThreadViewPost) { 511 514 return when (thread) { 512 515 is GetPostThreadResponseThreadUnion.BlockedPost -> listOf(listOf(SkeetData(blocked = true))) ··· 518 521 val rootSkeet = SkeetData.fromPostView(thread.value.post, thread.value.post.author) 519 522 val threads = mutableListOf<List<SkeetData>>() 520 523 521 - fun findPaths(current: ThreadViewPostReplieUnion, currentPath: List<SkeetData>, level: Int) { 522 - val skeet = when (current) { 523 - is ThreadViewPostReplieUnion.ThreadViewPost -> SkeetData.fromPostView( 524 - current.value.post, 525 - current.value.post.author 526 - ).copy(nestingLevel = level) 524 + // fun findPaths(current: ThreadViewPostReplieUnion, currentPath: List<SkeetData>, level: Int) { 525 + // val skeet = when (current) { 526 + // is ThreadViewPostReplieUnion.ThreadViewPost -> SkeetData.fromPostView( 527 + // current.value.post, 528 + // current.value.post.author 529 + // ).copy(nestingLevel = level) 530 + // 531 + // is ThreadViewPostReplieUnion.BlockedPost -> SkeetData(blocked = true) 532 + // is ThreadViewPostReplieUnion.NotFoundPost -> SkeetData(notFound = true) 533 + // else -> null 534 + // } 535 + // 536 + // if (skeet == null) return 537 + // 538 + // val newPath = currentPath + skeet 539 + // val replies = (current as? ThreadViewPostReplieUnion.ThreadViewPost)?.value?.replies 540 + // 541 + // if (replies.isNullOrEmpty()) { 542 + // threads.add(newPath) 543 + // } else { 544 + // replies.forEach { findPaths(it, newPath, level + 1) } 545 + // } 546 + // } 547 + // 527 548 528 - is ThreadViewPostReplieUnion.BlockedPost -> SkeetData(blocked = true) 529 - is ThreadViewPostReplieUnion.NotFoundPost -> SkeetData(notFound = true) 530 - else -> null 531 - } 549 + val list = mutableListOf<SkeetData>() 532 550 533 - if (skeet == null) return 551 + thread.value.replies.forEach { reply -> 552 + when (reply) { 553 + is ThreadViewPostReplieUnion.BlockedPost -> listOf(listOf(SkeetData(blocked = true))) 554 + is ThreadViewPostReplieUnion.NotFoundPost -> listOf(listOf(SkeetData(notFound = true))) 555 + is ThreadViewPostReplieUnion.ThreadViewPost -> run { 556 + list.add(SkeetData.fromPostView(reply.value.post, reply.value.post.author)) 557 + threads += getAllThreads( 558 + GetPostThreadResponseThreadUnion.ThreadViewPost(reply.value), 559 + startingLevel + 1 560 + ) 561 + } 534 562 535 - val newPath = currentPath + skeet 536 - val replies = (current as? ThreadViewPostReplieUnion.ThreadViewPost)?.value?.replies 537 - 538 - if (replies.isNullOrEmpty()) { 539 - threads.add(newPath) 540 - } else { 541 - replies.forEach { findPaths(it, newPath, level + 1) } 563 + is ThreadViewPostReplieUnion.Unknown -> {} 542 564 } 543 565 } 544 566 545 - if (thread.value.replies.isEmpty()) { 546 - threads.add(listOf(rootSkeet)) 547 - } else { 548 - thread.value.replies.forEach { findPaths(it, listOf(rootSkeet), 1) } 549 - } 567 + 568 + threads.add(list) 550 569 551 570 return threads.map { it.sortedBy { skeet -> skeet.createdAt } } 552 571 }