diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt index a2fdd3265..3e42a850f 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt @@ -13,6 +13,7 @@ import java.util.Collections import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job +import kotlinx.coroutines.delay import kotlinx.coroutines.launch import nostr.postr.events.Event @@ -99,17 +100,18 @@ class Note(val idHex: String) { val live: NoteLiveData = NoteLiveData(this) // Refreshes observers in batches. - val filterHandler = Handler(Looper.getMainLooper()) + val scope = CoroutineScope(Job() + Dispatchers.Main) var handlerWaiting = false @Synchronized fun invalidateData() { if (handlerWaiting) return handlerWaiting = true - filterHandler.postDelayed({ + scope.launch { + delay(100) live.refresh() handlerWaiting = false - }, 100) + } } } @@ -120,18 +122,12 @@ class NoteLiveData(val note: Note): LiveData(NoteState(note)) { override fun onActive() { super.onActive() - val scope = CoroutineScope(Job() + Dispatchers.Main) - scope.launch { - NostrSingleEventDataSource.add(note.idHex) - } + NostrSingleEventDataSource.add(note.idHex) } override fun onInactive() { super.onInactive() - val scope = CoroutineScope(Job() + Dispatchers.Main) - scope.launch { - NostrSingleEventDataSource.remove(note.idHex) - } + NostrSingleEventDataSource.remove(note.idHex) } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/UrlCachedPreviewer.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/UrlCachedPreviewer.kt index fe529c746..86adec1e6 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/UrlCachedPreviewer.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/UrlCachedPreviewer.kt @@ -22,7 +22,7 @@ object UrlCachedPreviewer { return } - val scope = CoroutineScope(Job() + Dispatchers.Main) + val scope = CoroutineScope(Job() + Dispatchers.IO) scope.launch { BahaUrlPreview(url, object : IUrlPreviewCallback { override fun onComplete(urlInfo: UrlInfoItem) { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/User.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/User.kt index 60f557711..4c67fd08d 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/User.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/User.kt @@ -11,6 +11,7 @@ import java.util.concurrent.ConcurrentHashMap import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job +import kotlinx.coroutines.delay import kotlinx.coroutines.launch import nostr.postr.events.ContactListEvent @@ -112,18 +113,18 @@ class User(val pubkey: ByteArray) { val live: UserLiveData = UserLiveData(this) // Refreshes observers in batches. - val filterHandler = Handler(Looper.getMainLooper()) + val scope = CoroutineScope(Job() + Dispatchers.Main) var handlerWaiting = false @Synchronized fun invalidateData() { if (handlerWaiting) return handlerWaiting = true - filterHandler.postDelayed({ - println("User Refresh") + scope.launch { + delay(100) live.refresh() handlerWaiting = false - }, 100) + } } } @@ -153,24 +154,18 @@ class UserMetadata { } class UserLiveData(val user: User): LiveData(UserState(user)) { - val scope = CoroutineScope(Job() + Dispatchers.Main) - fun refresh() { postValue(UserState(user)) } override fun onActive() { super.onActive() - scope.launch { - NostrSingleUserDataSource.add(user.pubkeyHex) - } + NostrSingleUserDataSource.add(user.pubkeyHex) } override fun onInactive() { super.onInactive() - scope.launch { - NostrSingleUserDataSource.remove(user.pubkeyHex) - } + NostrSingleUserDataSource.remove(user.pubkeyHex) } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrAccountDataSource.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrAccountDataSource.kt index 2b6a4736d..39deb6646 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrAccountDataSource.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrAccountDataSource.kt @@ -41,7 +41,7 @@ object NostrAccountDataSource: NostrDataSource("AccountData") { return JsonFilter( kinds = listOf(MetadataEvent.kind), authors = listOf(account.userProfile().pubkeyHex), - limit = 1 + limit = 3 ) } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrDataSource.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrDataSource.kt index ffae71cab..9cc9e5bcc 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrDataSource.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrDataSource.kt @@ -20,6 +20,7 @@ import kotlin.time.measureTimedValue import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job +import kotlinx.coroutines.delay import kotlinx.coroutines.launch import nostr.postr.events.ContactListEvent import nostr.postr.events.DeletionEvent @@ -118,7 +119,7 @@ abstract class NostrDataSource(val debugName: String) { val returningList = feed().take(100) // prepare previews - val scope = CoroutineScope(Job() + Dispatchers.Main) + val scope = CoroutineScope(Job() + Dispatchers.IO) scope.launch { loadPreviews(returningList) } @@ -147,17 +148,18 @@ abstract class NostrDataSource(val debugName: String) { channelIds.remove(channel.id) } - val filterHandler = Handler(Looper.getMainLooper()) + val scope = CoroutineScope(Job() + Dispatchers.IO) var handlerWaiting = false @Synchronized fun invalidateFilters() { if (handlerWaiting) return handlerWaiting = true - filterHandler.postDelayed({ + scope.launch { + delay(200) resetFilters() handlerWaiting = false - }, 200) + } } fun resetFilters() { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrSingleUserDataSource.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrSingleUserDataSource.kt index 781bb5910..009903bf5 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrSingleUserDataSource.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrSingleUserDataSource.kt @@ -16,7 +16,7 @@ object NostrSingleUserDataSource: NostrDataSource("SingleUserFeed") { JsonFilter( kinds = listOf(MetadataEvent.kind), authors = listOf(it.substring(0, 8)), - limit = 1 + limit = 10 ) } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt index 0c319dd73..08e1f2e05 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/DrawerContent.kt @@ -49,7 +49,10 @@ fun DrawerContent(navController: NavHostController, accountViewModel: AccountViewModel, accountStateViewModel: AccountStateViewModel) { - val accountUserState by accountViewModel.userLiveData.observeAsState() + val accountState by accountViewModel.accountLiveData.observeAsState() + val account = accountState?.account ?: return + + val accountUserState by account.userProfile().live.observeAsState() val accountUser = accountUserState?.user Surface( @@ -64,14 +67,18 @@ fun DrawerContent(navController: NavHostController, model = banner, contentDescription = "Profile Image", contentScale = ContentScale.FillWidth, - modifier = Modifier.fillMaxWidth().height(150.dp) + modifier = Modifier + .fillMaxWidth() + .height(150.dp) ) } else { Image( painter = painterResource(R.drawable.profile_banner), contentDescription = "Profile Banner", contentScale = ContentScale.FillWidth, - modifier = Modifier.fillMaxWidth().height(150.dp) + modifier = Modifier + .fillMaxWidth() + .height(150.dp) ) } @@ -106,12 +113,15 @@ fun DrawerContent(navController: NavHostController, fun ProfileContent(accountUser: User?, modifier: Modifier = Modifier, scaffoldState: ScaffoldState, navController: NavController) { val coroutineScope = rememberCoroutineScope() + println("AAA " + accountUser?.profilePicture()) + Column(modifier = modifier) { AsyncImage( model = accountUser?.profilePicture() ?: "https://robohash.org/ohno.png", contentDescription = "Profile Image", modifier = Modifier - .width(100.dp).height(100.dp) + .width(100.dp) + .height(100.dp) .clip(shape = CircleShape) .border(3.dp, MaterialTheme.colors.background, CircleShape) .background(MaterialTheme.colors.background) @@ -205,7 +215,8 @@ fun NavigationRow(navController: NavHostController, scaffoldState: ScaffoldState }) ) { Row( - modifier = Modifier.fillMaxWidth() + modifier = Modifier + .fillMaxWidth() .padding(vertical = 15.dp, horizontal = 25.dp), verticalAlignment = Alignment.CenterVertically ) { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedViewModel.kt index 3767099af..2d30f46d5 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedViewModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedViewModel.kt @@ -14,6 +14,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.cancel +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update @@ -26,24 +27,28 @@ class CardFeedViewModel(val dataSource: NostrDataSource): ViewModel() { private var lastNotes: List? = null fun refresh() { - val scope = CoroutineScope(Job() + Dispatchers.Main) + val scope = CoroutineScope(Job() + Dispatchers.IO) scope.launch { - val notes = dataSource.loadTop() + refreshSuspended() + } + } - val lastNotesCopy = lastNotes + private fun refreshSuspended() { + val notes = dataSource.loadTop() - val oldNotesState = feedContent.value - if (lastNotesCopy != null && oldNotesState is CardFeedState.Loaded) { - val newCards = convertToCard(notes.minus(lastNotesCopy)) - if (newCards.isNotEmpty()) { - lastNotes = notes - updateFeed((oldNotesState.feed + newCards).sortedBy { it.createdAt() }.reversed()) - } - } else { - val cards = convertToCard(notes) + val lastNotesCopy = lastNotes + + val oldNotesState = feedContent.value + if (lastNotesCopy != null && oldNotesState is CardFeedState.Loaded) { + val newCards = convertToCard(notes.minus(lastNotesCopy)) + if (newCards.isNotEmpty()) { lastNotes = notes - updateFeed(cards) + updateFeed((oldNotesState.feed + newCards).sortedBy { it.createdAt() }.reversed()) } + } else { + val cards = convertToCard(notes) + lastNotes = notes + updateFeed(cards) } } @@ -76,31 +81,28 @@ class CardFeedViewModel(val dataSource: NostrDataSource): ViewModel() { } fun updateFeed(notes: List) { - if (notes.isEmpty()) { - _feedContent.update { CardFeedState.Empty } - } else { - _feedContent.update { CardFeedState.Loaded(notes) } + val scope = CoroutineScope(Job() + Dispatchers.Main) + scope.launch { + if (notes.isEmpty()) { + _feedContent.update { CardFeedState.Empty } + } else { + _feedContent.update { CardFeedState.Loaded(notes) } + } } } - fun refreshCurrentList() { - val state = feedContent.value - if (state is CardFeedState.Loaded) { - _feedContent.update { CardFeedState.Loaded(state.feed) } - } - } - - val filterHandler = Handler(Looper.getMainLooper()) + val scope = CoroutineScope(Job() + Dispatchers.IO) var handlerWaiting = false @Synchronized fun invalidateData() { if (handlerWaiting) return handlerWaiting = true - filterHandler.postDelayed({ + scope.launch { + delay(100) refresh() handlerWaiting = false - }, 100) + } } private val cacheListener: (LocalCacheState) -> Unit = { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedViewModel.kt index b069bd112..71a3fb09a 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedViewModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedViewModel.kt @@ -23,6 +23,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.cancel +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update @@ -42,26 +43,33 @@ abstract class FeedViewModel(val dataSource: NostrDataSource): ViewModel() val feedContent = _feedContent.asStateFlow() fun refresh() { - val scope = CoroutineScope(Job() + Dispatchers.Main) + val scope = CoroutineScope(Job() + Dispatchers.IO) scope.launch { - val notes = dataSource.loadTop() + refreshSuspended() + } + } - val oldNotesState = feedContent.value - if (oldNotesState is FeedState.Loaded) { - if (notes != oldNotesState.feed) { - updateFeed(notes) - } - } else { + private fun refreshSuspended() { + val notes = dataSource.loadTop() + + val oldNotesState = feedContent.value + if (oldNotesState is FeedState.Loaded) { + if (notes != oldNotesState.feed) { updateFeed(notes) } + } else { + updateFeed(notes) } } fun updateFeed(notes: List) { - if (notes.isEmpty()) { - _feedContent.update { FeedState.Empty } - } else { - _feedContent.update { FeedState.Loaded(notes) } + val scope = CoroutineScope(Job() + Dispatchers.Main) + scope.launch { + if (notes.isEmpty()) { + _feedContent.update { FeedState.Empty } + } else { + _feedContent.update { FeedState.Loaded(notes) } + } } } @@ -72,17 +80,18 @@ abstract class FeedViewModel(val dataSource: NostrDataSource): ViewModel() } } - val filterHandler = Handler(Looper.getMainLooper()) + val scope = CoroutineScope(Job() + Dispatchers.IO) var handlerWaiting = false @Synchronized fun invalidateData() { if (handlerWaiting) return handlerWaiting = true - filterHandler.postDelayed({ + scope.launch { + delay(100) refresh() handlerWaiting = false - }, 100) + } } private val cacheListener: (LocalCacheState) -> Unit = { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedViewModel.kt index 62f1b22d9..992d43ebc 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedViewModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedViewModel.kt @@ -14,6 +14,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.cancel +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update @@ -32,26 +33,33 @@ open class UserFeedViewModel(val dataSource: NostrDataSource): ViewModel() val feedContent = _feedContent.asStateFlow() fun refresh() { - val scope = CoroutineScope(Job() + Dispatchers.Main) + val scope = CoroutineScope(Job() + Dispatchers.IO) scope.launch { - val notes = dataSource.loadTop() + refreshSuspended() + } + } - val oldNotesState = feedContent.value - if (oldNotesState is UserFeedState.Loaded) { - if (notes != oldNotesState.feed) { - updateFeed(notes) - } - } else { + private fun refreshSuspended() { + val notes = dataSource.loadTop() + + val oldNotesState = feedContent.value + if (oldNotesState is UserFeedState.Loaded) { + if (notes != oldNotesState.feed) { updateFeed(notes) } + } else { + updateFeed(notes) } } fun updateFeed(notes: List) { - if (notes.isEmpty()) { - _feedContent.update { UserFeedState.Empty } - } else { - _feedContent.update { UserFeedState.Loaded(notes) } + val scope = CoroutineScope(Job() + Dispatchers.Main) + scope.launch { + if (notes.isEmpty()) { + _feedContent.update { UserFeedState.Empty } + } else { + _feedContent.update { UserFeedState.Loaded(notes) } + } } } @@ -62,17 +70,18 @@ open class UserFeedViewModel(val dataSource: NostrDataSource): ViewModel() } } - val filterHandler = Handler(Looper.getMainLooper()) + val scope = CoroutineScope(Job() + Dispatchers.IO) var handlerWaiting = false @Synchronized fun invalidateData() { if (handlerWaiting) return handlerWaiting = true - filterHandler.postDelayed({ + scope.launch { + delay(100) refresh() handlerWaiting = false - }, 100) + } } private val cacheListener: (LocalCacheState) -> Unit = {