Skip to content

Commit 0044d6b

Browse files
committed
Fixed small images being rendered too large and flickering on scroll
Fixed compose stability. Now items in reader are not re-rendered when scrolling. Fixed small images being rendered too large on screen
1 parent 3ca41dc commit 0044d6b

36 files changed

+766
-385
lines changed

.editorconfig

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ ij_visual_guides = none
1414
ij_wrap_on_typing = false
1515

1616
[*.{kt,kts}]
17+
max_line_length = 200
1718
ktlint_code_style = ktlint_official
1819
ktlint_function_naming_ignore_when_annotated_with=Composable
1920
twitter_compose_allowed_composition_locals = LocalTypographySettings,LocalDimens,LocalWindowSize,LocalFoldableHinge

app/build.gradle.kts

+16-19
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ plugins {
33
alias(libs.plugins.kotlin.android)
44
alias(libs.plugins.kotlin.ksp)
55
alias(libs.plugins.kotlin.parcelize)
6+
alias(libs.plugins.kotlin.serialization)
67
}
78

89
android {
@@ -184,25 +185,21 @@ android {
184185
}
185186
}
186187

187-
// https://chris.banes.dev/composable-metrics/
188-
// gw installDebugMini -Pmyapp.enableComposeCompilerReports=true
189-
// build/compose_metrics/[...]-composables.txt
190-
// tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach {
191-
// kotlinOptions {
192-
// if (project.findProperty("myapp.enableComposeCompilerReports") == "true") {
193-
// freeCompilerArgs += [
194-
// "-P",
195-
// "plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=" +
196-
// project.buildDir.absolutePath + "/compose_metrics"
197-
// ]
198-
// freeCompilerArgs += [
199-
// "-P",
200-
// "plugin:androidx.compose.compiler.plugins.kotlin:metricsDestination=" +
201-
// project.buildDir.absolutePath + "/compose_metrics"
202-
// ]
203-
// }
204-
// }
205-
// }
188+
// gw installDebug -Pmyapp.enableComposeCompilerReports=true --rerun-tasks
189+
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile::class.java) {
190+
kotlinOptions {
191+
if (project.findProperty("myapp.enableComposeCompilerReports") == "true") {
192+
freeCompilerArgs += listOf(
193+
"-P",
194+
"plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=${project.buildDir.resolve("compose_metrics").canonicalPath}"
195+
)
196+
freeCompilerArgs += listOf(
197+
"-P",
198+
"plugin:androidx.compose.compiler.plugins.kotlin:metricsDestination=${project.buildDir.resolve("compose_metrics").canonicalPath}"
199+
)
200+
}
201+
}
202+
}
206203

