diff --git a/app/src/main/java/com/capyreader/app/ui/articles/ArticleHandler.kt b/app/src/main/java/com/capyreader/app/ui/articles/ArticleHandler.kt index eb894648e..92dfe105b 100644 --- a/app/src/main/java/com/capyreader/app/ui/articles/ArticleHandler.kt +++ b/app/src/main/java/com/capyreader/app/ui/articles/ArticleHandler.kt @@ -5,6 +5,7 @@ import androidx.compose.runtime.LaunchedEffect import com.capyreader.app.preferences.AppPreferences import com.jocmp.capy.Article import org.koin.compose.koinInject +import com.jocmp.capy.logging.CapyLog @Composable fun ArticleHandler( @@ -12,16 +13,12 @@ fun ArticleHandler( appPreferences: AppPreferences = koinInject(), onRequestArticle: (articleID: String) -> Unit, ) { - LaunchedEffect(article?.id) { - if (article != null) { - return@LaunchedEffect - } - - val articleID = appPreferences.articleID.get() - - if (articleID.isNotBlank()) { - appPreferences.articleID.delete() - onRequestArticle(articleID) + LaunchedEffect(article?.id, state) { + if (article != null && state?.pane != ThreePaneScaffoldRole.Primary) { + CapyLog.info("launch", mapOf("id" to article.id)) + onNavigateToArticle(article) + } else { + CapyLog.info("skip", mapOf("id" to article?.id, "state" to state?.pane)) } } } diff --git a/app/src/main/java/com/capyreader/app/ui/articles/ArticleScreen.kt b/app/src/main/java/com/capyreader/app/ui/articles/ArticleScreen.kt index b7526584a..936dd6cdb 100644 --- a/app/src/main/java/com/capyreader/app/ui/articles/ArticleScreen.kt +++ b/app/src/main/java/com/capyreader/app/ui/articles/ArticleScreen.kt @@ -145,6 +145,12 @@ fun ArticleScreen( val article = viewModel.article + val onInitialized = { completion: () -> Unit -> + viewModel.initialize(onComplete = completion) + } + + val article = viewModel.article.collectAsState(null).value + val search = ArticleSearch( query = searchQuery, start = { viewModel.startSearch() }, @@ -565,13 +571,14 @@ fun ArticleScreen( onBackPressed = { clearArticle() }, - onToggleRead = viewModel::toggleArticleRead, - onToggleStar = viewModel::toggleArticleStar, enableBackHandler = media == null, onSelectMedia = { media = it }, onSelectArticle = { articleID -> setArticle(articleID) }, + articleFinder = { + viewModel.findArticle(it) + }, onScrollToArticle = { index -> scrollToArticle(index) } diff --git a/app/src/main/java/com/capyreader/app/ui/articles/ArticleScreenViewModel.kt b/app/src/main/java/com/capyreader/app/ui/articles/ArticleScreenViewModel.kt index 7602ca6b6..2b903d985 100644 --- a/app/src/main/java/com/capyreader/app/ui/articles/ArticleScreenViewModel.kt +++ b/app/src/main/java/com/capyreader/app/ui/articles/ArticleScreenViewModel.kt @@ -8,6 +8,9 @@ import androidx.compose.runtime.setValue import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.viewModelScope import androidx.paging.PagingData +import androidx.navigation.toRoute +import androidx.paging.Pager +import androidx.paging.PagingConfig import com.capyreader.app.R import com.capyreader.app.common.toast import com.capyreader.app.notifications.NotificationHelper @@ -39,6 +42,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.lastOrNull import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import java.time.OffsetDateTime @@ -63,7 +67,9 @@ class ArticleScreenViewModel( private val _searchState = MutableStateFlow(SearchState.INACTIVE) - private var _article by mutableStateOf(null) + private var _articleID = MutableStateFlow("") + + val article: Flow = _articleID.flatMapLatest { account.findArticleAsync(it) } var refreshingAll by mutableStateOf(false) private set @@ -159,9 +165,6 @@ class ArticleScreenViewModel( val showUnauthorizedMessage: Boolean get() = _showUnauthorizedMessage == UnauthorizedMessageState.SHOW - val article: Article? - get() = _article - val searchQuery: Flow get() = _searchQuery @@ -335,13 +338,10 @@ class ArticleScreenViewModel( } fun selectArticle(articleID: String, onComplete: (article: Article) -> Unit = {}) { - if (_article?.id == articleID) { - return - } - viewModelScope.launchIO { val article = buildArticle(articleID) ?: return@launchIO - _article = article + + _articleID.value = articleID appPreferences.articleID.set(articleID) @@ -361,34 +361,6 @@ class ArticleScreenViewModel( } } - fun toggleArticleRead() { - _article?.let { article -> - viewModelScope.launch { - if (article.read) { - markUnread(article.id) - } else { - markRead(article.id) - } - } - - _article = article.copy(read = !article.read) - } - } - - fun toggleArticleStar() { - _article?.let { article -> - viewModelScope.launch { - if (article.starred) { - removeStar(article.id) - } else { - addStar(article.id) - } - - _article = article.copy(starred = !article.starred) - } - } - } - fun dismissUnauthorizedMessage() { _showUnauthorizedMessage = UnauthorizedMessageState.LATER } @@ -416,22 +388,18 @@ class ArticleScreenViewModel( } fun addStarAsync(articleID: String) { - toggleCurrentStarred(articleID) addStar(articleID) } fun removeStarAsync(articleID: String) = viewModelScope.launchIO { - toggleCurrentStarred(articleID) removeStar(articleID) } fun markReadAsync(articleID: String) = viewModelScope.launchIO { - toggleCurrentRead(articleID) markRead(articleID) } fun markUnreadAsync(articleID: String) = viewModelScope.launchIO { - toggleCurrentRead(articleID) markUnread(articleID) } @@ -487,22 +455,6 @@ class ArticleScreenViewModel( updateFilter(ArticleFilter.default().copy(latestFilter.status)) } - private fun toggleCurrentStarred(articleID: String) { - _article?.let { article -> - if (articleID == article.id) { - _article = article.copy(starred = !article.starred) - } - } - } - - private fun toggleCurrentRead(articleID: String) { - _article?.let { article -> - if (articleID == article.id) { - _article = article.copy(read = !article.read) - } - } - } - private fun updateFilter(filter: ArticleFilter) { appPreferences.filter.set(filter) @@ -554,52 +506,55 @@ class ArticleScreenViewModel( ) } - fun fetchFullContentAsync(article: Article? = _article) { - article ?: return - + fun fetchFullContentAsync() { viewModelScope.launchIO { + val article = article.lastOrNull() ?: return@launchIO + if (enableStickyFullContent && !account.isFullContentEnabled(feedID = article.feedID)) { account.enableStickyContent(article.feedID) } - _article = article.copy(fullContent = Article.FullContentState.LOADING) + TODO() +// _article = article.copy(fullContent = Article.FullContentState.LOADING) - _article?.let { fetchFullContent(it) } +// _article?.let { fetchFullContent(it) } } } fun resetFullContent() { - val article = _article ?: return - - _article = article.copy( - content = article.defaultContent, - fullContent = Article.FullContentState.NONE - ) + TODO() +// val article = _article ?: return - if (enableStickyFullContent) { - account.disableStickyContent(article.feedID) - } +// _article = article.copy( +// content = article.defaultContent, +// fullContent = Article.FullContentState.NONE +// ) +// +// if (enableStickyFullContent) { +// account.disableStickyContent(article.feedID) +// } } private suspend fun fetchFullContent(article: Article) { + TODO() account.fetchFullContent(article) .fold( onSuccess = { value -> - if (_article?.id == article.id) { - _article = article.copy( - content = value, - fullContent = Article.FullContentState.LOADED - ) - } +// if (_article?.id == article.id) { +// _article = article.copy( +// content = value, +// fullContent = Article.FullContentState.LOADED +// ) +// } }, onFailure = { - if (_article?.id != article.id) { - return - } - _article = article.copy( - content = article.defaultContent, - fullContent = Article.FullContentState.ERROR - ) +// if (_article?.id != article.id) { +// return +// } +// _article = article.copy( +// content = article.defaultContent, +// fullContent = Article.FullContentState.ERROR +// ) CapyLog.warn( "full_content", diff --git a/app/src/main/java/com/capyreader/app/ui/articles/detail/ArticleView.kt b/app/src/main/java/com/capyreader/app/ui/articles/detail/ArticleView.kt index 03598deac..c3b0eb51f 100644 --- a/app/src/main/java/com/capyreader/app/ui/articles/detail/ArticleView.kt +++ b/app/src/main/java/com/capyreader/app/ui/articles/detail/ArticleView.kt @@ -52,10 +52,12 @@ import com.capyreader.app.preferences.ArticleVerticalSwipe.NEXT_ARTICLE import com.capyreader.app.preferences.ArticleVerticalSwipe.OPEN_ARTICLE_IN_BROWSER import com.capyreader.app.preferences.ArticleVerticalSwipe.PREVIOUS_ARTICLE import com.capyreader.app.ui.LocalLinkOpener +import com.capyreader.app.ui.articles.LocalArticleActions import com.capyreader.app.ui.articles.LocalFullContent import com.capyreader.app.ui.collectChangesWithDefault import com.capyreader.app.ui.components.pullrefresh.SwipeRefresh import com.jocmp.capy.Article +import kotlinx.coroutines.flow.Flow import org.koin.compose.koinInject @OptIn(ExperimentalMaterial3Api::class) @@ -63,18 +65,34 @@ import org.koin.compose.koinInject fun ArticleView( article: Article, articles: LazyPagingItems
, + articleFinder: (id: String) -> Flow, onBackPressed: () -> Unit, - onToggleRead: () -> Unit, - onToggleStar: () -> Unit, enableBackHandler: Boolean = false, onScrollToArticle: (index: Int) -> Unit, onSelectArticle: (id: String) -> Unit, onSelectMedia: (media: Media) -> Unit, - appPreferences: AppPreferences = koinInject() + appPreferences: AppPreferences = koinInject(), ) { val enableHorizontalPager by appPreferences.readerOptions.enableHorizontaPagination.collectChangesWithDefault() val fullContent = LocalFullContent.current val openLink = articleOpenLink(article) + val actions = LocalArticleActions.current + + val onToggleRead = { + if (article.read) { + actions.markUnread(article.id) + } else { + actions.markRead(article.id) + } + } + + val onToggleStar = { + if (article.starred) { + actions.unstar(article.id) + } else { + actions.star(article.id) + } + } val onToggleFullContent = { if (article.fullContent == Article.FullContentState.LOADED) { diff --git a/capy/src/main/java/com/jocmp/capy/Account.kt b/capy/src/main/java/com/jocmp/capy/Account.kt index a19ffb4ad..04de5437b 100644 --- a/capy/src/main/java/com/jocmp/capy/Account.kt +++ b/capy/src/main/java/com/jocmp/capy/Account.kt @@ -32,6 +32,7 @@ import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.map import okhttp3.OkHttpClient import java.io.InputStream @@ -216,6 +217,19 @@ data class Account( return articleRecords.find(articleID = articleID)?.copy(enclosures = enclosures) } + fun findArticleAsync(articleID: String): Flow { + if (articleID.isBlank()) { + return flow { null } + } + + return articleRecords.findAsync(articleID) + .map { + val enclosures = enclosureRecords.byArticle(articleID) + + it?.copy(enclosures = enclosures) + } + } + suspend fun addStar(articleID: String): Result { articleRecords.addStar(articleID = articleID) diff --git a/capy/src/main/java/com/jocmp/capy/logging/CapyLog.kt b/capy/src/main/java/com/jocmp/capy/logging/CapyLog.kt index cb82af2f2..13d29ef5b 100644 --- a/capy/src/main/java/com/jocmp/capy/logging/CapyLog.kt +++ b/capy/src/main/java/com/jocmp/capy/logging/CapyLog.kt @@ -7,6 +7,10 @@ object CapyLog : Logging { Log.d(TAG, serializeData(event, data)) } + override fun info(tag: String, data: Map) { + Log.i(appTag(tag), serializeData(data)) + } + override fun info(event: String, data: Map) { Log.i(TAG, serializeData(event, data)) } diff --git a/capy/src/main/java/com/jocmp/capy/logging/Logging.kt b/capy/src/main/java/com/jocmp/capy/logging/Logging.kt index 42b6b90e0..3c74108d7 100644 --- a/capy/src/main/java/com/jocmp/capy/logging/Logging.kt +++ b/capy/src/main/java/com/jocmp/capy/logging/Logging.kt @@ -3,6 +3,8 @@ package com.jocmp.capy.logging interface Logging { fun debug(event: String, data: Map = emptyMap()) + fun info(tag: String, data: Map = emptyMap()) + fun info(event: String, data: Map = emptyMap()) fun warn(event: String, data: Map = emptyMap()) diff --git a/capy/src/main/java/com/jocmp/capy/persistence/ArticleRecords.kt b/capy/src/main/java/com/jocmp/capy/persistence/ArticleRecords.kt index 90743bad1..fd997a9d7 100644 --- a/capy/src/main/java/com/jocmp/capy/persistence/ArticleRecords.kt +++ b/capy/src/main/java/com/jocmp/capy/persistence/ArticleRecords.kt @@ -2,6 +2,7 @@ package com.jocmp.capy.persistence import app.cash.sqldelight.coroutines.asFlow import app.cash.sqldelight.coroutines.mapToList +import app.cash.sqldelight.coroutines.mapToOneOrNull import com.jocmp.capy.Article import com.jocmp.capy.ArticleFilter import com.jocmp.capy.ArticleNotification @@ -28,6 +29,15 @@ internal class ArticleRecords internal constructor( val byFeed = ByFeed(database) val bySavedSearch = BySavedSearch(database) + fun findAsync(articleID: String): Flow { + return database.articlesQueries.findBy( + articleID = articleID, + mapper = ::articleMapper + ) + .asFlow() + .mapToOneOrNull(Dispatchers.IO) + } + fun find(articleID: String): Article? { return database.articlesQueries.findBy( articleID = articleID,