[1.495.*] Pre-release merge (#1095)
This commit is contained in:
@@ -56,7 +56,14 @@ fun DatabasePosts(
|
||||
items.forEach { (month, posts) ->
|
||||
stickyHeader(contentType = "month-header") { MonthHeader(label = month) }
|
||||
items(items = posts, key = { it.shortId }, contentType = { "LobstersItem" }) { item ->
|
||||
LobstersListItem(item = item, postActions = postActions)
|
||||
val isSaved = postActions.isPostSaved(item)
|
||||
val isRead = postActions.isPostRead(item)
|
||||
LobstersListItem(
|
||||
item = item,
|
||||
isSaved = isSaved,
|
||||
isRead = isRead,
|
||||
postActions = postActions,
|
||||
)
|
||||
HorizontalDivider()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,13 @@ import me.saket.swipe.SwipeAction
|
||||
import me.saket.swipe.SwipeableActionsBox
|
||||
|
||||
@Composable
|
||||
fun LobstersListItem(item: UIPost, postActions: PostActions, modifier: Modifier = Modifier) {
|
||||
fun LobstersListItem(
|
||||
item: UIPost,
|
||||
isSaved: Boolean,
|
||||
isRead: Boolean,
|
||||
postActions: PostActions,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val commentsAction =
|
||||
SwipeAction(
|
||||
icon = rememberVectorPainter(Icons.AutoMirrored.Filled.Reply),
|
||||
@@ -44,12 +50,25 @@ fun LobstersListItem(item: UIPost, postActions: PostActions, modifier: Modifier
|
||||
swipeThreshold = 80.dp,
|
||||
backgroundUntilSwipeThreshold = MaterialTheme.colorScheme.surfaceVariant,
|
||||
) {
|
||||
LobstersCard(post = item, postActions = postActions, modifier = modifier)
|
||||
LobstersCard(
|
||||
post = item,
|
||||
isSaved = isSaved,
|
||||
isRead = isRead,
|
||||
postActions = postActions,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ThemePreviews
|
||||
@Composable
|
||||
private fun ItemPreview() {
|
||||
LobstersTheme { LobstersListItem(item = samplePosts(1).first(), postActions = TEST_POST_ACTIONS) }
|
||||
LobstersTheme {
|
||||
LobstersListItem(
|
||||
item = samplePosts(1).first(),
|
||||
isSaved = true,
|
||||
isRead = true,
|
||||
postActions = TEST_POST_ACTIONS,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,7 +87,14 @@ fun NetworkPosts(
|
||||
if (item != null) {
|
||||
val shouldShowPost = item.tags.none { tag -> filteredTags.contains(tag) }
|
||||
if (shouldShowPost) {
|
||||
LobstersListItem(item = item, postActions = postActions)
|
||||
val isSaved = postActions.isPostSaved(item)
|
||||
val isRead = postActions.isPostRead(item)
|
||||
LobstersListItem(
|
||||
item = item,
|
||||
isSaved = isSaved,
|
||||
isRead = isRead,
|
||||
postActions = postActions,
|
||||
)
|
||||
HorizontalDivider()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,12 +119,16 @@ class ClawViewModel(
|
||||
|
||||
var searchQuery by mutableStateOf("")
|
||||
|
||||
private var _readPosts = emptyList<String>()
|
||||
private var _savedPosts = emptyList<String>()
|
||||
private var _readPosts by mutableStateOf(emptySet<String>())
|
||||
private var _savedPosts by mutableStateOf(emptySet<String>())
|
||||
|
||||
init {
|
||||
viewModelScope.launch { savedPosts.collectLatest { _savedPosts = it.map(UIPost::shortId) } }
|
||||
viewModelScope.launch { readPostsRepository.readPosts.collectLatest { _readPosts = it } }
|
||||
viewModelScope.launch {
|
||||
savedPosts.collectLatest { _savedPosts = it.map(UIPost::shortId).toSet() }
|
||||
}
|
||||
viewModelScope.launch {
|
||||
readPostsRepository.readPosts.collectLatest { _readPosts = it.toSet() }
|
||||
}
|
||||
}
|
||||
|
||||
fun toggleSave(post: UIPost) {
|
||||
|
||||
@@ -18,9 +18,11 @@ import retrofit2.Retrofit
|
||||
|
||||
object SearchConverter : Converter<ResponseBody, List<LobstersPost>> {
|
||||
override fun convert(value: ResponseBody): List<LobstersPost> {
|
||||
val elements =
|
||||
Jsoup.parse(value.string(), LobstersApi.BASE_URL).select("div.story_liner.h-entry")
|
||||
return elements.map(::parsePost)
|
||||
return value.byteStream().use { stream ->
|
||||
Jsoup.parse(stream, "UTF-8", LobstersApi.BASE_URL)
|
||||
.select("div.story_liner.h-entry")
|
||||
.map(::parsePost)
|
||||
}
|
||||
}
|
||||
|
||||
private fun parsePost(elem: Element): LobstersPost {
|
||||
|
||||
+1
-2
@@ -50,9 +50,8 @@ class BaselineProfileBenchmark {
|
||||
compilationMode = compilationMode,
|
||||
startupMode = StartupMode.COLD,
|
||||
iterations = 10,
|
||||
setupBlock = { device.executeShellCommand("pm clear $PACKAGE_NAME") },
|
||||
) {
|
||||
device.executeShellCommand("pm clear $PACKAGE_NAME")
|
||||
|
||||
startActivityAndWait()
|
||||
|
||||
device.waitForIdle()
|
||||
|
||||
@@ -27,7 +27,7 @@ class KotlinCommonPlugin : Plugin<Project> {
|
||||
}
|
||||
}
|
||||
withType<Test>().configureEach {
|
||||
maxParallelForks = Runtime.getRuntime().availableProcessors() * 2
|
||||
maxParallelForks = (Runtime.getRuntime().availableProcessors() / 2).coerceAtLeast(1)
|
||||
testLogging { events(TestLogEvent.PASSED, TestLogEvent.SKIPPED, TestLogEvent.FAILED) }
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
||||
@@ -14,17 +14,30 @@ import kotlinx.coroutines.flow.asStateFlow
|
||||
|
||||
internal class CommentsHandler {
|
||||
|
||||
private val _listItems: MutableStateFlow<List<CommentNode>> = MutableStateFlow(emptyList())
|
||||
val listItems: StateFlow<List<CommentNode>> = _listItems.asStateFlow()
|
||||
private var rootNodes = emptyList<CommentNode>()
|
||||
private val _visibleNodes: MutableStateFlow<List<CommentNode>> = MutableStateFlow(emptyList())
|
||||
val listItems: StateFlow<List<CommentNode>> = _visibleNodes.asStateFlow()
|
||||
private var collapsedCommentIds = setOf<String>()
|
||||
|
||||
private fun updateVisibleNodes() {
|
||||
val flatList = mutableListOf<CommentNode>()
|
||||
fun traverse(node: CommentNode) {
|
||||
flatList.add(node)
|
||||
if (node.isExpanded) {
|
||||
node.children.forEach { traverse(it) }
|
||||
}
|
||||
}
|
||||
rootNodes.forEach { traverse(it) }
|
||||
_visibleNodes.value = flatList
|
||||
}
|
||||
|
||||
fun createListNode(
|
||||
comments: List<Comment>,
|
||||
commentState: PostComments?,
|
||||
isPostAuthor: (Comment) -> Boolean,
|
||||
) {
|
||||
val commentNodes = mutableListOf<CommentNode>()
|
||||
val isUnread = { id: String -> commentState?.commentIds?.contains(id) == false }
|
||||
val isUnread = { id: String -> commentState?.commentIds?.contains(id) != true }
|
||||
|
||||
for (i in comments.indices) {
|
||||
val comment = comments[i]
|
||||
@@ -53,7 +66,8 @@ internal class CommentsHandler {
|
||||
}
|
||||
}
|
||||
|
||||
_listItems.value = commentNodes
|
||||
rootNodes = commentNodes
|
||||
updateVisibleNodes()
|
||||
}
|
||||
|
||||
fun updateListNode(shortId: String, isExpanded: Boolean) {
|
||||
@@ -65,9 +79,9 @@ internal class CommentsHandler {
|
||||
return node.copy(children = updatedChildren)
|
||||
}
|
||||
|
||||
val listNode = _listItems.value.map { updateNode(it) }
|
||||
_listItems.value = listNode
|
||||
rootNodes = rootNodes.map { updateNode(it) }
|
||||
syncCollapsedState()
|
||||
updateVisibleNodes()
|
||||
}
|
||||
|
||||
private fun syncCollapsedState() {
|
||||
@@ -79,7 +93,7 @@ internal class CommentsHandler {
|
||||
}
|
||||
}
|
||||
}
|
||||
collapsedCommentIds = collectCollapsedIds(_listItems.value)
|
||||
collapsedCommentIds = collectCollapsedIds(rootNodes)
|
||||
}
|
||||
|
||||
fun updateUnreadStatus(commentState: PostComments?) {
|
||||
@@ -91,7 +105,7 @@ internal class CommentsHandler {
|
||||
return node.copy(isUnread = isUnread, children = updatedChildren)
|
||||
}
|
||||
|
||||
val listNode = _listItems.value.map { updateNode(it) }
|
||||
_listItems.value = listNode
|
||||
rootNodes = rootNodes.map { updateNode(it) }
|
||||
updateVisibleNodes()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,12 +8,6 @@ package dev.msfjarvis.claw.common.comments
|
||||
|
||||
import android.text.format.DateUtils
|
||||
import android.widget.Toast
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.expandVertically
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.shrinkVertically
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
@@ -66,8 +60,6 @@ import java.time.Instant
|
||||
import java.time.temporal.TemporalAccessor
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
private const val AnimationDuration = 100
|
||||
|
||||
@Composable
|
||||
internal fun CommentsPageInternal(
|
||||
details: UIPost,
|
||||
@@ -130,7 +122,7 @@ internal fun CommentsPageInternal(
|
||||
}
|
||||
|
||||
items(items = commentNodes, key = { node -> node.comment.shortId }) { node ->
|
||||
Node(node, openUserProfile, onToggleExpandedState)
|
||||
NodeBox(node, node.isExpanded, openUserProfile, onToggleExpandedState)
|
||||
}
|
||||
|
||||
item(key = "bottom_spacer") {
|
||||
@@ -151,38 +143,6 @@ internal fun CommentsPageInternal(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple tree view implementation by Anton Shilov who was smarter in 2020 than I am today
|
||||
* https://gist.github.com/antonshilov/ef8cd0a360a5cc0f823b2a4e85084720
|
||||
*/
|
||||
@Composable
|
||||
private fun Node(
|
||||
node: CommentNode,
|
||||
openUserProfile: (String) -> Unit,
|
||||
onToggleExpandedState: (String, Boolean) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
NodeBox(
|
||||
node = node,
|
||||
isExpanded = node.isExpanded,
|
||||
openUserProfile = openUserProfile,
|
||||
onToggleExpandedState = onToggleExpandedState,
|
||||
modifier = modifier,
|
||||
)
|
||||
|
||||
AnimatedVisibility(
|
||||
visible = node.isExpanded,
|
||||
enter = fadeIn(tween(AnimationDuration)) + expandVertically(tween(AnimationDuration)),
|
||||
exit = fadeOut(tween(AnimationDuration)) + shrinkVertically(tween(AnimationDuration)),
|
||||
) {
|
||||
Column(modifier = modifier, horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
node.children.forEach { model -> Node(model, openUserProfile, onToggleExpandedState) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun NodeBox(
|
||||
node: CommentNode,
|
||||
|
||||
@@ -40,8 +40,6 @@ import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.minimumInteractiveComponentSize
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -61,9 +59,13 @@ import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
@Composable
|
||||
fun LobstersCard(post: UIPost, postActions: PostActions, modifier: Modifier = Modifier) {
|
||||
val readState by remember(post.shortId) { mutableStateOf(postActions.isPostRead(post)) }
|
||||
var savedState by remember(post.shortId) { mutableStateOf(postActions.isPostSaved(post)) }
|
||||
fun LobstersCard(
|
||||
post: UIPost,
|
||||
isSaved: Boolean,
|
||||
isRead: Boolean,
|
||||
postActions: PostActions,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Box(
|
||||
modifier =
|
||||
modifier
|
||||
@@ -78,7 +80,7 @@ fun LobstersCard(post: UIPost, postActions: PostActions, modifier: Modifier = Mo
|
||||
) {
|
||||
PostDetails(
|
||||
post = post,
|
||||
isRead = { readState },
|
||||
isRead = { isRead },
|
||||
singleLineTitle = true,
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
@@ -87,15 +89,9 @@ fun LobstersCard(post: UIPost, postActions: PostActions, modifier: Modifier = Mo
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
SaveButton(
|
||||
isSaved = savedState,
|
||||
isSaved = isSaved,
|
||||
modifier =
|
||||
Modifier.clickable(
|
||||
role = Role.Button,
|
||||
onClick = {
|
||||
postActions.toggleSave(post)
|
||||
savedState = !savedState
|
||||
},
|
||||
),
|
||||
Modifier.clickable(role = Role.Button, onClick = { postActions.toggleSave(post) }),
|
||||
)
|
||||
HorizontalDivider(modifier = Modifier.width(48.dp))
|
||||
CommentsButton(
|
||||
@@ -295,5 +291,7 @@ val TEST_POST =
|
||||
@ThemePreviews
|
||||
@Composable
|
||||
private fun LobstersCardPreview() {
|
||||
LobstersTheme { LobstersCard(post = TEST_POST, postActions = TEST_POST_ACTIONS) }
|
||||
LobstersTheme {
|
||||
LobstersCard(post = TEST_POST, isSaved = true, isRead = true, postActions = TEST_POST_ACTIONS)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
package dev.msfjarvis.claw.common.theme
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.ContextWrapper
|
||||
import android.os.Build
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
@@ -103,7 +105,7 @@ fun LobstersTheme(
|
||||
val view = LocalView.current
|
||||
if (!view.isInEditMode) {
|
||||
SideEffect {
|
||||
val window = (view.context as Activity).window
|
||||
val window = view.context.findActivity()?.window ?: return@SideEffect
|
||||
WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = !darkTheme
|
||||
}
|
||||
}
|
||||
@@ -111,3 +113,10 @@ fun LobstersTheme(
|
||||
MaterialTheme(colorScheme = colorScheme, typography = AppTypography) { content() }
|
||||
}
|
||||
}
|
||||
|
||||
internal tailrec fun Context.findActivity(): Activity? =
|
||||
when (this) {
|
||||
is Activity -> this
|
||||
is ContextWrapper -> baseContext.findActivity()
|
||||
else -> null
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user