Skip to content

Commit

Permalink
- Adds a cache for reply levels when viewing threads.
Browse files Browse the repository at this point in the history
- Moves the lazy list state to the view model to saving the scrolling position when navigating between multiple threads.
  • Loading branch information
vitorpamplona committed Nov 1, 2024
1 parent a825f57 commit 91748d5
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import com.vitorpamplona.amethyst.model.AddressableNote
import com.vitorpamplona.amethyst.model.Channel
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.ThreadLevelCalculator
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.ui.dal.BookmarkPrivateFeedFilter
import com.vitorpamplona.amethyst.ui.dal.BookmarkPublicFeedFilter
Expand All @@ -53,10 +54,21 @@ import com.vitorpamplona.amethyst.ui.dal.UserProfileGalleryFeedFilter
import com.vitorpamplona.amethyst.ui.dal.UserProfileNewThreadFeedFilter
import com.vitorpamplona.amethyst.ui.dal.UserProfileReportsFeedFilter
import com.vitorpamplona.amethyst.ui.feeds.FeedContentState
import com.vitorpamplona.amethyst.ui.feeds.FeedState
import com.vitorpamplona.amethyst.ui.feeds.InvalidatableContent
import com.vitorpamplona.quartz.events.ChatroomKey
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.transformLatest
import kotlinx.coroutines.launch

