From 698ffe5a11849d13427d9abb62f1dcc8f1ab70dc Mon Sep 17 00:00:00 2001 From: Sasikanth Miriyampalli Date: Tue, 6 Feb 2024 07:44:07 +0530 Subject: [PATCH 1/3] Use `Instant#from` to convert parsed date string to `Instant` on Android This is handle most of the situations, in scenarios where certain required values are missing. We use `fallbackParseToInstant` which is essentially what we were doing before. We are making this change to resolve date time parsing not working on certain sources like kottke.org, 9to5mac, the pragmatic engineer, etc., on Android --- .../parser/DateTimeFormatters.android.kt | 124 +++++++++++------- 1 file changed, 74 insertions(+), 50 deletions(-) diff --git a/core/network/src/androidMain/kotlin/dev/sasikanth/rss/reader/core/network/parser/DateTimeFormatters.android.kt b/core/network/src/androidMain/kotlin/dev/sasikanth/rss/reader/core/network/parser/DateTimeFormatters.android.kt index 8c5f374df..541fc5005 100644 --- a/core/network/src/androidMain/kotlin/dev/sasikanth/rss/reader/core/network/parser/DateTimeFormatters.android.kt +++ b/core/network/src/androidMain/kotlin/dev/sasikanth/rss/reader/core/network/parser/DateTimeFormatters.android.kt @@ -16,6 +16,8 @@ package dev.sasikanth.rss.reader.core.network.parser +import java.time.Instant +import java.time.LocalDateTime import java.time.ZoneId import java.time.format.DateTimeFormatter import java.time.temporal.ChronoField @@ -55,61 +57,83 @@ internal actual fun String?.dateStringToEpochMillis(clock: Clock): Long? { for (dateFormatter in dateFormatters) { try { - val parsedValue = dateFormatter.parse(this) + val parsedValue = parseToInstant(dateFormatter, this) + return parsedValue.toEpochMilli() + } catch (e: Exception) { + try { + val parsedValue = fallbackParseToInstant(currentDate, dateFormatter, this) + return parsedValue.toEpochMilli() + } catch (e: Exception) { + // no-op + } + } + } - val year = - try { - parsedValue.get(ChronoField.YEAR) - } catch (e: UnsupportedTemporalTypeException) { - currentDate.get(ChronoField.YEAR) - } - val hourOfDay = - try { - parsedValue.get(ChronoField.HOUR_OF_DAY) - } catch (e: UnsupportedTemporalTypeException) { - 0 - } - val minuteOfHour = - try { - parsedValue.get(ChronoField.MINUTE_OF_HOUR) - } catch (e: UnsupportedTemporalTypeException) { - 0 - } - val secondOfMinute = - try { - parsedValue.get(ChronoField.SECOND_OF_MINUTE) - } catch (e: UnsupportedTemporalTypeException) { - 0 - } - val nanoOfSecond = - try { - parsedValue.get(ChronoField.NANO_OF_SECOND) - } catch (e: UnsupportedTemporalTypeException) { - 0 - } + return null +} - val updatedDate = - currentDate - .withDayOfMonth(parsedValue.get(ChronoField.DAY_OF_MONTH)) - .withMonth(parsedValue.get(ChronoField.MONTH_OF_YEAR)) - .withYear(year) - .withHour(hourOfDay) - .withMinute(minuteOfHour) - .withSecond(secondOfMinute) - .withNano(nanoOfSecond) +private fun parseToInstant(dateTimeFormatter: DateTimeFormatter, text: String): Instant { + return dateTimeFormatter.parse(text, Instant::from) +} + +/** + * In case the date string has values missing from it like year, day, hour, etc., we fill those + * using [currentDate] and then try to convert it to instant. + */ +private fun fallbackParseToInstant( + currentDate: LocalDateTime, + dateTimeFormatter: DateTimeFormatter, + text: String +): Instant { + val parsedValue = dateTimeFormatter.parse(text) + + val year = + try { + parsedValue.get(ChronoField.YEAR) + } catch (e: UnsupportedTemporalTypeException) { + currentDate.get(ChronoField.YEAR) + } + val hourOfDay = + try { + parsedValue.get(ChronoField.HOUR_OF_DAY) + } catch (e: UnsupportedTemporalTypeException) { + 0 + } + val minuteOfHour = + try { + parsedValue.get(ChronoField.MINUTE_OF_HOUR) + } catch (e: UnsupportedTemporalTypeException) { + 0 + } + val secondOfMinute = + try { + parsedValue.get(ChronoField.SECOND_OF_MINUTE) + } catch (e: UnsupportedTemporalTypeException) { + 0 + } + val nanoOfSecond = + try { + parsedValue.get(ChronoField.NANO_OF_SECOND) + } catch (e: UnsupportedTemporalTypeException) { + 0 + } - val zoneId = - try { - ZoneId.from(parsedValue) - } catch (e: Exception) { - TimeZone.UTC.toJavaZoneId() - } + val updatedDate = + currentDate + .withDayOfMonth(parsedValue.get(ChronoField.DAY_OF_MONTH)) + .withMonth(parsedValue.get(ChronoField.MONTH_OF_YEAR)) + .withYear(year) + .withHour(hourOfDay) + .withMinute(minuteOfHour) + .withSecond(secondOfMinute) + .withNano(nanoOfSecond) - return updatedDate.atZone(zoneId).toInstant().toEpochMilli() + val zoneId = + try { + ZoneId.from(parsedValue) } catch (e: Exception) { - // no-op + TimeZone.UTC.toJavaZoneId() } - } - return null + return updatedDate.atZone(zoneId).toInstant() } From cf8b35a2965b873a5b251a8eb75946e618c64299 Mon Sep 17 00:00:00 2001 From: Sasikanth Miriyampalli Date: Tue, 6 Feb 2024 08:02:07 +0530 Subject: [PATCH 2/3] Remove border and border radius around iframe in reader view --- .../kotlin/dev/sasikanth/rss/reader/reader/ui/ReaderHTML.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/shared/src/commonMain/kotlin/dev/sasikanth/rss/reader/reader/ui/ReaderHTML.kt b/shared/src/commonMain/kotlin/dev/sasikanth/rss/reader/reader/ui/ReaderHTML.kt index 6edb125be..afd230e7f 100644 --- a/shared/src/commonMain/kotlin/dev/sasikanth/rss/reader/reader/ui/ReaderHTML.kt +++ b/shared/src/commonMain/kotlin/dev/sasikanth/rss/reader/reader/ui/ReaderHTML.kt @@ -224,8 +224,6 @@ private object ReaderCSS { iframe { max-width: 100%; max-height: 250px; - border-radius: 28px; - border: 1px solid #fff; } blockquote { margin-left: 8px; From 46b8b107894f233fcb10d058650232ad46ff2ec4 Mon Sep 17 00:00:00 2001 From: Sasikanth Miriyampalli Date: Tue, 6 Feb 2024 08:09:48 +0530 Subject: [PATCH 3/3] Cache paging results after headers are added in feeds sheet --- .../dev/sasikanth/rss/reader/feeds/FeedsPresenter.kt | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/shared/src/commonMain/kotlin/dev/sasikanth/rss/reader/feeds/FeedsPresenter.kt b/shared/src/commonMain/kotlin/dev/sasikanth/rss/reader/feeds/FeedsPresenter.kt index b2640a3d6..107dee96a 100644 --- a/shared/src/commonMain/kotlin/dev/sasikanth/rss/reader/feeds/FeedsPresenter.kt +++ b/shared/src/commonMain/kotlin/dev/sasikanth/rss/reader/feeds/FeedsPresenter.kt @@ -223,8 +223,9 @@ class FeedsPresenter( } else { feedsPager(postsAfter = postsAfter) } + val feedsWithHeaders = addFeedsHeaders(feeds).cachedIn(coroutineScope) - _state.update { it.copy(feedsInExpandedMode = addFeedsHeaders(feeds)) } + _state.update { it.copy(feedsInExpandedMode = feedsWithHeaders) } } .launchIn(coroutineScope) } @@ -234,7 +235,7 @@ class FeedsPresenter( settingsRepository.postsType.distinctUntilChanged().flatMapLatest { postsType -> val postsAfter = postsAfterInstantFromPostsType(postsType) - feedsPager(postsAfter) + feedsPager(postsAfter).cachedIn(coroutineScope) } observableSelectedFeed.selectedFeed @@ -250,14 +251,12 @@ class FeedsPresenter( rssRepository.searchFeed(searchQuery = transformedSearchQuery, postsAfter = postsAfter) } .flow - .cachedIn(coroutineScope) private fun feedsPager(postsAfter: Instant) = createPager(config = createPagingConfig(pageSize = 20)) { rssRepository.allFeeds(postsAfter = postsAfter) } .flow - .cachedIn(coroutineScope) private fun observeShowUnreadCountPreference() { settingsRepository.showUnreadPostsCount