Skip to content

Commit 7af06ce

Browse files
committed
Convert article to flow
1 parent 5282ee3 commit 7af06ce

File tree

5 files changed

+93
-89
lines changed

5 files changed

+93
-89
lines changed

app/src/main/java/com/capyreader/app/ui/articles/ArticleScreen.kt

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,12 @@ fun ArticleScreen(
145145

146146
val article = viewModel.article
147147

148+
val onInitialized = { completion: () -> Unit ->
149+
viewModel.initialize(onComplete = completion)
150+
}
151+
152+
val article = viewModel.article.collectAsState(null).value
153+
148154
val search = ArticleSearch(
149155
query = searchQuery,
150156
start = { viewModel.startSearch() },
@@ -565,13 +571,14 @@ fun ArticleScreen(
565571
onBackPressed = {
566572
clearArticle()
567573
},
568-
onToggleRead = viewModel::toggleArticleRead,
569-
onToggleStar = viewModel::toggleArticleStar,
570574
enableBackHandler = media == null,
571575
onSelectMedia = { media = it },
572576
onSelectArticle = { articleID ->
573577
setArticle(articleID)
574578
},
579+
articleFinder = {
580+
viewModel.findArticle(it)
581+
},
575582
onScrollToArticle = { index ->
576583
scrollToArticle(index)
577584
}

app/src/main/java/com/capyreader/app/ui/articles/ArticleScreenViewModel.kt

Lines changed: 39 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ import androidx.compose.runtime.setValue
88
import androidx.lifecycle.AndroidViewModel
99
import androidx.lifecycle.viewModelScope
1010
import androidx.paging.PagingData
11+
import androidx.navigation.toRoute
12+
import androidx.paging.Pager
13+
import androidx.paging.PagingConfig
1114
import com.capyreader.app.R
1215
import com.capyreader.app.common.toast
1316
import com.capyreader.app.notifications.NotificationHelper
@@ -39,6 +42,7 @@ import kotlinx.coroutines.flow.Flow
3942
import kotlinx.coroutines.flow.MutableStateFlow
4043
import kotlinx.coroutines.flow.combine
4144
import kotlinx.coroutines.flow.flatMapLatest
45+
import kotlinx.coroutines.flow.lastOrNull
4246
import kotlinx.coroutines.flow.map
4347
import kotlinx.coroutines.launch
4448
import java.time.OffsetDateTime
@@ -63,7 +67,9 @@ class ArticleScreenViewModel(
6367

6468
private val _searchState = MutableStateFlow(SearchState.INACTIVE)
6569

66-
private var _article by mutableStateOf<Article?>(null)
70+
private var _articleID = MutableStateFlow("")
71+
72+
val article: Flow<Article?> = _articleID.flatMapLatest { account.findArticleAsync(it) }
6773

6874
var refreshingAll by mutableStateOf(false)
6975
private set
@@ -159,9 +165,6 @@ class ArticleScreenViewModel(
159165
val showUnauthorizedMessage: Boolean
160166
get() = _showUnauthorizedMessage == UnauthorizedMessageState.SHOW
161167

162-
val article: Article?
163-
get() = _article
164-
165168
val searchQuery: Flow<String>
166169
get() = _searchQuery
167170

@@ -335,13 +338,10 @@ class ArticleScreenViewModel(
335338
}
336339

337340
fun selectArticle(articleID: String, onComplete: (article: Article) -> Unit = {}) {
338-
if (_article?.id == articleID) {
339-
return
340-
}
341-
342341
viewModelScope.launchIO {
343342
val article = buildArticle(articleID) ?: return@launchIO
344-
_article = article
343+
344+
_articleID.value = articleID
345345

346346
appPreferences.articleID.set(articleID)
347347

@@ -361,34 +361,6 @@ class ArticleScreenViewModel(
361361
}
362362
}
363363

364-
fun toggleArticleRead() {
365-
_article?.let { article ->
366-
viewModelScope.launch {
367-
if (article.read) {
368-
markUnread(article.id)
369-
} else {
370-
markRead(article.id)
371-
}
372-
}
373-
374-
_article = article.copy(read = !article.read)
375-
}
376-
}
377-
378-
fun toggleArticleStar() {
379-
_article?.let { article ->
380-
viewModelScope.launch {
381-
if (article.starred) {
382-
removeStar(article.id)
383-
} else {
384-
addStar(article.id)
385-
}
386-
387-
_article = article.copy(starred = !article.starred)
388-
}
389-
}
390-
}
391-
392364
fun dismissUnauthorizedMessage() {
393365
_showUnauthorizedMessage = UnauthorizedMessageState.LATER
394366
}
@@ -416,22 +388,18 @@ class ArticleScreenViewModel(
416388
}
417389

418390
fun addStarAsync(articleID: String) {
419-
toggleCurrentStarred(articleID)
420391
addStar(articleID)
421392
}
422393

423394
fun removeStarAsync(articleID: String) = viewModelScope.launchIO {
424-
toggleCurrentStarred(articleID)
425395
removeStar(articleID)
426396
}
427397

428398
fun markReadAsync(articleID: String) = viewModelScope.launchIO {
429-
toggleCurrentRead(articleID)
430399
markRead(articleID)
431400
}
432401

433402
fun markUnreadAsync(articleID: String) = viewModelScope.launchIO {
434-
toggleCurrentRead(articleID)
435403
markUnread(articleID)
436404
}
437405

@@ -487,22 +455,6 @@ class ArticleScreenViewModel(
487455
updateFilter(ArticleFilter.default().copy(latestFilter.status))
488456
}
489457

490-
private fun toggleCurrentStarred(articleID: String) {
491-
_article?.let { article ->
492-
if (articleID == article.id) {
493-
_article = article.copy(starred = !article.starred)
494-
}
495-
}
496-
}
497-
498-
private fun toggleCurrentRead(articleID: String) {
499-
_article?.let { article ->
500-
if (articleID == article.id) {
501-
_article = article.copy(read = !article.read)
502-
}
503-
}
504-
}
505-
506458
private fun updateFilter(filter: ArticleFilter) {
507459
appPreferences.filter.set(filter)
508460

@@ -554,52 +506,55 @@ class ArticleScreenViewModel(
554506
)
555507
}
556508

557-
fun fetchFullContentAsync(article: Article? = _article) {
558-
article ?: return
559-
509+
fun fetchFullContentAsync() {
560510
viewModelScope.launchIO {
511+
val article = article.lastOrNull() ?: return@launchIO
512+
561513
if (enableStickyFullContent && !account.isFullContentEnabled(feedID = article.feedID)) {
562514
account.enableStickyContent(article.feedID)
563515
}
564516

565-
_article = article.copy(fullContent = Article.FullContentState.LOADING)
517+
TODO()
518+
// _article = article.copy(fullContent = Article.FullContentState.LOADING)
566519

567-
_article?.let { fetchFullContent(it) }
520+
// _article?.let { fetchFullContent(it) }
568521
}
569522
}
570523

571524
fun resetFullContent() {
572-
val article = _article ?: return
573-
574-
_article = article.copy(
575-
content = article.defaultContent,
576-
fullContent = Article.FullContentState.NONE
577-
)
525+
TODO()
526+
// val article = _article ?: return
578527

579-
if (enableStickyFullContent) {
580-
account.disableStickyContent(article.feedID)
581-
}
528+
// _article = article.copy(
529+
// content = article.defaultContent,
530+
// fullContent = Article.FullContentState.NONE
531+
// )
532+
//
533+
// if (enableStickyFullContent) {
534+
// account.disableStickyContent(article.feedID)
535+
// }
582536
}
583537

584538
private suspend fun fetchFullContent(article: Article) {
539+
TODO()
585540
account.fetchFullContent(article)
586541
.fold(
587542
onSuccess = { value ->
588-
if (_article?.id == article.id) {
589-
_article = article.copy(
590-
content = value,
591-
fullContent = Article.FullContentState.LOADED
592-
)
593-
}
543+
// if (_article?.id == article.id) {
544+
// _article = article.copy(
545+
// content = value,
546+
// fullContent = Article.FullContentState.LOADED
547+
// )
548+
// }
594549
},
595550
onFailure = {
596-
if (_article?.id != article.id) {
597-
return
598-
}
599-
_article = article.copy(
600-
content = article.defaultContent,
601-
fullContent = Article.FullContentState.ERROR
602-
)
551+
// if (_article?.id != article.id) {
552+
// return
553+
// }
554+
// _article = article.copy(
555+
// content = article.defaultContent,
556+
// fullContent = Article.FullContentState.ERROR
557+
// )
603558

604559
CapyLog.warn(
605560
"full_content",

app/src/main/java/com/capyreader/app/ui/articles/detail/ArticleView.kt

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,29 +52,47 @@ import com.capyreader.app.preferences.ArticleVerticalSwipe.NEXT_ARTICLE
5252
import com.capyreader.app.preferences.ArticleVerticalSwipe.OPEN_ARTICLE_IN_BROWSER
5353
import com.capyreader.app.preferences.ArticleVerticalSwipe.PREVIOUS_ARTICLE
5454
import com.capyreader.app.ui.LocalLinkOpener
55+
import com.capyreader.app.ui.articles.LocalArticleActions
5556
import com.capyreader.app.ui.articles.LocalFullContent
5657
import com.capyreader.app.ui.collectChangesWithDefault
5758
import com.capyreader.app.ui.components.pullrefresh.SwipeRefresh
5859
import com.jocmp.capy.Article
60+
import kotlinx.coroutines.flow.Flow
5961
import org.koin.compose.koinInject
6062

6163
@OptIn(ExperimentalMaterial3Api::class)
6264
@Composable
6365
fun ArticleView(
6466
article: Article,
6567
articles: LazyPagingItems<Article>,
68+
articleFinder: (id: String) -> Flow<Article?>,
6669
onBackPressed: () -> Unit,
67-
onToggleRead: () -> Unit,
68-
onToggleStar: () -> Unit,
6970
enableBackHandler: Boolean = false,
7071
onScrollToArticle: (index: Int) -> Unit,
7172
onSelectArticle: (id: String) -> Unit,
7273
onSelectMedia: (media: Media) -> Unit,
73-
appPreferences: AppPreferences = koinInject()
74+
appPreferences: AppPreferences = koinInject(),
7475
) {
7576
val enableHorizontalPager by appPreferences.readerOptions.enableHorizontaPagination.collectChangesWithDefault()
7677
val fullContent = LocalFullContent.current
7778
val openLink = articleOpenLink(article)
79+
val actions = LocalArticleActions.current
80+
81+
val onToggleRead = {
82+
if (article.read) {
83+
actions.markUnread(article.id)
84+
} else {
85+
actions.markRead(article.id)
86+
}
87+
}
88+
89+
val onToggleStar = {
90+
if (article.starred) {
91+
actions.unstar(article.id)
92+
} else {
93+
actions.star(article.id)
94+
}
95+
}
7896

7997
val onToggleFullContent = {
8098
if (article.fullContent == Article.FullContentState.LOADED) {

capy/src/main/java/com/jocmp/capy/Account.kt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import kotlinx.coroutines.async
3232
import kotlinx.coroutines.awaitAll
3333
import kotlinx.coroutines.flow.Flow
3434
import kotlinx.coroutines.flow.first
35+
import kotlinx.coroutines.flow.flow
3536
import kotlinx.coroutines.flow.map
3637
import okhttp3.OkHttpClient
3738
import java.io.InputStream
@@ -216,6 +217,19 @@ data class Account(
216217
return articleRecords.find(articleID = articleID)?.copy(enclosures = enclosures)
217218
}
218219

220+
fun findArticleAsync(articleID: String): Flow<Article?> {
221+
if (articleID.isBlank()) {
222+
return flow { null }
223+
}
224+
225+
return articleRecords.findAsync(articleID)
226+
.map {
227+
val enclosures = enclosureRecords.byArticle(articleID)
228+
229+
it?.copy(enclosures = enclosures)
230+
}
231+
}
232+
219233
suspend fun addStar(articleID: String): Result<Unit> {
220234
articleRecords.addStar(articleID = articleID)
221235

capy/src/main/java/com/jocmp/capy/persistence/ArticleRecords.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package com.jocmp.capy.persistence
22

33
import app.cash.sqldelight.coroutines.asFlow
44
import app.cash.sqldelight.coroutines.mapToList
5+
import app.cash.sqldelight.coroutines.mapToOneOrNull
56
import com.jocmp.capy.Article
67
import com.jocmp.capy.ArticleFilter
78
import com.jocmp.capy.ArticleNotification
@@ -28,6 +29,15 @@ internal class ArticleRecords internal constructor(
2829
val byFeed = ByFeed(database)
2930
val bySavedSearch = BySavedSearch(database)
3031

32+
fun findAsync(articleID: String): Flow<Article?> {
33+
return database.articlesQueries.findBy(
34+
articleID = articleID,
35+
mapper = ::articleMapper
36+
)
37+
.asFlow()
38+
.mapToOneOrNull(Dispatchers.IO)
39+
}
40+
3141
fun find(articleID: String): Article? {
3242
return database.articlesQueries.findBy(
3343
articleID = articleID,

0 commit comments

Comments
 (0)