diff --git a/app/src/main/java/org/wikipedia/notifications/NotificationActivity.kt b/app/src/main/java/org/wikipedia/notifications/NotificationActivity.kt index 0d72dc3cb27..fdb4b5ea54f 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 @@ -50,6 +49,7 @@ import org.wikipedia.page.LinkMovementMethodExt import org.wikipedia.richtext.RichTextUtil 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 @@ -64,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 @@ -390,7 +392,8 @@ class NotificationActivity : BaseActivity() { } } - binding.notificationTime.text = DateUtils.getRelativeTimeSpanString(n.date().time, System.currentTimeMillis(), 0L) + @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 2a999c3d11b..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() @@ -57,7 +59,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/PollNotificationWorker.kt b/app/src/main/java/org/wikipedia/notifications/PollNotificationWorker.kt index 108a70e75a8..554d85d4f8a 100644 --- a/app/src/main/java/org/wikipedia/notifications/PollNotificationWorker.kt +++ b/app/src/main/java/org/wikipedia/notifications/PollNotificationWorker.kt @@ -14,15 +14,19 @@ 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() - val lastNotificationTime = response.query?.notifications?.list?.maxOfOrNull { it.utcIso8601 }.orEmpty() + val lastNotificationTime = response.query?.notifications?.list?.maxOfOrNull { it.instant() } + ?: Instant.DISTANT_PAST 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 b9f54f59421..f1181e8684d 100644 --- a/app/src/main/java/org/wikipedia/notifications/db/Notification.kt +++ b/app/src/main/java/org/wikipedia/notifications/db/Notification.kt @@ -11,9 +11,10 @@ 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.util.* +import kotlin.time.Clock +import kotlin.time.ExperimentalTime +import kotlin.time.Instant @Serializable @Entity(primaryKeys = ["id", "wiki"]) @@ -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 @@ -40,8 +38,9 @@ class Notification(var id: Long = 0, return id + wiki.hashCode() } - fun date(): Date { - return timestamp?.date() ?: Date() + @OptIn(ExperimentalTime::class) + fun instant(): Instant { + return timestamp?.instant ?: Clock.System.now() } override fun toString(): String { @@ -69,14 +68,8 @@ class Notification(var id: Long = 0, } @Serializable - class Timestamp { - - val utciso8601: String? = null - - fun date(): Date { - return DateUtil.iso8601DateParse(utciso8601!!) - } - } + @OptIn(ExperimentalTime::class) + 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 d8d17369bf1..a42bdb35644 100644 --- a/app/src/main/java/org/wikipedia/settings/Prefs.kt +++ b/app/src/main/java/org/wikipedia/settings/Prefs.kt @@ -28,6 +28,8 @@ import org.wikipedia.util.ReleaseUtil.isDevRelease import org.wikipedia.util.StringUtil import org.wikipedia.watchlist.WatchlistFilterTypes import java.util.Date +import kotlin.time.ExperimentalTime +import kotlin.time.Instant /** Shared preferences utility for convenient POJO access. */ object Prefs { @@ -373,9 +375,13 @@ 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) - 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) + @OptIn(ExperimentalTime::class) + var remoteNotificationsSeenTime: Instant + get() { + val timestamp = PrefsIoUtil.getString(R.string.preference_key_remote_notifications_seen_time, "")!! + return Instant.parseOrNull(timestamp) ?: Instant.DISTANT_PAST + } + 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) diff --git a/app/src/main/java/org/wikipedia/util/DateUtil.kt b/app/src/main/java/org/wikipedia/util/DateUtil.kt index cb32ceb6d51..c5538dc3cb3 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.extensions.getResources @@ -15,9 +16,9 @@ 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 import java.time.temporal.TemporalAccessor import java.util.Calendar import java.util.Date @@ -189,18 +190,10 @@ 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) + val minResolution = if (weeks in 1..10) DateUtils.WEEK_IN_MILLIS else 0L + return DateUtils.getRelativeTimeSpanString(instant.toEpochMilli(), System.currentTimeMillis(), minResolution) } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 37d1b6574fd..350a0b407eb 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -50,7 +50,6 @@ composeBom = "2025.07.00" composeActivity = "1.10.1" composeViewModel = "2.9.2" - [libraries] android-sdk = { module = "org.maplibre.gl:android-sdk", version.ref = "androidSdk" } android-plugin-annotation-v9 = { module = "org.maplibre.gl:android-plugin-annotation-v9", version.ref = "androidPluginAnnotationV9" } @@ -88,8 +87,6 @@ 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" }