class NostrChannelFeedViewModel(
Expand Down Expand Up @@ -86,7 +98,7 @@ class NostrChatroomFeedViewModel(
class NostrThreadFeedViewModel(
account: Account,
noteId: String,
) : FeedViewModel(ThreadFeedFilter(account, noteId)) {
) : LevelFeedViewModel(ThreadFeedFilter(account, noteId)) {
class Factory(
val account: Account,
val noteId: String,
Expand Down Expand Up @@ -257,6 +269,44 @@ class NostrUserAppRecommendationsFeedViewModel(
}
}

abstract class LevelFeedViewModel(
localFilter: FeedFilter<Note>,
) : FeedViewModel(localFilter) {
var llState: LazyListState by mutableStateOf(LazyListState(0, 0))

// val cachedLevels = mutableMapOf<Note, MutableStateFlow<Int>>()

@OptIn(ExperimentalCoroutinesApi::class)
val levelCacheFlow: StateFlow<Map<Note, Int>> =
feedState.feedContent
.transformLatest { feed ->
emitAll(
if (feed is FeedState.Loaded) {
feed.feed.map {
val cache = mutableMapOf<Note, Int>()
it.list.forEach {
ThreadLevelCalculator.replyLevel(it, cache)
}
cache
}
} else {
MutableStateFlow(mapOf())
},
)
}.flowOn(Dispatchers.Default)
.stateIn(
viewModelScope,
SharingStarted.WhileSubscribed(5000),
mapOf(),
)

fun levelFlowForItem(note: Note) =
levelCacheFlow
.map {
it[note] ?: 0
}.distinctUntilChanged()
}

@Stable
abstract class FeedViewModel(
localFilter: FeedFilter<Note>,
Expand All @@ -272,8 +322,6 @@ abstract class FeedViewModel(

override fun invalidateData(ignoreIfDoing: Boolean) = feedState.invalidateData(ignoreIfDoing)

var llState: LazyListState by mutableStateOf(LazyListState(0, 0))

private var collectorJob: Job? = null

init {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
Expand Down Expand Up @@ -74,7 +75,6 @@ import coil3.compose.AsyncImage
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.ThreadLevelCalculator
import com.vitorpamplona.amethyst.ui.components.GenericLoadable
import com.vitorpamplona.amethyst.ui.components.InlineCarrousel
import com.vitorpamplona.amethyst.ui.components.LoadNote
Expand Down Expand Up @@ -141,7 +141,7 @@ import com.vitorpamplona.amethyst.ui.note.types.RenderTextModificationEvent
import com.vitorpamplona.amethyst.ui.note.types.RenderTorrent
import com.vitorpamplona.amethyst.ui.note.types.RenderTorrentComment
import com.vitorpamplona.amethyst.ui.note.types.VideoDisplay
import com.vitorpamplona.amethyst.ui.screen.FeedViewModel
import com.vitorpamplona.amethyst.ui.screen.LevelFeedViewModel
import com.vitorpamplona.amethyst.ui.screen.RenderFeedState
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chatrooms.ChannelHeader
Expand Down Expand Up @@ -198,14 +198,15 @@ import com.vitorpamplona.quartz.events.VideoEvent
import com.vitorpamplona.quartz.events.WikiNoteEvent
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext

@Composable
fun ThreadFeedView(
noteId: String,
viewModel: FeedViewModel,
viewModel: LevelFeedViewModel,
accountViewModel: AccountViewModel,
nav: INav,
) {
Expand All @@ -217,7 +218,7 @@ fun ThreadFeedView(
nav = nav,
routeForLastRead = null,
onLoaded = {
RenderThreadFeed(noteId, it, viewModel.llState, accountViewModel, nav)
RenderThreadFeed(noteId, it, viewModel.llState, viewModel::levelFlowForItem, accountViewModel, nav)
},
)
}
Expand All @@ -228,6 +229,7 @@ fun RenderThreadFeed(
noteId: String,
loaded: FeedState.Loaded,
listState: LazyListState,
createLevelFlow: (Note) -> Flow<Int>,
accountViewModel: AccountViewModel,
nav: INav,
) {
Expand Down Expand Up @@ -266,20 +268,27 @@ fun RenderThreadFeed(
state = listState,
) {
itemsIndexed(items.list, key = { _, item -> item.idHex }) { index, item ->
val level = createLevelFlow(item).collectAsStateWithLifecycle(0)

val modifier =
Modifier
.drawReplyLevel(
note = item,
level = level,
color = MaterialTheme.colorScheme.placeholderText,
selected =
if (item.idHex == noteId) {
MaterialTheme.colorScheme.lessImportantLink
} else {
MaterialTheme.colorScheme.placeholderText
},
)

if (index == 0) {
ProvideTextStyle(TextStyle(fontSize = 18.sp, lineHeight = 1.20.em)) {
NoteMaster(
item,
modifier =
Modifier.drawReplyLevel(
ThreadLevelCalculator.replyLevel(item),
MaterialTheme.colorScheme.placeholderText,
if (item.idHex == noteId) {
MaterialTheme.colorScheme.lessImportantLink
} else {
MaterialTheme.colorScheme.placeholderText
},
),
baseNote = item,
modifier = modifier,
accountViewModel = accountViewModel,
nav = nav,
)
Expand All @@ -292,17 +301,8 @@ fun RenderThreadFeed(
}

NoteCompose(
item,
modifier =
Modifier.drawReplyLevel(
ThreadLevelCalculator.replyLevel(item),
MaterialTheme.colorScheme.placeholderText,
if (item.idHex == noteId) {
MaterialTheme.colorScheme.lessImportantLink
} else {
MaterialTheme.colorScheme.placeholderText
},
),
baseNote = item,
modifier = modifier,
isBoostedNote = false,
unPackReply = false,
quotesLeft = 3,
Expand All @@ -321,7 +321,8 @@ fun RenderThreadFeed(

// Creates a Zebra pattern where each bar is a reply level.
fun Modifier.drawReplyLevel(
level: Int,
note: Note,
level: State<Int>,
color: Color,
selected: Color,
): Modifier =
Expand All @@ -335,17 +336,17 @@ fun Modifier.drawReplyLevel(
val strokeWidth = strokeWidthDp.dp.toPx()
val levelWidth = levelWidthDp.dp.toPx()

repeat(level) {
repeat(level.value) {
this.drawLine(
if (it == level - 1) selected else color,
if (it == level.value - 1) selected else color,
Offset(padding + it * levelWidth, 0f),
Offset(padding + it * levelWidth, size.height),
strokeWidth = strokeWidth,
)
}

return@drawBehind
}.padding(start = (2 + (level * 3)).dp)
}.padding(start = (2 + (level.value * 3)).dp)

@OptIn(ExperimentalFoundationApi::class)
@Composable
Expand Down

0 comments on commit 91748d5

Please sign in to comment.