No more blinking in Feeds

pull/55/head
Vitor Pamplona 2023-01-27 19:28:59 -03:00
rodzic ae82c690ea
commit 1e3654396b
13 zmienionych plików z 176 dodań i 96 usunięć

Wyświetl plik

@ -1,5 +1,6 @@
package com.vitorpamplona.amethyst.ui.screen
import androidx.compose.runtime.MutableState
import com.vitorpamplona.amethyst.model.Note
abstract class Card() {
@ -37,7 +38,7 @@ class BoostSetCard(val note: Note, val boostEvents: List<Note>): Card() {
sealed class CardFeedState {
object Loading: CardFeedState()
class Loaded(val feed: List<Card>): CardFeedState()
class Loaded(val feed: MutableState<List<Card>>): CardFeedState()
object Empty: CardFeedState()
class FeedError(val errorMessage: String): CardFeedState()
}

Wyświetl plik

@ -4,6 +4,7 @@ import androidx.compose.animation.Crossfade
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.runtime.Composable
@ -30,8 +31,6 @@ fun CardFeedView(viewModel: CardFeedViewModel, accountViewModel: AccountViewMode
var isRefreshing by remember { mutableStateOf(false) }
val swipeRefreshState = rememberSwipeRefreshState(isRefreshing)
val listState = rememberLazyListState()
LaunchedEffect(isRefreshing) {
if (isRefreshing) {
viewModel.refresh()
@ -59,21 +58,12 @@ fun CardFeedView(viewModel: CardFeedViewModel, accountViewModel: AccountViewMode
}
}
is CardFeedState.Loaded -> {
LazyColumn(
contentPadding = PaddingValues(
top = 10.dp,
bottom = 10.dp
),
state = listState
) {
itemsIndexed(state.feed, key = { _, item -> item.id() }) { index, item ->
when (item) {
is NoteCard -> NoteCompose(item.note, isInnerNote = false, accountViewModel = accountViewModel, navController = navController, routeForLastRead = routeForLastRead)
is LikeSetCard -> LikeSetCompose(item, isInnerNote = false, accountViewModel = accountViewModel, navController = navController, routeForLastRead = routeForLastRead)
is BoostSetCard -> BoostSetCompose(item, isInnerNote = false, accountViewModel = accountViewModel, navController = navController, routeForLastRead = routeForLastRead)
}
}
}
FeedLoaded(
state,
accountViewModel,
navController,
routeForLastRead
)
}
CardFeedState.Loading -> {
LoadingFeed()
@ -83,3 +73,47 @@ fun CardFeedView(viewModel: CardFeedViewModel, accountViewModel: AccountViewMode
}
}
}
@Composable
private fun FeedLoaded(
state: CardFeedState.Loaded,
accountViewModel: AccountViewModel,
navController: NavController,
routeForLastRead: String
) {
val listState = rememberLazyListState()
LazyColumn(
contentPadding = PaddingValues(
top = 10.dp,
bottom = 10.dp
),
state = listState
) {
itemsIndexed(state.feed.value, key = { _, item -> item.id() }) { index, item ->
when (item) {
is NoteCard -> NoteCompose(
item.note,
isInnerNote = false,
accountViewModel = accountViewModel,
navController = navController,
routeForLastRead = routeForLastRead
)
is LikeSetCard -> LikeSetCompose(
item,
isInnerNote = false,
accountViewModel = accountViewModel,
navController = navController,
routeForLastRead = routeForLastRead
)
is BoostSetCard -> BoostSetCompose(
item,
isInnerNote = false,
accountViewModel = accountViewModel,
navController = navController,
routeForLastRead = routeForLastRead
)
}
}
}
}

Wyświetl plik

@ -2,6 +2,7 @@ package com.vitorpamplona.amethyst.ui.screen
import android.os.Handler
import android.os.Looper
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.vitorpamplona.amethyst.model.LocalCache
@ -43,7 +44,7 @@ class CardFeedViewModel(val dataSource: NostrDataSource<Note>): ViewModel() {
val newCards = convertToCard(notes.minus(lastNotesCopy))
if (newCards.isNotEmpty()) {
lastNotes = notes
updateFeed((oldNotesState.feed + newCards).sortedBy { it.createdAt() }.reversed())
updateFeed((oldNotesState.feed.value + newCards).sortedBy { it.createdAt() }.reversed())
}
} else {
val cards = convertToCard(notes)
@ -83,14 +84,20 @@ class CardFeedViewModel(val dataSource: NostrDataSource<Note>): ViewModel() {
fun updateFeed(notes: List<Card>) {
val scope = CoroutineScope(Job() + Dispatchers.Main)
scope.launch {
val currentState = feedContent.value
if (notes.isEmpty()) {
_feedContent.update { CardFeedState.Empty }
} else if (currentState is CardFeedState.Loaded) {
// updates the current list
currentState.feed.value = notes
} else {
_feedContent.update { CardFeedState.Loaded(notes) }
_feedContent.update { CardFeedState.Loaded(mutableStateOf(notes)) }
}
}
}
var handlerWaiting = false
fun invalidateData() {
synchronized(handlerWaiting) {

Wyświetl plik

@ -25,7 +25,6 @@ fun ChatroomFeedView(viewModel: FeedViewModel, accountViewModel: AccountViewMode
val feedState by viewModel.feedContent.collectAsState()
var isRefreshing by remember { mutableStateOf(false) }
val swipeRefreshState = rememberSwipeRefreshState(isRefreshing)
val listState = rememberLazyListState()
@ -36,43 +35,36 @@ fun ChatroomFeedView(viewModel: FeedViewModel, accountViewModel: AccountViewMode
}
}
SwipeRefresh(
state = swipeRefreshState,
onRefresh = {
isRefreshing = true
},
) {
Column() {
Crossfade(targetState = feedState) { state ->
when (state) {
is FeedState.Empty -> {
FeedEmpty {
isRefreshing = true
Column() {
Crossfade(targetState = feedState) { state ->
when (state) {
is FeedState.Empty -> {
FeedEmpty {
isRefreshing = true
}
}
is FeedState.FeedError -> {
FeedError(state.errorMessage) {
isRefreshing = true
}
}
is FeedState.Loaded -> {
LazyColumn(
contentPadding = PaddingValues(
top = 10.dp,
bottom = 10.dp
),
reverseLayout = true,
state = listState
) {
var previousDate: String = ""
itemsIndexed(state.feed.value, key = { index, item -> if (index == 0) index else item.idHex }) { index, item ->
ChatroomMessageCompose(item, routeForLastRead, accountViewModel = accountViewModel, navController = navController)
}
}
is FeedState.FeedError -> {
FeedError(state.errorMessage) {
isRefreshing = true
}
}
is FeedState.Loaded -> {
LazyColumn(
contentPadding = PaddingValues(
top = 10.dp,
bottom = 10.dp
),
reverseLayout = true,
state = listState
) {
var previousDate: String = ""
itemsIndexed(state.feed, key = { index, item -> if (index == 0) index else item.idHex }) { index, item ->
ChatroomMessageCompose(item, routeForLastRead, accountViewModel = accountViewModel, navController = navController)
}
}
}
FeedState.Loading -> {
LoadingFeed()
}
}
FeedState.Loading -> {
LoadingFeed()
}
}
}

Wyświetl plik

@ -68,7 +68,7 @@ fun ChatroomListFeedView(viewModel: FeedViewModel, accountViewModel: AccountView
),
state = listState
) {
itemsIndexed(state.feed, key = { index, item -> item.idHex }) { index, item ->
itemsIndexed(state.feed.value, key = { index, item -> item.idHex }) { index, item ->
ChatroomCompose(item, accountViewModel = accountViewModel, navController = navController)
}
}

Wyświetl plik

@ -1,11 +1,12 @@
package com.vitorpamplona.amethyst.ui.screen
import androidx.compose.runtime.MutableState
import com.vitorpamplona.amethyst.model.Note
sealed class FeedState {
object Loading : FeedState()
class Loaded(val feed: List<Note>) : FeedState()
class Loaded(val feed: MutableState<List<Note>>) : FeedState()
object Empty : FeedState()
class FeedError(val errorMessage: String) : FeedState()
}

Wyświetl plik

@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.Button
@ -40,8 +41,6 @@ fun FeedView(viewModel: FeedViewModel, accountViewModel: AccountViewModel, navCo
var isRefreshing by remember { mutableStateOf(false) }
val swipeRefreshState = rememberSwipeRefreshState(isRefreshing)
val listState = rememberLazyListState()
LaunchedEffect(isRefreshing) {
if (isRefreshing) {
viewModel.hardRefresh()
@ -49,12 +48,15 @@ fun FeedView(viewModel: FeedViewModel, accountViewModel: AccountViewModel, navCo
}
}
println("FeedView Refresh ${feedState}")
SwipeRefresh(
state = swipeRefreshState,
onRefresh = {
isRefreshing = true
},
) {
Column() {
Crossfade(targetState = feedState) { state ->
when (state) {
@ -69,22 +71,12 @@ fun FeedView(viewModel: FeedViewModel, accountViewModel: AccountViewModel, navCo
}
}
is FeedState.Loaded -> {
LazyColumn(
contentPadding = PaddingValues(
top = 10.dp,
bottom = 10.dp
),
state = listState
) {
itemsIndexed(state.feed, key = { _, item -> item.idHex }) { index, item ->
NoteCompose(item,
isInnerNote = false,
routeForLastRead = routeForLastRead,
accountViewModel = accountViewModel,
navController = navController
)
}
}
FeedLoaded(
state,
routeForLastRead,
accountViewModel,
navController
)
}
FeedState.Loading -> {
LoadingFeed()
@ -95,6 +87,34 @@ fun FeedView(viewModel: FeedViewModel, accountViewModel: AccountViewModel, navCo
}
}
@Composable
private fun FeedLoaded(
state: FeedState.Loaded,
routeForLastRead: String?,
accountViewModel: AccountViewModel,
navController: NavController
) {
val listState = rememberLazyListState()
LazyColumn(
contentPadding = PaddingValues(
top = 10.dp,
bottom = 10.dp
),
state = listState
) {
itemsIndexed(state.feed.value, key = { _, item -> item.idHex }) { index, item ->
NoteCompose(
item,
isInnerNote = false,
routeForLastRead = routeForLastRead,
accountViewModel = accountViewModel,
navController = navController
)
}
}
}
@Composable
fun LoadingFeed() {
Column(

Wyświetl plik

@ -1,6 +1,7 @@
package com.vitorpamplona.amethyst.ui.screen
import android.util.Log
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
@ -97,10 +98,15 @@ abstract class FeedViewModel(val dataSource: NostrDataSource<Note>): ViewModel()
}
fun updateFeed(notes: List<Note>) {
val currentState = feedContent.value
if (notes.isEmpty()) {
_feedContent.update { FeedState.Empty }
} else if (currentState is FeedState.Loaded) {
// updates the current list
currentState.feed.value = notes
} else {
_feedContent.update { FeedState.Loaded(notes) }
_feedContent.update { FeedState.Loaded(mutableStateOf(notes)) }
}
}

Wyświetl plik

@ -88,9 +88,9 @@ fun ThreadFeedView(noteId: String, viewModel: FeedViewModel, accountViewModel: A
listState.animateScrollToItem(noteIdPositionInThread, 0)
}
val notePosition = state.feed.filter { it.idHex == noteId}.firstOrNull()
val notePosition = state.feed.value.filter { it.idHex == noteId}.firstOrNull()
if (notePosition != null) {
noteIdPositionInThread = state.feed.indexOf(notePosition)
noteIdPositionInThread = state.feed.value.indexOf(notePosition)
}
LazyColumn(
@ -100,7 +100,7 @@ fun ThreadFeedView(noteId: String, viewModel: FeedViewModel, accountViewModel: A
),
state = listState
) {
itemsIndexed(state.feed, key = { _, item -> item.idHex }) { index, item ->
itemsIndexed(state.feed.value, key = { _, item -> item.idHex }) { index, item ->
if (index == 0)
NoteMaster(item, accountViewModel = accountViewModel, navController = navController)
else {

Wyświetl plik

@ -1,10 +1,11 @@
package com.vitorpamplona.amethyst.ui.screen
import androidx.compose.runtime.MutableState
import com.vitorpamplona.amethyst.model.User
sealed class UserFeedState {
object Loading : UserFeedState()
class Loaded(val feed: List<User>) : UserFeedState()
class Loaded(val feed: MutableState<List<User>>) : UserFeedState()
object Empty : UserFeedState()
class FeedError(val errorMessage: String) : UserFeedState()
}

Wyświetl plik

@ -4,6 +4,7 @@ import androidx.compose.animation.Crossfade
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.runtime.Composable
@ -28,8 +29,6 @@ fun UserFeedView(viewModel: UserFeedViewModel, accountViewModel: AccountViewMode
var isRefreshing by remember { mutableStateOf(false) }
val swipeRefreshState = rememberSwipeRefreshState(isRefreshing)
val listState = rememberLazyListState()
LaunchedEffect(isRefreshing) {
if (isRefreshing) {
viewModel.refresh()
@ -57,17 +56,7 @@ fun UserFeedView(viewModel: UserFeedViewModel, accountViewModel: AccountViewMode
}
}
is UserFeedState.Loaded -> {
LazyColumn(
contentPadding = PaddingValues(
top = 10.dp,
bottom = 10.dp
),
state = listState
) {
itemsIndexed(state.feed, key = { _, item -> item.pubkeyHex }) { index, item ->
UserCompose(item, accountViewModel = accountViewModel, navController = navController)
}
}
FeedLoaded(state, accountViewModel, navController)
}
UserFeedState.Loading -> {
LoadingFeed()
@ -77,3 +66,24 @@ fun UserFeedView(viewModel: UserFeedViewModel, accountViewModel: AccountViewMode
}
}
}
@Composable
private fun FeedLoaded(
state: UserFeedState.Loaded,
accountViewModel: AccountViewModel,
navController: NavController
) {
val listState = rememberLazyListState()
LazyColumn(
contentPadding = PaddingValues(
top = 10.dp,
bottom = 10.dp
),
state = listState
) {
itemsIndexed(state.feed.value, key = { _, item -> item.pubkeyHex }) { index, item ->
UserCompose(item, accountViewModel = accountViewModel, navController = navController)
}
}
}

Wyświetl plik

@ -2,10 +2,12 @@ package com.vitorpamplona.amethyst.ui.screen
import android.os.Handler
import android.os.Looper
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.LocalCacheState
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.service.NostrDataSource
import com.vitorpamplona.amethyst.service.NostrHiddenAccountsDataSource
@ -60,10 +62,15 @@ open class UserFeedViewModel(val dataSource: NostrDataSource<User>): ViewModel()
fun updateFeed(notes: List<User>) {
val scope = CoroutineScope(Job() + Dispatchers.Main)
scope.launch {
val currentState = feedContent.value
if (notes.isEmpty()) {
_feedContent.update { UserFeedState.Empty }
} else if (currentState is UserFeedState.Loaded) {
// updates the current list
currentState.feed.value = notes
} else {
_feedContent.update { UserFeedState.Loaded(notes) }
_feedContent.update { UserFeedState.Loaded(mutableStateOf(notes)) }
}
}
}

Wyświetl plik

@ -50,6 +50,7 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel
@ -425,7 +426,7 @@ fun FollowButton(onClick: () -> Unit) {
backgroundColor = MaterialTheme.colors.primary
)
) {
Text(text = "Follow", color = Color.White)
Text(text = "Follow", color = Color.White, textAlign = TextAlign.Center)
}
}