From 3fc84d05fd463255a964f03074747539f9cdc401 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Mon, 20 Mar 2023 08:41:35 +0530 Subject: [PATCH 1/9] Improve relative time formatting in the notification activity. --- .../notifications/NotificationActivity.kt | 3 +-- .../notifications/NotificationViewModel.kt | 2 +- .../wikipedia/notifications/db/Notification.kt | 15 ++++++--------- app/src/main/java/org/wikipedia/util/DateUtil.kt | 9 +++++++++ 4 files changed, 17 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/org/wikipedia/notifications/NotificationActivity.kt b/app/src/main/java/org/wikipedia/notifications/NotificationActivity.kt index ec1c3f882b1..94d20cd07df 100644 --- a/app/src/main/java/org/wikipedia/notifications/NotificationActivity.kt +++ b/app/src/main/java/org/wikipedia/notifications/NotificationActivity.kt @@ -7,7 +7,6 @@ import android.graphics.Typeface import android.graphics.drawable.Drawable import android.net.Uri import android.os.Bundle -import android.text.format.DateUtils import android.view.Menu import android.view.MenuItem import android.view.View @@ -379,7 +378,7 @@ class NotificationActivity : BaseActivity() { } } - binding.notificationTime.text = DateUtils.getRelativeTimeSpanString(n.date().time, System.currentTimeMillis(), 0L) + binding.notificationTime.text = DateUtil.formatRelativeTime(n.instant()) binding.notificationTime.setTextColor(if (n.isUnread) primaryColor else inactiveColor) binding.notificationOverflowMenu.imageTintList = if (n.isUnread) primaryColor else inactiveColor diff --git a/app/src/main/java/org/wikipedia/notifications/NotificationViewModel.kt b/app/src/main/java/org/wikipedia/notifications/NotificationViewModel.kt index 6c2a6a30678..f0ad655b41d 100644 --- a/app/src/main/java/org/wikipedia/notifications/NotificationViewModel.kt +++ b/app/src/main/java/org/wikipedia/notifications/NotificationViewModel.kt @@ -56,7 +56,7 @@ class NotificationViewModel : ViewModel() { } } // Sort them by descending date... - notificationList.sortByDescending { it.date() } + notificationList.sortByDescending { it.instant() } // Filtered the tab selection val tabSelectedList = notificationList diff --git a/app/src/main/java/org/wikipedia/notifications/db/Notification.kt b/app/src/main/java/org/wikipedia/notifications/db/Notification.kt index b9f54f59421..f09cc6f3528 100644 --- a/app/src/main/java/org/wikipedia/notifications/db/Notification.kt +++ b/app/src/main/java/org/wikipedia/notifications/db/Notification.kt @@ -11,8 +11,8 @@ import org.wikipedia.Constants import org.wikipedia.dataclient.WikiSite import org.wikipedia.json.JsonUtil import org.wikipedia.page.Namespace -import org.wikipedia.util.DateUtil import org.wikipedia.util.UriUtil +import java.time.Instant import java.util.* @Serializable @@ -40,8 +40,8 @@ class Notification(var id: Long = 0, return id + wiki.hashCode() } - fun date(): Date { - return timestamp?.date() ?: Date() + fun instant(): Instant { + return timestamp?.instant ?: Instant.now() } override fun toString(): String { @@ -69,12 +69,9 @@ class Notification(var id: Long = 0, } @Serializable - class Timestamp { - - val utciso8601: String? = null - - fun date(): Date { - return DateUtil.iso8601DateParse(utciso8601!!) + class Timestamp(val utciso8601: String) { + val instant: Instant by lazy { + Instant.parse(utciso8601) } } diff --git a/app/src/main/java/org/wikipedia/util/DateUtil.kt b/app/src/main/java/org/wikipedia/util/DateUtil.kt index 51cc08bb813..1eaed237930 100644 --- a/app/src/main/java/org/wikipedia/util/DateUtil.kt +++ b/app/src/main/java/org/wikipedia/util/DateUtil.kt @@ -4,6 +4,7 @@ import android.content.Context import android.icu.text.RelativeDateTimeFormatter import android.os.Build import android.text.format.DateFormat +import android.text.format.DateUtils import org.wikipedia.R import org.wikipedia.WikipediaApp import org.wikipedia.feed.model.UtcDate @@ -15,6 +16,7 @@ import java.time.ZoneId import java.time.ZoneOffset import java.time.format.DateTimeFormatter import java.time.format.FormatStyle +import java.time.temporal.ChronoUnit import java.time.temporal.TemporalAccessor import java.util.* import java.util.concurrent.ConcurrentHashMap @@ -176,4 +178,11 @@ object DateUtil { else targetResource.getQuantityString(R.plurals.diff_years, diffInYears, diffInYears) } } + + fun formatRelativeTime(instant: Instant): CharSequence { + val localDate = LocalDateTime.ofInstant(instant, ZoneId.systemDefault()).toLocalDate() + val weeks = localDate.until(LocalDate.now(), ChronoUnit.WEEKS) + val flags = if (weeks in 1..10) DateUtils.WEEK_IN_MILLIS else 0L + return DateUtils.getRelativeTimeSpanString(instant.toEpochMilli(), System.currentTimeMillis(), flags) + } } From 5e72b1931ae2d3a086d408666a05aad11fcdd5d2 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Sun, 23 Apr 2023 05:27:46 +0530 Subject: [PATCH 2/9] Fix test issue. --- app/src/test/java/org/wikipedia/test/MockRetrofitTest.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/test/java/org/wikipedia/test/MockRetrofitTest.kt b/app/src/test/java/org/wikipedia/test/MockRetrofitTest.kt index c4169954da2..e510e2265a9 100644 --- a/app/src/test/java/org/wikipedia/test/MockRetrofitTest.kt +++ b/app/src/test/java/org/wikipedia/test/MockRetrofitTest.kt @@ -1,6 +1,7 @@ package org.wikipedia.test import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory +import kotlinx.serialization.ExperimentalSerializationApi import okhttp3.MediaType.Companion.toMediaType import org.junit.Before import org.wikipedia.dataclient.RestService @@ -17,6 +18,7 @@ abstract class MockRetrofitTest : MockWebServerTest() { private set protected val wikiSite = forLanguageCode("en") + @OptIn(ExperimentalSerializationApi::class) @Before @Throws(Throwable::class) override fun setUp() { From 0ed177debf46302f0e83d706a00458ecb075b9ad Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Mon, 24 Apr 2023 19:35:55 +0530 Subject: [PATCH 3/9] Fix variable name. --- app/src/main/java/org/wikipedia/util/DateUtil.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/wikipedia/util/DateUtil.kt b/app/src/main/java/org/wikipedia/util/DateUtil.kt index 1eaed237930..3607594169e 100644 --- a/app/src/main/java/org/wikipedia/util/DateUtil.kt +++ b/app/src/main/java/org/wikipedia/util/DateUtil.kt @@ -182,7 +182,7 @@ object DateUtil { fun formatRelativeTime(instant: Instant): CharSequence { val localDate = LocalDateTime.ofInstant(instant, ZoneId.systemDefault()).toLocalDate() val weeks = localDate.until(LocalDate.now(), ChronoUnit.WEEKS) - val flags = if (weeks in 1..10) DateUtils.WEEK_IN_MILLIS else 0L - return DateUtils.getRelativeTimeSpanString(instant.toEpochMilli(), System.currentTimeMillis(), flags) + val minResolution = if (weeks in 1..10) DateUtils.WEEK_IN_MILLIS else 0L + return DateUtils.getRelativeTimeSpanString(instant.toEpochMilli(), System.currentTimeMillis(), minResolution) } } From 4f1f76efe91ba984f334932dc14a527b35d88e75 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Sun, 24 Sep 2023 10:15:55 +0530 Subject: [PATCH 4/9] Add Instant serializer --- .../org/wikipedia/json/InstantSerializer.kt | 26 +++++++++++++++++++ .../notifications/NotificationActivity.kt | 1 + .../notifications/PollNotificationWorker.kt | 4 ++- .../notifications/db/Notification.kt | 10 ++----- .../main/java/org/wikipedia/settings/Prefs.kt | 10 ++++--- 5 files changed, 39 insertions(+), 12 deletions(-) create mode 100644 app/src/main/java/org/wikipedia/json/InstantSerializer.kt diff --git a/app/src/main/java/org/wikipedia/json/InstantSerializer.kt b/app/src/main/java/org/wikipedia/json/InstantSerializer.kt new file mode 100644 index 00000000000..76c705dd965 --- /dev/null +++ b/app/src/main/java/org/wikipedia/json/InstantSerializer.kt @@ -0,0 +1,26 @@ +package org.wikipedia.json + +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializable +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import java.time.Instant + +/** + * Serializes/deserializes [Instant] values from their ISO-8601 representations. + */ +object InstantSerializer : KSerializer { + override val descriptor = PrimitiveSerialDescriptor("Instant", PrimitiveKind.STRING) + + override fun deserialize(decoder: Decoder): Instant { + return Instant.parse(decoder.decodeString()) + } + + override fun serialize(encoder: Encoder, value: Instant) { + encoder.encodeString(value.toString()) + } +} + +typealias InstantAsString = @Serializable(InstantSerializer::class) Instant diff --git a/app/src/main/java/org/wikipedia/notifications/NotificationActivity.kt b/app/src/main/java/org/wikipedia/notifications/NotificationActivity.kt index 7a8db63fa7c..f11412eec41 100644 --- a/app/src/main/java/org/wikipedia/notifications/NotificationActivity.kt +++ b/app/src/main/java/org/wikipedia/notifications/NotificationActivity.kt @@ -50,6 +50,7 @@ import org.wikipedia.richtext.RichTextUtil import org.wikipedia.search.SearchFragment import org.wikipedia.settings.NotificationSettingsActivity import org.wikipedia.settings.Prefs +import org.wikipedia.util.DateUtil import org.wikipedia.util.DeviceUtil import org.wikipedia.util.DimenUtil import org.wikipedia.util.FeedbackUtil diff --git a/app/src/main/java/org/wikipedia/notifications/PollNotificationWorker.kt b/app/src/main/java/org/wikipedia/notifications/PollNotificationWorker.kt index 3d17899c9bc..061b2ada75e 100644 --- a/app/src/main/java/org/wikipedia/notifications/PollNotificationWorker.kt +++ b/app/src/main/java/org/wikipedia/notifications/PollNotificationWorker.kt @@ -10,6 +10,7 @@ import org.wikipedia.dataclient.WikiSite import org.wikipedia.dataclient.mwapi.MwException import org.wikipedia.settings.Prefs import org.wikipedia.util.log.L +import java.time.Instant class PollNotificationWorker( private val appContext: Context, @@ -18,7 +19,8 @@ class PollNotificationWorker( override suspend fun doWork(): Result { return try { val response = ServiceFactory.get(WikipediaApp.instance.wikiSite).lastUnreadNotification() - val lastNotificationTime = response.query?.notifications?.list?.maxOfOrNull { it.utcIso8601 }.orEmpty() + val lastNotificationTime = response.query?.notifications?.list?.maxOfOrNull { it.instant() } + ?: Instant.EPOCH if (lastNotificationTime > Prefs.remoteNotificationsSeenTime) { Prefs.remoteNotificationsSeenTime = lastNotificationTime retrieveNotifications() diff --git a/app/src/main/java/org/wikipedia/notifications/db/Notification.kt b/app/src/main/java/org/wikipedia/notifications/db/Notification.kt index f09cc6f3528..a15d6bbfe24 100644 --- a/app/src/main/java/org/wikipedia/notifications/db/Notification.kt +++ b/app/src/main/java/org/wikipedia/notifications/db/Notification.kt @@ -9,6 +9,7 @@ import kotlinx.serialization.json.decodeFromJsonElement import kotlinx.serialization.json.jsonPrimitive import org.wikipedia.Constants import org.wikipedia.dataclient.WikiSite +import org.wikipedia.json.InstantAsString import org.wikipedia.json.JsonUtil import org.wikipedia.page.Namespace import org.wikipedia.util.UriUtil @@ -28,9 +29,6 @@ class Notification(var id: Long = 0, var timestamp: Timestamp? = null, @SerialName("*") var contents: Contents? = null) { - val utcIso8601: String - get() = timestamp?.utciso8601.orEmpty() - val isFromWikidata: Boolean get() = wiki == Constants.WIKIDATA_DB_NAME @@ -69,11 +67,7 @@ class Notification(var id: Long = 0, } @Serializable - class Timestamp(val utciso8601: String) { - val instant: Instant by lazy { - Instant.parse(utciso8601) - } - } + class Timestamp(@SerialName("utciso8601") val instant: InstantAsString) @Serializable class Link { diff --git a/app/src/main/java/org/wikipedia/settings/Prefs.kt b/app/src/main/java/org/wikipedia/settings/Prefs.kt index de2fd5d392e..12f5ff0d28c 100644 --- a/app/src/main/java/org/wikipedia/settings/Prefs.kt +++ b/app/src/main/java/org/wikipedia/settings/Prefs.kt @@ -19,6 +19,7 @@ import org.wikipedia.util.DateUtil.dbDateParse import org.wikipedia.util.ReleaseUtil.isDevRelease import org.wikipedia.util.StringUtil import org.wikipedia.watchlist.WatchlistFilterTypes +import java.time.Instant import java.util.* /** Shared preferences utility for convenient POJO access. */ @@ -367,9 +368,12 @@ object Prefs { get() = PrefsIoUtil.getBoolean(R.string.preference_key_show_remove_chinese_variant_prompt, true) set(enabled) = PrefsIoUtil.setBoolean(R.string.preference_key_show_remove_chinese_variant_prompt, enabled) - var remoteNotificationsSeenTime - get() = PrefsIoUtil.getString(R.string.preference_key_remote_notifications_seen_time, "").orEmpty() - set(seenTime) = PrefsIoUtil.setString(R.string.preference_key_remote_notifications_seen_time, seenTime) + var remoteNotificationsSeenTime: Instant + get() { + val timestamp = PrefsIoUtil.getString(R.string.preference_key_remote_notifications_seen_time, "")!! + return if (timestamp.isEmpty()) Instant.EPOCH else Instant.parse(timestamp) + } + set(seenTime) = PrefsIoUtil.setString(R.string.preference_key_remote_notifications_seen_time, seenTime.toString()) var showHistoryOfflineArticlesToast get() = PrefsIoUtil.getBoolean(R.string.preference_key_history_offline_articles_toast, true) From 7f5ac4d47555147030de0ffed728c70784b4fab8 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Sun, 24 Sep 2023 10:41:04 +0530 Subject: [PATCH 5/9] Use LocalDate.ofInstant() --- app/src/main/java/org/wikipedia/util/DateUtil.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/wikipedia/util/DateUtil.kt b/app/src/main/java/org/wikipedia/util/DateUtil.kt index 3607594169e..86bf179066d 100644 --- a/app/src/main/java/org/wikipedia/util/DateUtil.kt +++ b/app/src/main/java/org/wikipedia/util/DateUtil.kt @@ -180,7 +180,7 @@ object DateUtil { } fun formatRelativeTime(instant: Instant): CharSequence { - val localDate = LocalDateTime.ofInstant(instant, ZoneId.systemDefault()).toLocalDate() + val localDate = LocalDate.ofInstant(instant, ZoneId.systemDefault()) val weeks = localDate.until(LocalDate.now(), ChronoUnit.WEEKS) val minResolution = if (weeks in 1..10) DateUtils.WEEK_IN_MILLIS else 0L return DateUtils.getRelativeTimeSpanString(instant.toEpochMilli(), System.currentTimeMillis(), minResolution) From c0f7ca4382a6257ac50e7dac369b7c6724e5dbe2 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Thu, 24 Oct 2024 05:31:13 +0530 Subject: [PATCH 6/9] Switch to Kotlinx Datetime --- app/build.gradle | 1 + .../org/wikipedia/json/InstantSerializer.kt | 26 ------------------- .../notifications/NotificationActivity.kt | 3 ++- .../notifications/PollNotificationWorker.kt | 4 +-- .../notifications/db/Notification.kt | 9 +++---- .../main/java/org/wikipedia/settings/Prefs.kt | 6 ++--- gradle/libs.versions.toml | 6 ++--- 7 files changed, 14 insertions(+), 41 deletions(-) delete mode 100644 app/src/main/java/org/wikipedia/json/InstantSerializer.kt diff --git a/app/build.gradle b/app/build.gradle index 0ba4be16638..f95ffffb4dc 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -177,6 +177,7 @@ dependencies { implementation libs.kotlinx.coroutines.core implementation libs.kotlinx.coroutines.android implementation libs.kotlinx.serialization.json + implementation libs.kotlinx.datetime implementation libs.material implementation libs.appcompat diff --git a/app/src/main/java/org/wikipedia/json/InstantSerializer.kt b/app/src/main/java/org/wikipedia/json/InstantSerializer.kt deleted file mode 100644 index 76c705dd965..00000000000 --- a/app/src/main/java/org/wikipedia/json/InstantSerializer.kt +++ /dev/null @@ -1,26 +0,0 @@ -package org.wikipedia.json - -import kotlinx.serialization.KSerializer -import kotlinx.serialization.Serializable -import kotlinx.serialization.descriptors.PrimitiveKind -import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.encoding.Encoder -import java.time.Instant - -/** - * Serializes/deserializes [Instant] values from their ISO-8601 representations. - */ -object InstantSerializer : KSerializer { - override val descriptor = PrimitiveSerialDescriptor("Instant", PrimitiveKind.STRING) - - override fun deserialize(decoder: Decoder): Instant { - return Instant.parse(decoder.decodeString()) - } - - override fun serialize(encoder: Encoder, value: Instant) { - encoder.encodeString(value.toString()) - } -} - -typealias InstantAsString = @Serializable(InstantSerializer::class) Instant diff --git a/app/src/main/java/org/wikipedia/notifications/NotificationActivity.kt b/app/src/main/java/org/wikipedia/notifications/NotificationActivity.kt index 59b89bb69f6..bfa76a0744b 100644 --- a/app/src/main/java/org/wikipedia/notifications/NotificationActivity.kt +++ b/app/src/main/java/org/wikipedia/notifications/NotificationActivity.kt @@ -36,6 +36,7 @@ import androidx.recyclerview.widget.RecyclerView import com.google.android.material.appbar.AppBarLayout import com.google.android.material.tabs.TabLayout import kotlinx.coroutines.launch +import kotlinx.datetime.toJavaInstant import org.wikipedia.Constants import org.wikipedia.R import org.wikipedia.WikipediaApp @@ -391,7 +392,7 @@ class NotificationActivity : BaseActivity() { } } - binding.notificationTime.text = DateUtil.formatRelativeTime(n.instant()) + binding.notificationTime.text = DateUtil.formatRelativeTime(n.instant().toJavaInstant()) binding.notificationTime.setTextColor(if (n.isUnread) primaryColor else inactiveColor) binding.notificationOverflowMenu.imageTintList = if (n.isUnread) primaryColor else inactiveColor diff --git a/app/src/main/java/org/wikipedia/notifications/PollNotificationWorker.kt b/app/src/main/java/org/wikipedia/notifications/PollNotificationWorker.kt index bc94ab586ec..17e733447af 100644 --- a/app/src/main/java/org/wikipedia/notifications/PollNotificationWorker.kt +++ b/app/src/main/java/org/wikipedia/notifications/PollNotificationWorker.kt @@ -7,6 +7,7 @@ import androidx.work.NetworkType import androidx.work.OneTimeWorkRequestBuilder import androidx.work.WorkManager import androidx.work.WorkerParameters +import kotlinx.datetime.Instant import org.wikipedia.WikipediaApp import org.wikipedia.csrf.CsrfTokenClient import org.wikipedia.dataclient.ServiceFactory @@ -14,7 +15,6 @@ import org.wikipedia.dataclient.WikiSite import org.wikipedia.dataclient.mwapi.MwException import org.wikipedia.settings.Prefs import org.wikipedia.util.log.L -import java.time.Instant class PollNotificationWorker( private val appContext: Context, @@ -24,7 +24,7 @@ class PollNotificationWorker( return try { val response = ServiceFactory.get(WikipediaApp.instance.wikiSite).lastUnreadNotification() val lastNotificationTime = response.query?.notifications?.list?.maxOfOrNull { it.instant() } - ?: Instant.EPOCH + ?: Instant.fromEpochMilliseconds(0) if (lastNotificationTime > Prefs.remoteNotificationsSeenTime) { Prefs.remoteNotificationsSeenTime = lastNotificationTime retrieveNotifications() diff --git a/app/src/main/java/org/wikipedia/notifications/db/Notification.kt b/app/src/main/java/org/wikipedia/notifications/db/Notification.kt index a15d6bbfe24..570c4d2fc81 100644 --- a/app/src/main/java/org/wikipedia/notifications/db/Notification.kt +++ b/app/src/main/java/org/wikipedia/notifications/db/Notification.kt @@ -1,6 +1,8 @@ package org.wikipedia.notifications.db import androidx.room.Entity +import kotlinx.datetime.Clock +import kotlinx.datetime.Instant import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.json.JsonElement @@ -9,12 +11,9 @@ import kotlinx.serialization.json.decodeFromJsonElement import kotlinx.serialization.json.jsonPrimitive import org.wikipedia.Constants import org.wikipedia.dataclient.WikiSite -import org.wikipedia.json.InstantAsString import org.wikipedia.json.JsonUtil import org.wikipedia.page.Namespace import org.wikipedia.util.UriUtil -import java.time.Instant -import java.util.* @Serializable @Entity(primaryKeys = ["id", "wiki"]) @@ -39,7 +38,7 @@ class Notification(var id: Long = 0, } fun instant(): Instant { - return timestamp?.instant ?: Instant.now() + return timestamp?.instant ?: Clock.System.now() } override fun toString(): String { @@ -67,7 +66,7 @@ class Notification(var id: Long = 0, } @Serializable - class Timestamp(@SerialName("utciso8601") val instant: InstantAsString) + class Timestamp(@SerialName("utciso8601") val instant: Instant) @Serializable class Link { diff --git a/app/src/main/java/org/wikipedia/settings/Prefs.kt b/app/src/main/java/org/wikipedia/settings/Prefs.kt index 4e2f0528c3b..a04fb1e13ae 100644 --- a/app/src/main/java/org/wikipedia/settings/Prefs.kt +++ b/app/src/main/java/org/wikipedia/settings/Prefs.kt @@ -1,6 +1,7 @@ package org.wikipedia.settings import android.location.Location +import kotlinx.datetime.Instant import okhttp3.Cookie import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import okhttp3.logging.HttpLoggingInterceptor @@ -23,8 +24,7 @@ import org.wikipedia.util.DateUtil.dbDateParse import org.wikipedia.util.ReleaseUtil.isDevRelease import org.wikipedia.util.StringUtil import org.wikipedia.watchlist.WatchlistFilterTypes -import java.time.Instant -import java.util.* +import java.util.Date /** Shared preferences utility for convenient POJO access. */ object Prefs { @@ -369,7 +369,7 @@ object Prefs { var remoteNotificationsSeenTime: Instant get() { val timestamp = PrefsIoUtil.getString(R.string.preference_key_remote_notifications_seen_time, "")!! - return if (timestamp.isEmpty()) Instant.EPOCH else Instant.parse(timestamp) + return if (timestamp.isEmpty()) Instant.fromEpochMilliseconds(0) else Instant.parse(timestamp) } set(seenTime) = PrefsIoUtil.setString(R.string.preference_key_remote_notifications_seen_time, seenTime.toString()) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5de01eb8cd6..b89d198a7d5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -45,6 +45,7 @@ swiperefreshlayout = "1.1.0" uiautomator = "2.3.0" viewpager2 = "1.1.0" workRuntimeKtx = "2.9.1" +kotlinxDatetime = "0.6.1" [libraries] android-sdk = { module = "org.maplibre.gl:android-sdk", version.ref = "androidSdk" } @@ -74,17 +75,14 @@ flexbox = { module = "com.google.android.flexbox:flexbox", version.ref = "flexbo fragment-ktx = { module = "androidx.fragment:fragment-ktx", version.ref = "fragmentKtx" } glide = { module = "com.github.bumptech.glide:glide", version.ref = "glideVersion" } glide-ksp = { module = "com.github.bumptech.glide:ksp", version.ref = "glideVersion" } -google-services = { module = "com.google.gms:google-services", version.ref = "googleServices" } -gradle = { module = "com.android.tools.build:gradle", version.ref = "gradle" } hamcrest = { module = "org.hamcrest:hamcrest", version.ref = "hamcrest" } installreferrer = { module = "com.android.installreferrer:installreferrer", version.ref = "installreferrer" } jsoup = { module = "org.jsoup:jsoup", version.ref = "jsoup" } junit = { module = "junit:junit", version.ref = "junit" } -kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlinStdlibJdk8" } -kotlin-serialization = { module = "org.jetbrains.kotlin:kotlin-serialization", version.ref = "kotlinStdlibJdk8" } kotlin-stdlib-jdk8 = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8", version.ref = "kotlinStdlibJdk8" } kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinCoroutinesVersion" } kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinCoroutinesVersion" } +kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinxDatetime" } kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" } leakcanary-android = { module = "com.squareup.leakcanary:leakcanary-android", version.ref = "leakCanaryVersion" } material = { module = "com.google.android.material:material", version.ref = "material" } From 88e9838d03d0ca250511b71690d5c6142a7038cc Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Wed, 23 Jul 2025 07:41:52 +0530 Subject: [PATCH 7/9] Fix import --- app/build.gradle | 1 - .../org/wikipedia/notifications/NotificationActivity.kt | 4 +++- .../org/wikipedia/notifications/NotificationViewModel.kt | 2 ++ .../org/wikipedia/notifications/PollNotificationWorker.kt | 4 +++- .../java/org/wikipedia/notifications/db/Notification.kt | 7 +++++-- app/src/main/java/org/wikipedia/settings/Prefs.kt | 7 +++++-- gradle/libs.versions.toml | 2 -- 7 files changed, 18 insertions(+), 9 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index eeee6323fb7..f10833748b3 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -179,7 +179,6 @@ dependencies { implementation libs.kotlinx.coroutines.core implementation libs.kotlinx.coroutines.android implementation libs.kotlinx.serialization.json - implementation libs.kotlinx.datetime implementation libs.material implementation libs.appcompat diff --git a/app/src/main/java/org/wikipedia/notifications/NotificationActivity.kt b/app/src/main/java/org/wikipedia/notifications/NotificationActivity.kt index f823d87b16d..fdb4b5ea54f 100644 --- a/app/src/main/java/org/wikipedia/notifications/NotificationActivity.kt +++ b/app/src/main/java/org/wikipedia/notifications/NotificationActivity.kt @@ -36,7 +36,6 @@ import androidx.recyclerview.widget.RecyclerView import com.google.android.material.appbar.AppBarLayout import com.google.android.material.tabs.TabLayout import kotlinx.coroutines.launch -import kotlinx.datetime.toJavaInstant import org.wikipedia.Constants import org.wikipedia.R import org.wikipedia.WikipediaApp @@ -65,6 +64,8 @@ import org.wikipedia.views.NotificationActionsOverflowView import org.wikipedia.views.SearchAndFilterActionProvider import org.wikipedia.views.SwipeableItemTouchHelperCallback import org.wikipedia.views.WikiCardView +import kotlin.time.ExperimentalTime +import kotlin.time.toJavaInstant class NotificationActivity : BaseActivity() { private lateinit var binding: ActivityNotificationsBinding @@ -391,6 +392,7 @@ class NotificationActivity : BaseActivity() { } } + @OptIn(ExperimentalTime::class) binding.notificationTime.text = DateUtil.formatRelativeTime(n.instant().toJavaInstant()) binding.notificationTime.setTextColor(if (n.isUnread) primaryColor else inactiveColor) binding.notificationOverflowMenu.imageTintList = if (n.isUnread) primaryColor else inactiveColor diff --git a/app/src/main/java/org/wikipedia/notifications/NotificationViewModel.kt b/app/src/main/java/org/wikipedia/notifications/NotificationViewModel.kt index 4ca850eccaa..947d42b5d07 100644 --- a/app/src/main/java/org/wikipedia/notifications/NotificationViewModel.kt +++ b/app/src/main/java/org/wikipedia/notifications/NotificationViewModel.kt @@ -16,6 +16,7 @@ import org.wikipedia.util.Resource import org.wikipedia.util.StringUtil import java.util.Date import java.util.Random +import kotlin.time.ExperimentalTime class NotificationViewModel : ViewModel() { @@ -47,6 +48,7 @@ class NotificationViewModel : ViewModel() { _uiState.value = Resource.Success(pair) } + @OptIn(ExperimentalTime::class) private fun processList(list: List): List { if (currentContinueStr.isNullOrEmpty()) { notificationList.clear() diff --git a/app/src/main/java/org/wikipedia/notifications/PollNotificationWorker.kt b/app/src/main/java/org/wikipedia/notifications/PollNotificationWorker.kt index 37226738033..5d7c25e9297 100644 --- a/app/src/main/java/org/wikipedia/notifications/PollNotificationWorker.kt +++ b/app/src/main/java/org/wikipedia/notifications/PollNotificationWorker.kt @@ -7,7 +7,6 @@ import androidx.work.NetworkType import androidx.work.OneTimeWorkRequestBuilder import androidx.work.WorkManager import androidx.work.WorkerParameters -import kotlinx.datetime.Instant import org.wikipedia.WikipediaApp import org.wikipedia.csrf.CsrfTokenClient import org.wikipedia.dataclient.ServiceFactory @@ -15,11 +14,14 @@ import org.wikipedia.dataclient.WikiSite import org.wikipedia.dataclient.mwapi.MwException import org.wikipedia.settings.Prefs import org.wikipedia.util.log.L +import kotlin.time.ExperimentalTime +import kotlin.time.Instant class PollNotificationWorker( private val appContext: Context, params: WorkerParameters ) : CoroutineWorker(appContext, params) { + @OptIn(ExperimentalTime::class) override suspend fun doWork(): Result { return try { val response = ServiceFactory.get(WikipediaApp.instance.wikiSite).lastUnreadNotification() diff --git a/app/src/main/java/org/wikipedia/notifications/db/Notification.kt b/app/src/main/java/org/wikipedia/notifications/db/Notification.kt index 570c4d2fc81..f1181e8684d 100644 --- a/app/src/main/java/org/wikipedia/notifications/db/Notification.kt +++ b/app/src/main/java/org/wikipedia/notifications/db/Notification.kt @@ -1,8 +1,6 @@ package org.wikipedia.notifications.db import androidx.room.Entity -import kotlinx.datetime.Clock -import kotlinx.datetime.Instant import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.json.JsonElement @@ -14,6 +12,9 @@ import org.wikipedia.dataclient.WikiSite import org.wikipedia.json.JsonUtil import org.wikipedia.page.Namespace import org.wikipedia.util.UriUtil +import kotlin.time.Clock +import kotlin.time.ExperimentalTime +import kotlin.time.Instant @Serializable @Entity(primaryKeys = ["id", "wiki"]) @@ -37,6 +38,7 @@ class Notification(var id: Long = 0, return id + wiki.hashCode() } + @OptIn(ExperimentalTime::class) fun instant(): Instant { return timestamp?.instant ?: Clock.System.now() } @@ -66,6 +68,7 @@ class Notification(var id: Long = 0, } @Serializable + @OptIn(ExperimentalTime::class) class Timestamp(@SerialName("utciso8601") val instant: Instant) @Serializable diff --git a/app/src/main/java/org/wikipedia/settings/Prefs.kt b/app/src/main/java/org/wikipedia/settings/Prefs.kt index dec2599d0c8..f6e140ddde2 100644 --- a/app/src/main/java/org/wikipedia/settings/Prefs.kt +++ b/app/src/main/java/org/wikipedia/settings/Prefs.kt @@ -1,7 +1,6 @@ package org.wikipedia.settings import android.location.Location -import kotlinx.datetime.Instant import okhttp3.Cookie import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import okhttp3.logging.HttpLoggingInterceptor @@ -29,6 +28,9 @@ import org.wikipedia.util.ReleaseUtil.isDevRelease import org.wikipedia.util.StringUtil import org.wikipedia.watchlist.WatchlistFilterTypes import java.util.Date +import kotlin.time.Clock +import kotlin.time.ExperimentalTime +import kotlin.time.Instant /** Shared preferences utility for convenient POJO access. */ object Prefs { @@ -374,10 +376,11 @@ object Prefs { get() = PrefsIoUtil.getBoolean(R.string.preference_key_reading_lists_first_time_sync, true) set(value) = PrefsIoUtil.setBoolean(R.string.preference_key_reading_lists_first_time_sync, value) + @OptIn(ExperimentalTime::class) var remoteNotificationsSeenTime: Instant get() { val timestamp = PrefsIoUtil.getString(R.string.preference_key_remote_notifications_seen_time, "")!! - return if (timestamp.isEmpty()) Instant.fromEpochMilliseconds(0) else Instant.parse(timestamp) + return Instant.parseOrNull(timestamp) ?: Clock.System.now() } set(seenTime) = PrefsIoUtil.setString(R.string.preference_key_remote_notifications_seen_time, seenTime.toString()) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4da974383bd..350a0b407eb 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -46,7 +46,6 @@ swiperefreshlayout = "1.1.0" uiautomator = "2.3.0" viewpager2 = "1.1.0" workRuntimeKtx = "2.10.2" -kotlinxDatetime = "0.7.1" composeBom = "2025.07.00" composeActivity = "1.10.1" composeViewModel = "2.9.2" @@ -91,7 +90,6 @@ junit = { module = "junit:junit", version.ref = "junit" } kotlin-stdlib-jdk8 = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8", version.ref = "kotlinStdlibJdk8" } kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinCoroutinesVersion" } kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinCoroutinesVersion" } -kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinxDatetime" } kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" } leakcanary-android = { module = "com.squareup.leakcanary:leakcanary-android", version.ref = "leakCanaryVersion" } material = { module = "com.google.android.material:material", version.ref = "material" } From 07c86cb82e620a093ec28a106ae6f7b530b30a94 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Wed, 23 Jul 2025 08:14:31 +0530 Subject: [PATCH 8/9] Use Instant.DISTANT_PAST --- .../java/org/wikipedia/notifications/PollNotificationWorker.kt | 2 +- app/src/main/java/org/wikipedia/settings/Prefs.kt | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/wikipedia/notifications/PollNotificationWorker.kt b/app/src/main/java/org/wikipedia/notifications/PollNotificationWorker.kt index 5d7c25e9297..554d85d4f8a 100644 --- a/app/src/main/java/org/wikipedia/notifications/PollNotificationWorker.kt +++ b/app/src/main/java/org/wikipedia/notifications/PollNotificationWorker.kt @@ -26,7 +26,7 @@ class PollNotificationWorker( return try { val response = ServiceFactory.get(WikipediaApp.instance.wikiSite).lastUnreadNotification() val lastNotificationTime = response.query?.notifications?.list?.maxOfOrNull { it.instant() } - ?: Instant.fromEpochMilliseconds(0) + ?: Instant.DISTANT_PAST if (lastNotificationTime > Prefs.remoteNotificationsSeenTime) { Prefs.remoteNotificationsSeenTime = lastNotificationTime retrieveNotifications() diff --git a/app/src/main/java/org/wikipedia/settings/Prefs.kt b/app/src/main/java/org/wikipedia/settings/Prefs.kt index f6e140ddde2..a42bdb35644 100644 --- a/app/src/main/java/org/wikipedia/settings/Prefs.kt +++ b/app/src/main/java/org/wikipedia/settings/Prefs.kt @@ -28,7 +28,6 @@ import org.wikipedia.util.ReleaseUtil.isDevRelease import org.wikipedia.util.StringUtil import org.wikipedia.watchlist.WatchlistFilterTypes import java.util.Date -import kotlin.time.Clock import kotlin.time.ExperimentalTime import kotlin.time.Instant @@ -380,7 +379,7 @@ object Prefs { var remoteNotificationsSeenTime: Instant get() { val timestamp = PrefsIoUtil.getString(R.string.preference_key_remote_notifications_seen_time, "")!! - return Instant.parseOrNull(timestamp) ?: Clock.System.now() + return Instant.parseOrNull(timestamp) ?: Instant.DISTANT_PAST } set(seenTime) = PrefsIoUtil.setString(R.string.preference_key_remote_notifications_seen_time, seenTime.toString()) From 39106806858be3d612e3bb1217605a7fcb7f9da1 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Wed, 23 Jul 2025 08:59:24 +0530 Subject: [PATCH 9/9] Remove unused methods --- app/src/main/java/org/wikipedia/util/DateUtil.kt | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/app/src/main/java/org/wikipedia/util/DateUtil.kt b/app/src/main/java/org/wikipedia/util/DateUtil.kt index 46c32d7f7cf..c5538dc3cb3 100644 --- a/app/src/main/java/org/wikipedia/util/DateUtil.kt +++ b/app/src/main/java/org/wikipedia/util/DateUtil.kt @@ -16,7 +16,6 @@ import java.time.LocalDate import java.time.LocalDateTime import java.time.ZoneId import java.time.ZoneOffset -import java.time.ZonedDateTime import java.time.format.DateTimeFormatter import java.time.format.FormatStyle import java.time.temporal.ChronoUnit @@ -191,21 +190,6 @@ object DateUtil { } } - fun startOfYearInMillis(year: Int, zoneId: ZoneId = ZoneId.systemDefault()): Long { - val localDate = LocalDate.of(year, 1, 1) - return localDate.atStartOfDay(zoneId).toInstant().toEpochMilli() - } - - fun endOfYearInMillis(year: Int, zoneId: ZoneId = ZoneId.systemDefault()): Long { - val localDate = LocalDate.of(year, 12, 31) - return localDate.atTime(0, 0, 0).atZone(zoneId).toInstant().toEpochMilli() - } - - fun epochMilliToYear(epochMilli: Long, zoneId: ZoneId = ZoneId.systemDefault()): Int { - val zonedDateTime = ZonedDateTime.ofInstant(Instant.ofEpochMilli(epochMilli), zoneId) - return zonedDateTime.year - } - fun formatRelativeTime(instant: Instant): CharSequence { val localDate = LocalDate.ofInstant(instant, ZoneId.systemDefault()) val weeks = localDate.until(LocalDate.now(), ChronoUnit.WEEKS)