207204
configurations.all {
208205
resolutionStrategy {

app/src/androidTest/java/com/nononsenseapps/feeder/db/room/MigrationFromLegacy5ToLatest.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,7 @@ class MigrationFromLegacy5ToLatest {
281281
assertTrue(it.unread)
282282
assertNull(it.author)
283283
assertNull(it.enclosureLink)
284-
assertNull(it.imageUrl)
284+
assertNull(it.thumbnailImage)
285285
assertNull(it.pubDate)
286286
assertNull(it.link)
287287
assertFalse(it.notified)
@@ -318,7 +318,7 @@ class MigrationFromLegacy5ToLatest {
318318
assertFalse(it.unread)
319319
assertEquals("author$index", it.author)
320320
assertEquals("https://enclosure$index", it.enclosureLink)
321-
assertEquals("https://image$index", it.imageUrl)
321+
assertEquals("https://image$index", it.thumbnailImage?.url)
322322
assertEquals(ZonedDateTime.of(2018, 2, 3, 4, 5, 0, 0, ZoneOffset.UTC), it.pubDate)
323323
assertEquals("https://link$index", it.link)
324324
assertTrue(it.notified)

app/src/androidTest/java/com/nononsenseapps/feeder/db/room/MigrationFromLegacy6ToLatest.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,7 @@ class MigrationFromLegacy6ToLatest {
254254
assertTrue(it.unread)
255255
assertNull(it.author)
256256
assertNull(it.enclosureLink)
257-
assertNull(it.imageUrl)
257+
assertNull(it.thumbnailImage)
258258
assertNull(it.pubDate)
259259
assertNull(it.link)
260260
assertFalse(it.notified)
@@ -289,7 +289,7 @@ class MigrationFromLegacy6ToLatest {
289289
assertFalse(it.unread)
290290
assertEquals("author$index", it.author)
291291
assertEquals("https://enclosure$index", it.enclosureLink)
292-
assertEquals("https://image$index", it.imageUrl)
292+
assertEquals("https://image$index", it.thumbnailImage?.url)
293293
assertEquals(ZonedDateTime.of(2018, 2, 3, 4, 5, 0, 0, ZoneOffset.UTC), it.pubDate)
294294
assertEquals("https://link$index", it.link)
295295
assertTrue(it.notified)

app/src/main/java/com/nononsenseapps/feeder/archmodel/FeedItemStore.kt

+8-9
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,15 @@ import com.nononsenseapps.feeder.db.room.AppDatabase
99
import com.nononsenseapps.feeder.db.room.FeedItem
1010
import com.nononsenseapps.feeder.db.room.FeedItemCursor
1111
import com.nononsenseapps.feeder.db.room.FeedItemDao
12-
import com.nononsenseapps.feeder.db.room.FeedItemDao.Companion.feedItemsListOrderByAsc
13-
import com.nononsenseapps.feeder.db.room.FeedItemDao.Companion.feedItemsListOrderByDesc
12+
import com.nononsenseapps.feeder.db.room.FeedItemDao.Companion.FEED_ITEM_LIST_SORT_ORDER_ASC
13+
import com.nononsenseapps.feeder.db.room.FeedItemDao.Companion.FEED_ITEM_LIST_SORT_ORDER_DESC
1414
import com.nononsenseapps.feeder.db.room.FeedItemIdWithLink
1515
import com.nononsenseapps.feeder.db.room.FeedItemWithFeed
1616
import com.nononsenseapps.feeder.db.room.ID_SAVED_ARTICLES
1717
import com.nononsenseapps.feeder.db.room.ID_UNSET
1818
import com.nononsenseapps.feeder.db.room.upsertFeedItems
19+
import com.nononsenseapps.feeder.model.PREVIEW_COLUMNS
1920
import com.nononsenseapps.feeder.model.PreviewItem
20-
import com.nononsenseapps.feeder.model.previewColumns
2121
import com.nononsenseapps.feeder.ui.compose.feed.FeedListItem
2222
import com.nononsenseapps.feeder.ui.compose.feedarticle.FeedListFilter
2323
import kotlinx.coroutines.Dispatchers
@@ -77,15 +77,15 @@ class FeedItemStore(override val di: DI) : DIAware {
7777
val args = mutableListOf<Any?>()
7878

7979
queryString.apply {
80-
append("SELECT $previewColumns FROM feed_items\n")
80+
append("SELECT $PREVIEW_COLUMNS FROM feed_items\n")
8181
append("LEFT JOIN feeds ON feed_items.feed_id = feeds.id\n")
8282
append("WHERE\n")
8383

8484
rawQueryFilter(filter, args, minReadTime, feedId, tag)
8585

8686
when (newestFirst) {
87-
true -> append("ORDER BY $feedItemsListOrderByDesc\n")
88-
else -> append("ORDER BY $feedItemsListOrderByAsc\n")
87+
true -> append("ORDER BY $FEED_ITEM_LIST_SORT_ORDER_DESC\n")
88+
else -> append("ORDER BY $FEED_ITEM_LIST_SORT_ORDER_ASC\n")
8989
}
9090
}
9191

@@ -276,8 +276,7 @@ class FeedItemStore(override val di: DI) : DIAware {
276276
dao.markAllAsRead()
277277
}
278278

279-
fun getFeedsItemsWithDefaultFullTextNeedingDownload(): Flow<List<FeedItemIdWithLink>> =
280-
dao.getFeedsItemsWithDefaultFullTextNeedingDownload()
279+
fun getFeedsItemsWithDefaultFullTextNeedingDownload(): Flow<List<FeedItemIdWithLink>> = dao.getFeedsItemsWithDefaultFullTextNeedingDownload()
281280

282281
suspend fun markAsFullTextDownloaded(feedItemId: Long) = dao.markAsFullTextDownloaded(feedItemId)
283282

@@ -332,7 +331,7 @@ private fun PreviewItem.toFeedListItem() =
332331
feedTitle = feedDisplayTitle,
333332
unread = readTime == null,
334333
pubDate = pubDate?.toLocalDateTime()?.formatDynamically() ?: "",
335-
imageUrl = imageUrl,
334+
image = image,
336335
link = link,
337336
bookmarked = bookmarked,
338337
feedImageUrl = feedImageUrl,

app/src/main/java/com/nononsenseapps/feeder/archmodel/Repository.kt

+3-4
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import com.nononsenseapps.feeder.db.room.ID_UNSET
2424
import com.nononsenseapps.feeder.db.room.RemoteFeed
2525
import com.nononsenseapps.feeder.db.room.SyncDevice
2626
import com.nononsenseapps.feeder.db.room.SyncRemote
27+
import com.nononsenseapps.feeder.model.ThumbnailImage
2728
import com.nononsenseapps.feeder.model.workmanager.SyncServiceSendReadWorker
2829
import com.nononsenseapps.feeder.model.workmanager.requestFeedSync
2930
import com.nononsenseapps.feeder.sync.DeviceListResponse
@@ -510,8 +511,7 @@ class Repository(override val di: DI) : DIAware {
510511

511512
fun ensurePeriodicSyncConfigured() = settingsStore.configurePeriodicSync(replace = false)
512513

513-
fun getFeedsItemsWithDefaultFullTextNeedingDownload(): Flow<List<FeedItemIdWithLink>> =
514-
feedItemStore.getFeedsItemsWithDefaultFullTextNeedingDownload()
514+
fun getFeedsItemsWithDefaultFullTextNeedingDownload(): Flow<List<FeedItemIdWithLink>> = feedItemStore.getFeedsItemsWithDefaultFullTextNeedingDownload()
515515

516516
suspend fun markAsFullTextDownloaded(feedItemId: Long) = feedItemStore.markAsFullTextDownloaded(feedItemId)
517517

@@ -796,8 +796,7 @@ data class Article(
796796
val bookmarked: Boolean = item?.bookmarked ?: false
797797
val wordCount: Int = item?.wordCount ?: 0
798798
val wordCountFull: Int = item?.wordCountFull ?: 0
799-
val image: String? = item?.imageUrl
800-
val imageFromBody: Boolean = item?.imageFromBody ?: false
799+
val image: ThumbnailImage? = item?.thumbnailImage
801800
}
802801

803802
enum class TextToDisplay {

app/src/main/java/com/nononsenseapps/feeder/db/room/Converters.kt

+27
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
package com.nononsenseapps.feeder.db.room
22

33
import androidx.room.TypeConverter
4+
import com.nononsenseapps.feeder.model.ImageFromHTML
5+
import com.nononsenseapps.feeder.model.ThumbnailImage
46
import com.nononsenseapps.feeder.util.sloppyLinkToStrictURLNoThrows
7+
import kotlinx.serialization.encodeToString
8+
import kotlinx.serialization.json.Json
59
import java.net.URL
610
import java.time.Instant
711
import java.time.ZonedDateTime
@@ -38,4 +42,27 @@ class Converters {
3842

3943
@TypeConverter
4044
fun longFromInstant(value: Instant?): Long? = value?.toEpochMilli()
45+
46+
@TypeConverter
47+
fun stringFromThumbnailImage(value: ThumbnailImage?): String? =
48+
value?.let {
49+
Json.encodeToString(it)
50+
}
51+
52+
@TypeConverter
53+
fun thumbnailImageFromString(value: String?): ThumbnailImage? =
54+
value?.let {
55+
try {
56+
Json.decodeFromString<ThumbnailImage>(it)
57+
} catch (_: Throwable) {
58+
// Legacy values may be stored as just a URL
59+
try {
60+
val url = URL(it)
61+
// But we don't know what type so we pick a conservative default
62+
ImageFromHTML(url = url.toString(), width = null, height = null)
63+
} catch (_: Throwable) {
64+
null
65+
}
66+
}
67+
}
4168
}

app/src/main/java/com/nononsenseapps/feeder/db/room/FeedItem.kt

+9-4
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import com.nononsenseapps.feeder.db.COL_WORD_COUNT_FULL
3030
import com.nononsenseapps.feeder.db.FEED_ITEMS_TABLE_NAME
3131
import com.nononsenseapps.feeder.model.ParsedArticle
3232
import com.nononsenseapps.feeder.model.ParsedFeed
33+
import com.nononsenseapps.feeder.model.ThumbnailImage
3334
import com.nononsenseapps.feeder.model.host
3435
import com.nononsenseapps.feeder.ui.text.HtmlToPlainTextConverter
3536
import java.net.URI
@@ -74,8 +75,13 @@ data class FeedItem
7475
var title: String = "",
7576
@ColumnInfo(name = COL_PLAINTITLE) var plainTitle: String = "",
7677
@ColumnInfo(name = COL_PLAINSNIPPET) var plainSnippet: String = "",
77-
@ColumnInfo(name = COL_IMAGEURL) var imageUrl: String? = null,
78-
@ColumnInfo(name = COL_IMAGE_FROM_BODY) var imageFromBody: Boolean = false,
78+
@ColumnInfo(name = COL_IMAGEURL) var thumbnailImage: ThumbnailImage? = null,
79+
@ColumnInfo(name = COL_IMAGE_FROM_BODY)
80+
@Deprecated(
81+
"This column has been 'removed' but sqlite doesn't support drop column.",
82+
replaceWith = ReplaceWith("thumbnailImage?.fromBody ?: false"),
83+
)
84+
var imageFromBody: Boolean = false,
7985
@ColumnInfo(name = COL_ENCLOSURELINK) var enclosureLink: String? = null,
8086
@ColumnInfo(name = COL_ENCLOSURE_TYPE) var enclosureType: String? = null,
8187
@ColumnInfo(name = COL_AUTHOR) var author: String? = null,
@@ -152,8 +158,7 @@ data class FeedItem
152158
this.title = this.plainTitle
153159
this.plainSnippet = summary
154160

155-
this.imageUrl = safeImage?.url
156-
this.imageFromBody = safeImage?.fromBody ?: false
161+
this.thumbnailImage = safeImage
157162
val firstEnclosure = entry.attachments?.firstOrNull()
158163
this.enclosureLink = firstEnclosure?.url
159164
this.enclosureType = firstEnclosure?.mime_type?.lowercase()

0 commit comments

Comments
 (0)