Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main'
Browse files Browse the repository at this point in the history
  • Loading branch information
weblate committed Feb 9, 2025
2 parents 6f83b41 + 9edeaf2 commit 3483cf2
Show file tree
Hide file tree
Showing 12 changed files with 103 additions and 61 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -514,10 +514,15 @@ fun ArticleLayout(
}

ArticleListBackHandler(
enabled = isFeedActive(media, article, search)
) {
toggleDrawer()
}
enabled = isFeedActive(media, article, search),
isDrawerOpen = drawerState.isOpen,
toggleDrawer = {
toggleDrawer()
},
closeDrawer = {
closeDrawer()
}
)

LayoutNavigationHandler(
enabled = article == null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,18 @@ import org.koin.compose.koinInject
@Composable
fun ArticleListBackHandler(
appPreferences: AppPreferences = koinInject(),
closeDrawer: () -> Unit,
toggleDrawer: () -> Unit,
enabled: Boolean,
onBack: () -> Unit,
isDrawerOpen: Boolean,
) {
val backAction by appPreferences.articleListOptions.backAction.asState()

BackHandler(enabled && backAction != BackAction.SYSTEM_BACK) {
onBack()
toggleDrawer()
}

BackHandler(enabled && isDrawerOpen && backAction == BackAction.SYSTEM_BACK) {
closeDrawer()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ class ArticleScreenViewModel(

val savedSearches = account.savedSearches

val allFeeds = account.allFeeds
val allFeeds = account.taggedFeeds

val allFolders = account.folders

Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package com.capyreader.app.ui.articles.detail

import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
Expand Down Expand Up @@ -83,12 +83,6 @@ fun ArticleReader(
}

ArticleStyleListener(webView = webViewState.webView)

DisposableEffect(article.id) {
onDispose {
webViewState.reset()
}
}
}

@Composable
Expand Down
49 changes: 14 additions & 35 deletions app/src/main/java/com/capyreader/app/ui/components/WebView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,6 @@ import com.capyreader.app.ui.articles.detail.byline
import com.jocmp.capy.Article
import com.jocmp.capy.articles.ArticleRenderer
import com.jocmp.capy.common.windowOrigin
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.koin.compose.koinInject
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
Expand Down Expand Up @@ -118,41 +114,24 @@ class AccompanistWebViewClient(
class WebViewState(
private val renderer: ArticleRenderer,
private val colors: Map<String, String>,
private val scope: CoroutineScope,
internal val webView: WebView,
) {
init {
loadEmpty()
}

fun loadHtml(article: Article, showImages: Boolean) {
scope.launch {
withContext(Dispatchers.IO) {
val html = renderer.render(
article,
hideImages = !showImages,
byline = article.byline(context = webView.context),
colors = colors
)

withContext(Dispatchers.Main) {
webView.loadDataWithBaseURL(
windowOrigin(article.url),
html,
null,
"UTF-8",
null,
)
}
}
}
}
val html = renderer.render(
article,
hideImages = !showImages,
byline = article.byline(context = webView.context),
colors = colors
)

fun reset() {
loadEmpty()
webView.loadDataWithBaseURL(
windowOrigin(article.url),
html,
null,
"UTF-8",
null,
)
}

private fun loadEmpty() = webView.loadUrl("about:blank")
}

@SuppressLint("SetJavaScriptEnabled")
Expand Down Expand Up @@ -195,7 +174,7 @@ fun rememberWebViewState(
webViewClient = client
}

WebViewState(renderer, colors, scope, webView).also {
WebViewState(renderer, colors, webView).also {
client.state = it
}
}
Expand Down
8 changes: 6 additions & 2 deletions capy/src/main/java/com/jocmp/capy/Account.kt
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ data class Account(

private val articleContent = ArticleContent(httpClient = localHttpClient)

val taggedFeeds = feedRecords.taggedFeeds().map {
it.sortedByTitle()
}

val allFeeds = feedRecords.feeds().map {
it.sortedByTitle()
}
Expand All @@ -76,12 +80,12 @@ data class Account(
it.sortedByName()
}

val feeds: Flow<List<Feed>> = allFeeds.map { all ->
val feeds: Flow<List<Feed>> = taggedFeeds.map { all ->
all.filter { it.folderName.isBlank() }
.sortedByTitle()
}

val folders: Flow<List<Folder>> = allFeeds.map { ungrouped ->
val folders: Flow<List<Folder>> = taggedFeeds.map { ungrouped ->
ungrouped
.filter { it.folderName.isNotBlank() }
.groupBy { it.folderName }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,13 @@ internal fun listMapper(
url = url,
feedTitle = feedTitle,
imageURL = imageURL,
summary = summary ?: "",
summary = if (!summary.isNullOrBlank()) {
summary
} else if (title.isNullOrBlank()) {
url.orEmpty()
} else {
""
},
updatedAt = updatedAt,
read = read ?: false,
starred = starred ?: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,12 +95,15 @@ internal class ArticleRecords internal constructor(
}
}

return activeNotifications()
return notifications(articleIDs)
}

private suspend fun activeNotifications(): List<ArticleNotification> {
private suspend fun notifications(articleIDs: List<String>): List<ArticleNotification> {
return notificationQueries
.activeNotifications(mapper = ::articleNotificationMapper)
.notificationsByID(
article_ids = articleIDs,
mapper = ::articleNotificationMapper
)
.asFlow()
.mapToList(Dispatchers.IO)
.firstOrNull()
Expand Down
9 changes: 8 additions & 1 deletion capy/src/main/java/com/jocmp/capy/persistence/FeedRecords.kt
Original file line number Diff line number Diff line change
Expand Up @@ -86,13 +86,20 @@ internal class FeedRecords(private val database: Database) {
return Folder(title = title, feeds = feeds)
}

internal fun feeds(): Flow<List<Feed>> {
internal fun taggedFeeds(): Flow<List<Feed>> {
return database.feedsQueries
.tagged(mapper = ::feedMapper)
.asFlow()
.mapToList(Dispatchers.IO)
}

internal fun feeds(): Flow<List<Feed>> {
return database.feedsQueries
.all(mapper = ::feedMapper)
.asFlow()
.mapToList(Dispatchers.IO)
}

internal fun removeFeed(feedID: String) {
database.feedsQueries.delete(listOf(feedID))
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
activeNotifications:
notificationsByID:
SELECT
article_id,
articles.title,
Expand All @@ -9,7 +9,7 @@ SELECT
FROM article_notifications
JOIN articles ON article_notifications.article_id = articles.id
JOIN feeds ON articles.feed_id = feeds.id
AND article_notifications.dismissed_at IS NULL;
WHERE articles.id IN :article_ids;

createNotification:
INSERT INTO article_notifications(
Expand Down Expand Up @@ -39,4 +39,4 @@ LEFT JOIN article_notifications ON article_notifications.article_id = articles.i
WHERE article_statuses.updated_at >= :since
AND article_statuses.read = 0
AND feeds.enable_notifications = 1
AND article_notifications.dismissed_at IS NULL;
AND article_notifications.article_id IS NULL;
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import com.jocmp.capy.ArticleFilter
import com.jocmp.capy.InMemoryDatabaseProvider
import com.jocmp.capy.accounts.AddFeedResult
import com.jocmp.capy.accounts.FakeFaviconFetcher
import com.jocmp.capy.common.TimeHelpers
import com.jocmp.capy.db.Database
import com.jocmp.capy.fixtures.FeedFixture
import com.jocmp.capy.logging.CapyLog
Expand Down Expand Up @@ -113,6 +114,30 @@ class LocalAccountDelegateTest {
assertEquals(expected = 2, actual = articlesCount)
}

@Test
fun refreshAll_doesNotChangeUpdatedAtTimestamp() = runTest {
val mockNow = ZonedDateTime.parse("2024-12-25T09:00:00-00:00")
mockkObject(TimeHelpers)
coEvery { feedFinder.fetch(url = any()) }.returns(Result.success(channel))
every { TimeHelpers.nowUTC() }.returns(mockNow)

FeedFixture(database).create(feedID = channel.link!!)

delegate.refresh(ArticleFilter.default())

var article = ArticleRecords(database).find(articleID = item.link!!)!!

val firstUpdatedAt = article.updatedAt
assertEquals(expected = mockNow, actual = firstUpdatedAt)

val futureNow = ZonedDateTime.parse("2025-12-25T09:00:00-00:00")
every { TimeHelpers.nowUTC() }.returns(futureNow)
delegate.refresh(ArticleFilter.default())
article = ArticleRecords(database).find(articleID = item.link!!)!!

assertEquals(expected = firstUpdatedAt, actual = article.updatedAt)
}


@Test
fun refreshAll_updatesEntriesWithCutoff() = runTest {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import com.jocmp.capy.fixtures.ArticleFixture
import com.jocmp.capy.fixtures.FeedFixture
import com.jocmp.capy.reload
import com.jocmp.capy.repeated
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
Expand Down Expand Up @@ -437,7 +436,7 @@ class ArticleRecordsTest {
}

@Test
fun createNotifications_ignoresDeletedNotifications() = runBlocking {
fun createNotifications_ignoresDeletedNotifications() = runTest {
val feed = FeedFixture(database).create(enableNotifications = true)
val article = articleFixture.create(feed = feed, read = false)
val since = article.publishedAt.minusMinutes(15)
Expand All @@ -450,6 +449,20 @@ class ArticleRecordsTest {

assertEquals(actual = refreshedNotifications.size, expected = 0)
}

@Test
fun createNotifications_ignoresCreatedNotifications() = runTest {
val feed = FeedFixture(database).create(enableNotifications = true)
val article = articleFixture.create(feed = feed, read = false)
val since = article.publishedAt.minusMinutes(15)

val notifications = articleRecords.createNotifications(since = since)
assertEquals(actual = notifications.size, expected = 1)

val refreshedNotifications = articleRecords.createNotifications(since = since)

assertEquals(actual = refreshedNotifications.size, expected = 0)
}
}

fun sortedMessage(expected: List<Article>, actual: List<Article>): String {
Expand Down

0 comments on commit 3483cf2

Please sign in to comment.