Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ public actual data class UiDateTime internal constructor(

internal actual fun Instant.toUi(): UiDateTime = UiDateTime(this)

internal actual operator fun UiDateTime.compareTo(other: UiDateTime): Int = value.compareTo(other.value)

public sealed interface LocalizedShortTime {
public data class String(
val value: kotlin.String,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
package dev.dimension.flare.ui.render

import kotlinx.datetime.toKotlinInstant
import kotlinx.datetime.toNSDate
import platform.Foundation.NSDate
import kotlin.time.Instant

public actual typealias UiDateTime = NSDate

internal actual fun Instant.toUi(): UiDateTime = toNSDate()

internal actual operator fun UiDateTime.compareTo(other: UiDateTime): Int {
val instant = this.toKotlinInstant()
val otherInstant = other.toKotlinInstant()
return instant.compareTo(otherInstant)
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,19 @@ package dev.dimension.flare.data.datasource.bluesky
import androidx.paging.ExperimentalPagingApi
import androidx.paging.LoadType
import androidx.paging.PagingState
import app.bsky.feed.FeedViewPost
import app.bsky.feed.GetPostThreadQueryParams
import app.bsky.feed.GetPostThreadResponseThreadUnion
import app.bsky.feed.GetPostsQueryParams
import app.bsky.feed.ReplyRef
import app.bsky.feed.ReplyRefParentUnion
import app.bsky.feed.ReplyRefRootUnion
import app.bsky.feed.ThreadViewPost
import app.bsky.feed.ThreadViewPostParentUnion
import app.bsky.feed.ThreadViewPostReplieUnion
import dev.dimension.flare.common.BaseTimelineRemoteMediator
import dev.dimension.flare.data.database.cache.CacheDatabase
import dev.dimension.flare.data.database.cache.mapper.toDb
import dev.dimension.flare.data.database.cache.mapper.toDbPagingTimeline
import dev.dimension.flare.data.database.cache.model.DbPagingTimeline
import dev.dimension.flare.data.database.cache.model.DbPagingTimelineWithStatus
import dev.dimension.flare.data.network.bluesky.BlueskyService
Expand Down Expand Up @@ -78,7 +82,7 @@ internal class StatusDetailRemoteMediator(
).requireResponse()
.posts
.firstOrNull()
listOfNotNull(current)
listOfNotNull(current).map(::FeedViewPost)
} else {
val context =
service
Expand All @@ -102,11 +106,49 @@ internal class StatusDetailRemoteMediator(
val replies =
thread.value.replies.mapNotNull {
when (it) {
is ThreadViewPostReplieUnion.ThreadViewPost -> it.value.post
is ThreadViewPostReplieUnion.ThreadViewPost -> {
if (it.value.replies.any()) {
val last =
it.value.replies.last().let {
when (it) {
is ThreadViewPostReplieUnion.ThreadViewPost -> it.value.post
else -> null
}
}
if (last != null) {
val parents =
listOfNotNull(it.value.post) +
it.value.replies.toList().dropLast(1).mapNotNull {
Copy link

Copilot AI Aug 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Converting immutable collection to list with toList() and then calling dropLast(1) creates unnecessary intermediate collections. Consider using take(it.value.replies.size - 1) or direct indexing for better performance.

Suggested change
it.value.replies.toList().dropLast(1).mapNotNull {
it.value.replies.take(it.value.replies.size - 1).mapNotNull {

Copilot uses AI. Check for mistakes.
when (it) {
is ThreadViewPostReplieUnion.ThreadViewPost -> it.value.post
else -> null
}
}
val currentRef =
ReplyRef(
root = ReplyRefRootUnion.PostView(parents.last()),
parent = ReplyRefParentUnion.PostView(parents.last()),
)

FeedViewPost(
post = last,
reply = currentRef,
)
} else {
FeedViewPost(
it.value.post,
)
}
} else {
FeedViewPost(
it.value.post,
)
}
}
else -> null
}
}
parents.map { it.post }.reversed() + thread.value.post + replies
parents.map { FeedViewPost(it.post) }.reversed() + FeedViewPost(thread.value.post) + replies
}

else -> emptyList()
Expand All @@ -115,7 +157,7 @@ internal class StatusDetailRemoteMediator(
return Result(
endOfPaginationReached = true,
data =
result.toDb(
result.toDbPagingTimeline(
accountKey,
pagingKey,
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,15 +132,13 @@ public abstract class TimelinePresenter :
}?.dataSource
data
.render(dataSource, useDbKeyInItemKey)
.let {
transform(it)
}
.let { transform(it) }
}
}
}
}

protected open fun transform(data: UiTimeline): UiTimeline = data
protected open suspend fun transform(data: UiTimeline): UiTimeline = data

private fun networkPager(
pagingSource: BaseTimelinePagingSourceFactory<*>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,22 @@ import dev.dimension.flare.data.repository.accountServiceFlow
import dev.dimension.flare.model.AccountType
import dev.dimension.flare.model.MicroBlogKey
import dev.dimension.flare.ui.model.UiTimeline
import dev.dimension.flare.ui.model.takeSuccess
import dev.dimension.flare.ui.model.toUi
import dev.dimension.flare.ui.presenter.PresenterBase
import dev.dimension.flare.ui.presenter.home.TimelinePresenter
import dev.dimension.flare.ui.presenter.home.TimelineState
import dev.dimension.flare.ui.render.compareTo
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toPersistentList
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapNotNull
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject

Expand All @@ -23,35 +33,76 @@ public class StatusContextPresenter(
) : PresenterBase<TimelineState>(),
KoinComponent {
private val accountRepository: AccountRepository by inject()

@OptIn(ExperimentalCoroutinesApi::class)
private val timelinePresenter by lazy {
object : TimelinePresenter() {
private val currentStatusCreatedAtFlow by lazy {
accountServiceFlow(
accountType = accountType,
repository = accountRepository,
).flatMapLatest { service ->
service.status(statusKey).toUi()
}.mapNotNull { it.takeSuccess() }
.mapNotNull {
if (it.content is UiTimeline.ItemContent.Status) {
it.content.createdAt
} else {
null
}
}.distinctUntilChanged()
}

override val loader: Flow<BaseTimelineLoader> by lazy {
accountServiceFlow(
accountType = accountType,
repository = accountRepository,
).map { service ->
service.context(statusKey)
}.combine(currentStatusCreatedAtFlow) { loader, _ ->
loader
}
Copy link

Copilot AI Aug 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The underscore parameter _ suggests the currentStatusCreatedAtFlow value is being ignored in the combine operation. Consider removing this combine if the flow value isn't needed, or use the value if it's intended for triggering loader updates.

Suggested change
}
}.sample(currentStatusCreatedAtFlow)

Copilot uses AI. Check for mistakes.
}

override fun transform(data: UiTimeline): UiTimeline =
data.copy(
override suspend fun transform(data: UiTimeline): UiTimeline {
val currentCreatedAt = currentStatusCreatedAtFlow.firstOrNull()
Copy link

Copilot AI Aug 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Calling firstOrNull() on the flow in every transform operation could be inefficient. Consider passing the current timestamp as a parameter to the transform method or caching it at a higher level.

Copilot uses AI. Check for mistakes.
return data.copy(
content =
when (val content = data.content) {
is UiTimeline.ItemContent.Status ->
content.copy(
parents = persistentListOf(),
)
is UiTimeline.ItemContent.Status -> {
if (currentCreatedAt != null && content.createdAt <= currentCreatedAt) {
content.copy(
parents = persistentListOf(),
)
} else if (currentCreatedAt != null) {
content.copy(
parents =
content.parents
.filter {
it.createdAt > currentCreatedAt
}.toPersistentList(),
)
} else {
content
}
}

else -> content
},
)
}
}
}

@Composable
override fun body(): TimelineState {
val listState = timelinePresenter.body()
remember { LogStatusHistoryPresenter(accountType = accountType, statusKey = statusKey) }.body()
remember {
LogStatusHistoryPresenter(
accountType = accountType,
statusKey = statusKey,
)
}.body()
return listState
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ import kotlin.time.Instant
public expect class UiDateTime

internal expect fun Instant.toUi(): UiDateTime

internal expect operator fun UiDateTime.compareTo(other: UiDateTime): Int