diff --git a/app/src/main/java/org/wikipedia/dataclient/mwapi/MwQueryPage.kt b/app/src/main/java/org/wikipedia/dataclient/mwapi/MwQueryPage.kt index 368e27453f9..6b5ba3b828b 100644 --- a/app/src/main/java/org/wikipedia/dataclient/mwapi/MwQueryPage.kt +++ b/app/src/main/java/org/wikipedia/dataclient/mwapi/MwQueryPage.kt @@ -11,7 +11,8 @@ import org.wikipedia.dataclient.page.Protection import org.wikipedia.gallery.ImageInfo import org.wikipedia.json.JsonUtil import org.wikipedia.page.Namespace -import org.wikipedia.util.DateUtil +import kotlin.time.ExperimentalTime +import kotlin.time.Instant @Serializable class MwQueryPage { @@ -88,20 +89,21 @@ class MwQueryPage { } @Serializable - class Revision { - private val slots: Map? = null - val minor = false - @SerialName("revid") val revId: Long = 0 - @SerialName("parentid") val parentRevId: Long = 0 - @SerialName("anon") val isAnon = false - @SerialName("temp") val isTemp = false - @SerialName("timestamp") val timeStamp: String = "" - val size = 0 - val user: String = "" - val comment: String = "" - val parsedcomment: String = "" - - private val oresscores: JsonElement? = null + @OptIn(ExperimentalTime::class) + class Revision( + private val slots: Map? = null, + val minor: Boolean = false, + @SerialName("revid") val revId: Long = 0, + @SerialName("parentid") val parentRevId: Long = 0, + @SerialName("anon") val isAnon: Boolean = false, + @SerialName("temp") val isTemp: Boolean = false, + val timestamp: Instant, + val size: Int = 0, + val user: String = "", + val comment: String = "", + val parsedcomment: String = "", + private val oresscores: JsonElement? = null, + ) { val ores: MwQueryResult.OresResult? get() = if (oresscores != null && oresscores !is JsonArray) { JsonUtil.json.decodeFromJsonElement(oresscores) @@ -111,8 +113,6 @@ class MwQueryPage { var diffSize = 0 - val localDateTime by lazy { DateUtil.iso8601LocalDateTimeParse(timeStamp) } - fun getContentFromSlot(slot: String): String { return slots?.get(slot)?.content.orEmpty() } diff --git a/app/src/main/java/org/wikipedia/dataclient/mwapi/MwQueryResult.kt b/app/src/main/java/org/wikipedia/dataclient/mwapi/MwQueryResult.kt index 428134591f7..2af77e55d95 100644 --- a/app/src/main/java/org/wikipedia/dataclient/mwapi/MwQueryResult.kt +++ b/app/src/main/java/org/wikipedia/dataclient/mwapi/MwQueryResult.kt @@ -12,14 +12,12 @@ import org.wikipedia.notifications.db.Notification.SeenTime import org.wikipedia.notifications.db.Notification.UnreadNotificationWikiItem import org.wikipedia.page.PageTitle import org.wikipedia.settings.SiteInfo -import org.wikipedia.util.DateUtil import org.wikipedia.util.StringUtil -import java.time.Instant -import java.time.LocalDateTime -import java.time.ZoneId -import java.util.* +import kotlin.time.ExperimentalTime +import kotlin.time.Instant @Serializable +@OptIn(ExperimentalTime::class) class MwQueryResult { @SerialName("userinfo") val userInfo: UserInfo? = null @@ -167,58 +165,51 @@ class MwQueryResult { @SerialName("continue") val continueStr: String? = null) @Serializable - class WatchlistItem { - - @SerialName("new") val isNew = false - @SerialName("anon") val isAnon = false - @SerialName("minor") val isMinor = false - @SerialName("bot") val isBot = false - @SerialName("old_revid") private val oldRevid: Long = 0 - private val timestamp: String? = null - private val comment: String? = null - val type: String = "" - @SerialName("pageid") val pageId = 0 - val revid: Long = 0 - val ns = 0 - val title: String = "" - val user: String = "" - val logtype: String = "" - val logdisplay: String = "" - val oldlen = 0 - val newlen = 0 - var wiki: WikiSite? = null - @SerialName("parsedcomment") val parsedComment: String = "" - val date: Date - get() = DateUtil.iso8601DateParse(timestamp.orEmpty()) - } + class WatchlistItem( + @SerialName("new") val isNew: Boolean = false, + @SerialName("anon") val isAnon: Boolean = false, + @SerialName("minor") val isMinor: Boolean = false, + @SerialName("bot") val isBot: Boolean = false, + @SerialName("old_revid") private val oldRevid: Long = 0, + val timestamp: Instant, + private val comment: String? = null, + val type: String = "", + @SerialName("pageid") val pageId: Int = 0, + val revid: Long = 0, + val ns: Int = 0, + val title: String = "", + val user: String = "", + val logtype: String = "", + val logdisplay: String = "", + val oldlen: Int = 0, + val newlen: Int = 0, + var wiki: WikiSite? = null, + @SerialName("parsedcomment") val parsedComment: String = "", + ) @Serializable - class RecentChange { - private val type: String = "" - private val ns = 0 - val title: String = "" - val pageid: Int = 0 - @SerialName("revid") val curRev: Long = 0 - @SerialName("old_revid") val revFrom: Long = 0 - val rcid: Long = 0 - val user: String = "" - val anon = false - val bot = false - - @SerialName("new") private val isNew = false - private val minor = false - val oldlen = 0 - val newlen = 0 - private val timestamp: String = "" - - @SerialName("parsedcomment") val parsedComment: String = "" - private val tags: List? = null - private val oresscores: JsonElement? = null - - val parsedInstant: Instant by lazy { Instant.parse(timestamp) } - val parsedDateTime: LocalDateTime by lazy { - LocalDateTime.ofInstant(parsedInstant, ZoneId.systemDefault()) - } + class RecentChange( + private val type: String = "", + private val ns: Int = 0, + val title: String = "", + val pageid: Int = 0, + @SerialName("revid") val curRev: Long = 0, + @SerialName("old_revid") val revFrom: Long = 0, + val rcid: Long = 0, + val user: String = "", + val anon: Boolean = false, + val bot: Boolean = false, + + @SerialName("new") private val isNew: Boolean = false, + private val minor: Boolean = false, + val oldlen: Int = 0, + val newlen: Int = 0, + val timestamp: Instant, + + @SerialName("parsedcomment") val parsedComment: String = "", + private val tags: List? = null, + private val oresscores: JsonElement? = null, + ) { val joinedTags by lazy { tags?.joinToString(separator = ", ").orEmpty() } override fun toString(): String { diff --git a/app/src/main/java/org/wikipedia/dataclient/mwapi/UserContribution.kt b/app/src/main/java/org/wikipedia/dataclient/mwapi/UserContribution.kt index a3e5ae6e013..73c03ff68d8 100644 --- a/app/src/main/java/org/wikipedia/dataclient/mwapi/UserContribution.kt +++ b/app/src/main/java/org/wikipedia/dataclient/mwapi/UserContribution.kt @@ -1,25 +1,25 @@ package org.wikipedia.dataclient.mwapi import kotlinx.serialization.Serializable -import org.wikipedia.util.DateUtil +import kotlin.time.ExperimentalTime +import kotlin.time.Instant @Serializable -class UserContribution { - val userid: Int = 0 - val user: String = "" - val pageid: Int = 0 - val revid: Long = 0 - val parentid: Long = 0 - val ns: Int = 0 - val title: String = "" - private val timestamp: String = "" - val comment: String = "" - val new: Boolean = false - val minor: Boolean = false - val top: Boolean = false - val size: Int = 0 - val sizediff: Int = 0 - val tags: List = emptyList() - - val parsedDateTime by lazy { DateUtil.iso8601LocalDateTimeParse(timestamp) } -} +@OptIn(ExperimentalTime::class) +class UserContribution( + val userid: Int = 0, + val user: String = "", + val pageid: Int = 0, + val revid: Long = 0, + val parentid: Long = 0, + val ns: Int = 0, + val title: String = "", + val timestamp: Instant, + val comment: String = "", + val new: Boolean = false, + val minor: Boolean = false, + val top: Boolean = false, + val size: Int = 0, + val sizediff: Int = 0, + val tags: List = emptyList(), +) diff --git a/app/src/main/java/org/wikipedia/dataclient/mwapi/UserInfo.kt b/app/src/main/java/org/wikipedia/dataclient/mwapi/UserInfo.kt index 1e95ccc4899..d4e33353b2f 100644 --- a/app/src/main/java/org/wikipedia/dataclient/mwapi/UserInfo.kt +++ b/app/src/main/java/org/wikipedia/dataclient/mwapi/UserInfo.kt @@ -4,16 +4,20 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.json.JsonElement import org.wikipedia.dataclient.mwapi.MwServiceError.BlockInfo -import org.wikipedia.util.DateUtil -import java.util.* +import java.time.LocalDate +import java.time.ZoneId +import kotlin.time.ExperimentalTime +import kotlin.time.Instant +import kotlin.time.toJavaInstant @Serializable +@OptIn(ExperimentalTime::class) class UserInfo : BlockInfo() { val id = 0 private val groups: List? = null - @SerialName("latestcontrib") private val latestContrib: String? = null - @SerialName("registrationdate") private val regDate: String? = null - @SerialName("registration") private val registration: String? = null + @SerialName("latestcontrib") private val latestContrib: Instant = Instant.DISTANT_PAST + @SerialName("registrationdate") private val regInstant: Instant? = null + private val registration: Instant? = null @SerialName("editcount") val editCount = -1 val name: String = "" val anon: Boolean = false @@ -30,25 +34,14 @@ class UserInfo : BlockInfo() { return groups?.toSet() ?: emptySet() } - val latestContribDate: Date - get() { - var date = Date(0) - if (!latestContrib.isNullOrEmpty()) { - date = DateUtil.iso8601DateParse(latestContrib) - } - return date - } + val latestContribDate: LocalDate by lazy { + LocalDate.ofInstant(latestContrib.toJavaInstant(), ZoneId.systemDefault()) + } - val registrationDate: Date - get() { - var date = Date(0) - if (!regDate.isNullOrEmpty()) { - date = DateUtil.iso8601DateParse(regDate) - } else if (!registration.isNullOrEmpty()) { - date = DateUtil.iso8601DateParse(registration) - } - return date - } + val registrationDate: LocalDate by lazy { + val instant = regInstant ?: registration ?: Instant.DISTANT_PAST + LocalDate.ofInstant(instant.toJavaInstant(), ZoneId.systemDefault()) + } @Serializable class Options { diff --git a/app/src/main/java/org/wikipedia/diff/ArticleEditDetailsFragment.kt b/app/src/main/java/org/wikipedia/diff/ArticleEditDetailsFragment.kt index 79eb8b26aed..c26db0a19ec 100644 --- a/app/src/main/java/org/wikipedia/diff/ArticleEditDetailsFragment.kt +++ b/app/src/main/java/org/wikipedia/diff/ArticleEditDetailsFragment.kt @@ -67,6 +67,7 @@ import org.wikipedia.util.log.L import org.wikipedia.views.SurveyDialog import org.wikipedia.watchlist.WatchlistExpiry import org.wikipedia.watchlist.WatchlistExpiryDialog +import kotlin.time.ExperimentalTime class ArticleEditDetailsFragment : Fragment(), WatchlistExpiryDialog.Callback, MenuProvider { interface Callback { @@ -447,12 +448,13 @@ class ArticleEditDetailsFragment : Fragment(), WatchlistExpiryDialog.Callback, M binding.newerIdButton.isVisible = false } + @OptIn(ExperimentalTime::class) private fun updateAfterRevisionFetchSuccess() { binding.articleTitleView.text = StringUtil.fromHtml(viewModel.pageTitle.displayText) if (viewModel.revisionFrom != null) { binding.usernameFromButton.text = viewModel.revisionFrom!!.user - binding.revisionFromTimestamp.text = DateUtil.getTimeAndDateString(requireContext(), viewModel.revisionFrom!!.timeStamp) + binding.revisionFromTimestamp.text = DateUtil.getTimeAndDateString(requireContext(), viewModel.revisionFrom!!.timestamp) binding.revisionFromEditComment.text = StringUtil.fromHtml(viewModel.revisionFrom!!.parsedcomment.trim()) binding.revisionFromTimestamp.setTextColor(ResourceUtil.getThemedColor(requireContext(), R.attr.progressive_color)) binding.overlayRevisionFromTimestamp.setTextColor(ResourceUtil.getThemedColor(requireContext(), R.attr.progressive_color)) @@ -472,7 +474,7 @@ class ArticleEditDetailsFragment : Fragment(), WatchlistExpiryDialog.Callback, M viewModel.revisionTo?.let { binding.usernameToButton.text = it.user - binding.revisionToTimestamp.text = DateUtil.getTimeAndDateString(requireContext(), it.timeStamp) + binding.revisionToTimestamp.text = DateUtil.getTimeAndDateString(requireContext(), it.timestamp) binding.overlayRevisionToTimestamp.text = binding.revisionToTimestamp.text binding.revisionToEditComment.text = StringUtil.fromHtml(it.parsedcomment.trim()) diff --git a/app/src/main/java/org/wikipedia/notifications/AnonymousNotificationHelper.kt b/app/src/main/java/org/wikipedia/notifications/AnonymousNotificationHelper.kt index dcee12b1a39..db7cb93ee74 100644 --- a/app/src/main/java/org/wikipedia/notifications/AnonymousNotificationHelper.kt +++ b/app/src/main/java/org/wikipedia/notifications/AnonymousNotificationHelper.kt @@ -6,21 +6,24 @@ import org.wikipedia.dataclient.WikiSite import org.wikipedia.dataclient.mwapi.MwQueryResponse import org.wikipedia.page.PageTitle import org.wikipedia.settings.Prefs -import org.wikipedia.util.DateUtil -import java.util.Date -import java.util.concurrent.TimeUnit +import kotlin.time.Clock +import kotlin.time.Duration.Companion.days +import kotlin.time.ExperimentalTime +import kotlin.time.Instant +@OptIn(ExperimentalTime::class) object AnonymousNotificationHelper { - private const val NOTIFICATION_DURATION_DAYS = 7L + private val NOTIFICATION_DURATION_DAYS = 7.days fun onEditSubmitted() { if (!AccountUtil.isLoggedIn) { - Prefs.lastAnonEditTime = Date().time + Prefs.lastAnonEditTime = System.currentTimeMillis() } } suspend fun maybeGetAnonUserInfo(wikiSite: WikiSite): MwQueryResponse { - return if (Date().time - Prefs.lastAnonEditTime < TimeUnit.DAYS.toMillis(NOTIFICATION_DURATION_DAYS)) { + val lastAnon = Instant.fromEpochMilliseconds(Prefs.lastAnonEditTime) + return if (Clock.System.now() - lastAnon < NOTIFICATION_DURATION_DAYS) { ServiceFactory.get(wikiSite).getUserInfo() } else { MwQueryResponse() @@ -41,10 +44,11 @@ object AnonymousNotificationHelper { } fun anonTalkPageHasRecentMessage(response: MwQueryResponse, title: PageTitle): Boolean { - response.query?.firstPage()?.revisions?.firstOrNull()?.timeStamp?.let { - if (Date().time - DateUtil.iso8601DateParse(it).time < TimeUnit.DAYS.toMillis(NOTIFICATION_DURATION_DAYS)) { + response.query?.firstPage()?.revisions?.firstOrNull()?.timestamp?.let { + val now = Clock.System.now() + if (now - it < NOTIFICATION_DURATION_DAYS) { Prefs.hasAnonymousNotification = true - Prefs.lastAnonNotificationTime = Date().time + Prefs.lastAnonNotificationTime = now.toEpochMilliseconds() Prefs.lastAnonNotificationLang = title.wikiSite.languageCode return true } @@ -53,6 +57,7 @@ object AnonymousNotificationHelper { } fun isWithinAnonNotificationTime(): Boolean { - return Date().time - Prefs.lastAnonNotificationTime < TimeUnit.DAYS.toMillis(NOTIFICATION_DURATION_DAYS) + val lastAnon = Instant.fromEpochMilliseconds(Prefs.lastAnonNotificationTime) + return Clock.System.now() - lastAnon < NOTIFICATION_DURATION_DAYS } } diff --git a/app/src/main/java/org/wikipedia/page/edithistory/EditHistoryItemView.kt b/app/src/main/java/org/wikipedia/page/edithistory/EditHistoryItemView.kt index fb137b3d3be..f5ca59515b5 100644 --- a/app/src/main/java/org/wikipedia/page/edithistory/EditHistoryItemView.kt +++ b/app/src/main/java/org/wikipedia/page/edithistory/EditHistoryItemView.kt @@ -17,6 +17,7 @@ import org.wikipedia.dataclient.mwapi.MwQueryPage import org.wikipedia.util.DateUtil import org.wikipedia.util.ResourceUtil import org.wikipedia.util.StringUtil +import kotlin.time.ExperimentalTime class EditHistoryItemView(context: Context) : FrameLayout(context) { interface Listener { @@ -70,7 +71,8 @@ class EditHistoryItemView(context: Context) : FrameLayout(context) { StringUtil.setHighlightedAndBoldenedText(binding.editHistoryTitle, historyTitle, currentQuery) } StringUtil.setHighlightedAndBoldenedText(binding.userNameText, itemRevision.user, currentQuery) - binding.editHistoryTimeText.text = DateUtil.getTimeString(context, DateUtil.iso8601DateParse(itemRevision.timeStamp)) + @OptIn(ExperimentalTime::class) + binding.editHistoryTimeText.text = DateUtil.getTimeString(context, itemRevision.timestamp) } fun setSelectedState(selectedState: Int) { diff --git a/app/src/main/java/org/wikipedia/page/edithistory/EditHistoryListActivity.kt b/app/src/main/java/org/wikipedia/page/edithistory/EditHistoryListActivity.kt index 90ac5769c19..1a1e7d5f837 100644 --- a/app/src/main/java/org/wikipedia/page/edithistory/EditHistoryListActivity.kt +++ b/app/src/main/java/org/wikipedia/page/edithistory/EditHistoryListActivity.kt @@ -52,6 +52,7 @@ import org.wikipedia.views.EditHistoryFilterOverflowView import org.wikipedia.views.EditHistoryStatsView import org.wikipedia.views.SearchAndFilterActionProvider import org.wikipedia.views.WikiErrorView +import kotlin.time.ExperimentalTime class EditHistoryListActivity : BaseActivity() { @@ -159,14 +160,15 @@ class EditHistoryListActivity : BaseActivity() { updateCompareStateItems() } + @OptIn(ExperimentalTime::class) private fun updateCompareStateItems() { binding.compareFromCard.isVisible = viewModel.selectedRevisionFrom != null if (viewModel.selectedRevisionFrom != null) { - binding.compareFromText.text = DateUtil.getShortDayWithTimeString(viewModel.selectedRevisionFrom!!.timeStamp) + binding.compareFromText.text = DateUtil.getShortDayWithTimeString(viewModel.selectedRevisionFrom!!.timestamp) } binding.compareToCard.isVisible = viewModel.selectedRevisionTo != null if (viewModel.selectedRevisionTo != null) { - binding.compareToText.text = DateUtil.getShortDayWithTimeString(viewModel.selectedRevisionTo!!.timeStamp) + binding.compareToText.text = DateUtil.getShortDayWithTimeString(viewModel.selectedRevisionTo!!.timestamp) } enableCompareButton(binding.compareConfirmButton, viewModel.selectedRevisionFrom != null && viewModel.selectedRevisionTo != null) } diff --git a/app/src/main/java/org/wikipedia/page/edithistory/EditHistoryListViewModel.kt b/app/src/main/java/org/wikipedia/page/edithistory/EditHistoryListViewModel.kt index 661267882ad..966c3b3f8f5 100644 --- a/app/src/main/java/org/wikipedia/page/edithistory/EditHistoryListViewModel.kt +++ b/app/src/main/java/org/wikipedia/page/edithistory/EditHistoryListViewModel.kt @@ -30,8 +30,14 @@ import org.wikipedia.util.Resource import org.wikipedia.util.log.L import retrofit2.HttpException import java.io.IOException +import java.time.LocalDate +import java.time.ZoneId import java.util.Calendar +import kotlin.time.Clock +import kotlin.time.ExperimentalTime +import kotlin.time.toJavaInstant +@OptIn(ExperimentalTime::class) class EditHistoryListViewModel(savedStateHandle: SavedStateHandle) : ViewModel() { val editHistoryStatsData = MutableLiveData>() @@ -55,6 +61,7 @@ class EditHistoryListViewModel(savedStateHandle: SavedStateHandle) : ViewModel() }).flow.map { pagingData -> val anonEditsOnly = Prefs.editHistoryFilterType == EditCount.EDIT_TYPE_ANONYMOUS val userEditsOnly = Prefs.editHistoryFilterType == EditCount.EDIT_TYPE_EDITORS + val zoneId = ZoneId.systemDefault() pagingData.insertSeparators { before, after -> if (before != null && after != null) { @@ -78,8 +85,8 @@ class EditHistoryListViewModel(savedStateHandle: SavedStateHandle) : ViewModel() }.map { EditHistoryItem(it) }.insertSeparators { before, after -> - val dateBefore = before?.item?.localDateTime?.toLocalDate() - val dateAfter = after?.item?.localDateTime?.toLocalDate() + val dateBefore = before?.item?.timestamp?.let { LocalDate.ofInstant(it.toJavaInstant(), zoneId) } + val dateAfter = after?.item?.timestamp?.let { LocalDate.ofInstant(it.toJavaInstant(), zoneId) } if (dateAfter != null && dateAfter != dateBefore) { EditHistorySeparator(DateUtil.getShortDateString(dateAfter)) } else { @@ -118,7 +125,7 @@ class EditHistoryListViewModel(savedStateHandle: SavedStateHandle) : ViewModel() pageId = page?.pageId ?: -1 editHistoryStatsData.postValue(Resource.Success(EditHistoryStats( - page?.revisions?.first() ?: MwQueryPage.Revision(), + page?.revisions?.first() ?: MwQueryPage.Revision(timestamp = Clock.System.now()), articleMetricsResponse.await()?.firstItem?.results ?: emptyList(), editCountsResponse.await(), editCountsUserResponse.await(), diff --git a/app/src/main/java/org/wikipedia/readinglist/ReadingListFragment.kt b/app/src/main/java/org/wikipedia/readinglist/ReadingListFragment.kt index 349a3c626e0..9e7bf50a969 100644 --- a/app/src/main/java/org/wikipedia/readinglist/ReadingListFragment.kt +++ b/app/src/main/java/org/wikipedia/readinglist/ReadingListFragment.kt @@ -77,7 +77,7 @@ import org.wikipedia.views.MultiSelectActionModeCallback import org.wikipedia.views.MultiSelectActionModeCallback.Companion.isTagType import org.wikipedia.views.PageItemView import org.wikipedia.views.SwipeableItemTouchHelperCallback -import java.util.Date +import java.time.LocalDateTime import java.util.Locale class ReadingListFragment : Fragment(), MenuProvider, ReadingListItemActionsDialog.Callback { @@ -434,7 +434,8 @@ class ReadingListFragment : Fragment(), MenuProvider, ReadingListItemActionsDial ReadingListMode.PREVIEW -> { if (readingList == null) { val emptyTitle = requireContext().getString(R.string.reading_lists_preview_header_title) - val emptyDescription = DateUtil.getTimeAndDateString(requireContext(), Date()) + val emptyDescription = DateUtil.getTimeAndDateString(requireContext(), + LocalDateTime.now()) viewModel.updateList(emptyTitle, emptyDescription, encoded = true) } else { update() diff --git a/app/src/main/java/org/wikipedia/suggestededits/SuggestedEditsRecentEditsItemView.kt b/app/src/main/java/org/wikipedia/suggestededits/SuggestedEditsRecentEditsItemView.kt index ecaaaea8d5e..09042c841b1 100644 --- a/app/src/main/java/org/wikipedia/suggestededits/SuggestedEditsRecentEditsItemView.kt +++ b/app/src/main/java/org/wikipedia/suggestededits/SuggestedEditsRecentEditsItemView.kt @@ -5,7 +5,6 @@ import android.graphics.Typeface import android.util.AttributeSet import android.view.LayoutInflater import android.view.View -import android.view.View.OnClickListener import android.view.ViewGroup import android.widget.FrameLayout import androidx.annotation.DrawableRes @@ -16,6 +15,7 @@ import org.wikipedia.dataclient.mwapi.MwQueryResult import org.wikipedia.util.DateUtil import org.wikipedia.util.ResourceUtil import org.wikipedia.util.StringUtil +import kotlin.time.ExperimentalTime class SuggestedEditsRecentEditsItemView constructor(context: Context, attrs: AttributeSet? = null) : FrameLayout(context, attrs) { val binding = ItemSuggestedEditsRecentEditsBinding.inflate(LayoutInflater.from(context), this, true) @@ -38,7 +38,7 @@ class SuggestedEditsRecentEditsItemView constructor(context: Context, attrs: Att } } - @Suppress("KotlinConstantConditions") + @OptIn(ExperimentalTime::class) fun setItem(item: MwQueryResult.RecentChange, currentQuery: String?) { this.item = item var isSummaryEmpty = false @@ -59,7 +59,7 @@ class SuggestedEditsRecentEditsItemView constructor(context: Context, attrs: Att binding.tagsText.setTypeface(Typeface.SANS_SERIF, if (isTagsEmpty) Typeface.ITALIC else Typeface.NORMAL) binding.tagsText.setTextColor(ResourceUtil.getThemedColor(context, if (isTagsEmpty) R.attr.secondary_color else R.attr.primary_color)) - StringUtil.setHighlightedAndBoldenedText(binding.timeText, DateUtil.getTimeString(context, item.parsedDateTime), currentQuery) + StringUtil.setHighlightedAndBoldenedText(binding.timeText, DateUtil.getTimeString(context, item.timestamp), currentQuery) binding.userNameText.text = item.user StringUtil.setHighlightedAndBoldenedText(binding.userNameText, item.user, currentQuery) binding.userNameText.contentDescription = context.getString(R.string.talk_user_title, item.user) diff --git a/app/src/main/java/org/wikipedia/suggestededits/SuggestedEditsRecentEditsViewModel.kt b/app/src/main/java/org/wikipedia/suggestededits/SuggestedEditsRecentEditsViewModel.kt index bd44833f8d2..15d72bdb441 100644 --- a/app/src/main/java/org/wikipedia/suggestededits/SuggestedEditsRecentEditsViewModel.kt +++ b/app/src/main/java/org/wikipedia/suggestededits/SuggestedEditsRecentEditsViewModel.kt @@ -21,11 +21,14 @@ import org.wikipedia.suggestededits.provider.EditingSuggestionsProvider import org.wikipedia.util.DateUtil import retrofit2.HttpException import java.io.IOException -import java.time.Duration import java.time.Instant -import java.util.Calendar -import java.util.Date +import java.time.LocalDate +import java.time.LocalDateTime +import java.time.ZoneId +import java.time.temporal.ChronoUnit import kotlin.math.max +import kotlin.time.ExperimentalTime +import kotlin.time.toJavaInstant class SuggestedEditsRecentEditsViewModel : ViewModel() { @@ -42,22 +45,25 @@ class SuggestedEditsRecentEditsViewModel : ViewModel() { private var cachedContinueKey: String? = null private val pageSize = 50 + @OptIn(ExperimentalTime::class) val recentEditsFlow = Pager(PagingConfig(pageSize = pageSize, initialLoadSize = pageSize), pagingSourceFactory = { RecentEditsPagingSource() }).flow.map { pagingData -> + val zoneId = ZoneId.systemDefault() pagingData.filter { if (currentQuery.isNotEmpty()) { + val dateTime = LocalDateTime.ofInstant(it.timestamp.toJavaInstant(), zoneId) it.parsedComment.contains(currentQuery, true) || it.title.contains(currentQuery, true) || it.user.contains(currentQuery, true) || it.joinedTags.contains(currentQuery, true) || - it.parsedDateTime.toString().contains(currentQuery, true) + dateTime.toString().contains(currentQuery, true) } else true }.map { RecentEditsItem(it) }.insertSeparators { before, after -> - val dateBefore = before?.item?.parsedDateTime?.toLocalDate() - val dateAfter = after?.item?.parsedDateTime?.toLocalDate() + val dateBefore = before?.item?.timestamp?.let { LocalDate.ofInstant(it.toJavaInstant(), zoneId) } + val dateAfter = after?.item?.timestamp?.let { LocalDate.ofInstant(it.toJavaInstant(), zoneId) } if (dateAfter != null && dateAfter != dateBefore) { RecentEditsSeparator(DateUtil.getShortDateString(dateAfter)) } else { @@ -133,7 +139,9 @@ class SuggestedEditsRecentEditsViewModel : ViewModel() { userInfoCache.addAll(usersInfoResponse) // Filtering User experiences and registration. - val finalRecentChanges = filterUserRegistration(filterUserExperience(recentChanges, userInfoCache)).sortedByDescending { it.parsedDateTime } + @OptIn(ExperimentalTime::class) + val finalRecentChanges = filterUserRegistration(filterUserExperience(recentChanges, userInfoCache)) + .sortedByDescending { it.timestamp } return Triple(finalRecentChanges, allRecentChanges, response.continuation?.rcContinuation) } @@ -220,7 +228,7 @@ class SuggestedEditsRecentEditsViewModel : ViewModel() { var qualifiedUser = false userInfo?.let { val editsCount = userInfo.editCount - val diffDays = diffDays(userInfo.registrationDate) + val diffDays = userInfo.registrationDate.until(LocalDate.now(), ChronoUnit.DAYS) findUserExperienceFilters.forEach { type -> val userExperienceArray = type.value.split("|") val requiredEdits = userExperienceArray.first().split(",") @@ -294,11 +302,5 @@ class SuggestedEditsRecentEditsViewModel : ViewModel() { } return recentChanges } - - private fun diffDays(date: Date): Long { - val nowDate = Calendar.getInstance().toInstant() - val beginDate = date.toInstant() - return Duration.between(beginDate, nowDate).toDays() - } } } diff --git a/app/src/main/java/org/wikipedia/suggestededits/SuggestedEditsTasksFragment.kt b/app/src/main/java/org/wikipedia/suggestededits/SuggestedEditsTasksFragment.kt index fb5834dcb70..051ff1950a8 100644 --- a/app/src/main/java/org/wikipedia/suggestededits/SuggestedEditsTasksFragment.kt +++ b/app/src/main/java/org/wikipedia/suggestededits/SuggestedEditsTasksFragment.kt @@ -49,6 +49,7 @@ import org.wikipedia.util.ResourceUtil import org.wikipedia.util.UriUtil import org.wikipedia.views.DefaultRecyclerAdapter import org.wikipedia.views.DefaultViewHolder +import java.time.LocalDate import java.time.LocalDateTime import java.time.ZoneId @@ -233,7 +234,10 @@ class SuggestedEditsTasksFragment : Fragment() { if (viewModel.latestEditStreak < 2) { contributionContainer.editStreakStatsView.setTitle(resources.getString(R.string.suggested_edits_last_edited)) - contributionContainer.editStreakStatsView.setDescription(if (viewModel.latestEditDate.time > 0) DateUtil.getMDYDateString(viewModel.latestEditDate) else resources.getString(R.string.suggested_edits_last_edited_never)) + contributionContainer.editStreakStatsView.setDescription( + if (viewModel.latestEditDate > LocalDate.MIN) DateUtil.getMDYDateString(viewModel.latestEditDate) + else resources.getString(R.string.suggested_edits_last_edited_never) + ) } else { contributionContainer.editStreakStatsView.setTitle(resources.getString(R.string.suggested_edits_edit_streak_label_text)) contributionContainer.editStreakStatsView.setDescription(resources.getQuantityString(R.plurals.suggested_edits_edit_streak_detail_text, diff --git a/app/src/main/java/org/wikipedia/suggestededits/SuggestedEditsTasksFragmentViewModel.kt b/app/src/main/java/org/wikipedia/suggestededits/SuggestedEditsTasksFragmentViewModel.kt index f8cb876e42c..6c867799531 100644 --- a/app/src/main/java/org/wikipedia/suggestededits/SuggestedEditsTasksFragmentViewModel.kt +++ b/app/src/main/java/org/wikipedia/suggestededits/SuggestedEditsTasksFragmentViewModel.kt @@ -17,9 +17,11 @@ import org.wikipedia.settings.Prefs import org.wikipedia.usercontrib.UserContribStats import org.wikipedia.util.Resource import org.wikipedia.util.ThrowableUtil -import java.time.Instant +import java.time.LocalDate +import java.time.ZoneId import java.time.temporal.ChronoUnit -import java.util.Date +import kotlin.time.ExperimentalTime +import kotlin.time.toJavaInstant class SuggestedEditsTasksFragmentViewModel : ViewModel() { @@ -39,7 +41,7 @@ class SuggestedEditsTasksFragmentViewModel : ViewModel() { var totalPageviews = 0L var totalContributions = 0 var homeContributions = 0 - var latestEditDate = Date() + var latestEditDate = LocalDate.now() var latestEditStreak = 0 var revertSeverity = 0 @@ -86,10 +88,10 @@ class SuggestedEditsTasksFragmentViewModel : ViewModel() { homeSiteResponse.query?.userInfo?.let { // T371442: In the case of Igbo Wikipedia, allow patrolling if the user has 500 or more edits, and 30 days of tenure. // For all other wikis, allow patrolling if the user has rollback rights or is an admin. - if (WikipediaApp.instance.wikiSite.languageCode == "ig") { - allowToPatrolEdits = it.editCount >= 500 && it.registrationDate.toInstant().plus(30, ChronoUnit.DAYS).isBefore(Instant.now()) + allowToPatrolEdits = if (WikipediaApp.instance.wikiSite.languageCode == "ig") { + it.editCount >= 500 && it.registrationDate.plusDays(30) < LocalDate.now() } else { - allowToPatrolEdits = it.rights.contains("rollback") || it.groups().contains("sysop") + it.rights.contains("rollback") || it.groups().contains("sysop") } if (it.isBlocked) { @@ -112,15 +114,11 @@ class SuggestedEditsTasksFragmentViewModel : ViewModel() { totalContributions += commonsResponse.query?.userInfo!!.editCount totalContributions += homeContributions - latestEditDate = wikidataResponse.query?.userInfo!!.latestContribDate - - if (commonsResponse.query?.userInfo!!.latestContribDate.after(latestEditDate)) { - latestEditDate = commonsResponse.query?.userInfo!!.latestContribDate - } - - if (homeSiteResponse.query?.userInfo!!.latestContribDate.after(latestEditDate)) { - latestEditDate = homeSiteResponse.query?.userInfo!!.latestContribDate - } + latestEditDate = maxOf( + wikidataResponse.query?.userInfo!!.latestContribDate, + commonsResponse.query?.userInfo!!.latestContribDate, + homeSiteResponse.query?.userInfo!!.latestContribDate + ) val totalContributionsList = homeSiteResponse.query!!.userContributions + wikidataResponse.query!!.userContributions + @@ -141,11 +139,14 @@ class SuggestedEditsTasksFragmentViewModel : ViewModel() { } } + @OptIn(ExperimentalTime::class) private fun getEditStreak(contributions: List): Int { if (contributions.isEmpty()) { return 0 } - val dates = contributions.map { it.parsedDateTime.toLocalDate() } + val zoneId = ZoneId.systemDefault() + val dates = contributions + .map { LocalDate.ofInstant(it.timestamp.toJavaInstant(), zoneId) } .toSortedSet(Comparator.reverseOrder()) return dates.asSequence() .zipWithNext { date1, date2 -> date2.until(date1, ChronoUnit.DAYS) } diff --git a/app/src/main/java/org/wikipedia/suggestededits/provider/EditingSuggestionsProvider.kt b/app/src/main/java/org/wikipedia/suggestededits/provider/EditingSuggestionsProvider.kt index 1c1404b5db3..582c8c032f2 100644 --- a/app/src/main/java/org/wikipedia/suggestededits/provider/EditingSuggestionsProvider.kt +++ b/app/src/main/java/org/wikipedia/suggestededits/provider/EditingSuggestionsProvider.kt @@ -17,10 +17,13 @@ import org.wikipedia.json.JsonUtil import org.wikipedia.page.PageTitle import org.wikipedia.suggestededits.SuggestedEditsRecentEditsViewModel import org.wikipedia.util.log.L -import java.time.Instant import java.util.concurrent.Semaphore import kotlin.math.abs +import kotlin.time.Clock +import kotlin.time.ExperimentalTime +import kotlin.time.toJavaInstant +@OptIn(ExperimentalTime::class) object EditingSuggestionsProvider { private val mutex: Semaphore = Semaphore(1) @@ -45,7 +48,7 @@ object EditingSuggestionsProvider { private var revertCandidateLang: String = "" private val revertCandidateCache: ArrayDeque = ArrayDeque() private var revertCandidateLastRevId = 0L - private var revertCandidateLastTimeStamp = Instant.now() + private var revertCandidateLastTimeStamp = Clock.System.now() private const val MAX_RETRY_LIMIT: Long = 20 @@ -353,7 +356,7 @@ object EditingSuggestionsProvider { revertCandidateCache.addFirst(it) if (it.curRev > revertCandidateLastRevId) { revertCandidateLastRevId = it.curRev - revertCandidateLastTimeStamp = it.parsedInstant + revertCandidateLastTimeStamp = it.timestamp } } } @@ -385,7 +388,8 @@ object EditingSuggestionsProvider { SuggestedEditsRecentEditsViewModel.getRecentEditsCall(wikiSite) else SuggestedEditsRecentEditsViewModel.getRecentEditsCall(wikiSite, - startTimeStamp = revertCandidateLastTimeStamp, direction = "newer") + startTimeStamp = revertCandidateLastTimeStamp.toJavaInstant(), + direction = "newer") // Retrieve the list of filtered changes from our filter, but *also* get // the list of total changes so that we can update our maxRevId and latest @@ -398,8 +402,8 @@ object EditingSuggestionsProvider { if (candidate.curRev > maxRevId) { maxRevId = candidate.curRev } - if (candidate.parsedInstant > revertCandidateLastTimeStamp) { - revertCandidateLastTimeStamp = candidate.parsedInstant + if (candidate.timestamp > revertCandidateLastTimeStamp) { + revertCandidateLastTimeStamp = candidate.timestamp } } for (candidate in filteredChanges) { diff --git a/app/src/main/java/org/wikipedia/usercontrib/UserContribItemView.kt b/app/src/main/java/org/wikipedia/usercontrib/UserContribItemView.kt index 0b116b46435..56aa2c729e8 100644 --- a/app/src/main/java/org/wikipedia/usercontrib/UserContribItemView.kt +++ b/app/src/main/java/org/wikipedia/usercontrib/UserContribItemView.kt @@ -12,6 +12,7 @@ import org.wikipedia.dataclient.mwapi.UserContribution import org.wikipedia.util.DateUtil import org.wikipedia.util.ResourceUtil import org.wikipedia.util.StringUtil +import kotlin.time.ExperimentalTime class UserContribItemView(context: Context) : FrameLayout(context) { interface Listener { @@ -50,6 +51,7 @@ class UserContribItemView(context: Context) : FrameLayout(context) { StringUtil.setHighlightedAndBoldenedText(binding.editSummary, editSummary, currentQuery) } binding.currentIndicator.isVisible = contrib.top - binding.editHistoryTimeText.text = DateUtil.getTimeString(context, contrib.parsedDateTime) + @OptIn(ExperimentalTime::class) + binding.editHistoryTimeText.text = DateUtil.getTimeString(context, contrib.timestamp) } } diff --git a/app/src/main/java/org/wikipedia/usercontrib/UserContribListViewModel.kt b/app/src/main/java/org/wikipedia/usercontrib/UserContribListViewModel.kt index 133de7f57b8..7a8cc991def 100644 --- a/app/src/main/java/org/wikipedia/usercontrib/UserContribListViewModel.kt +++ b/app/src/main/java/org/wikipedia/usercontrib/UserContribListViewModel.kt @@ -4,7 +4,14 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import androidx.paging.* +import androidx.paging.Pager +import androidx.paging.PagingConfig +import androidx.paging.PagingSource +import androidx.paging.PagingState +import androidx.paging.cachedIn +import androidx.paging.filter +import androidx.paging.insertSeparators +import androidx.paging.map import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch @@ -19,7 +26,10 @@ import org.wikipedia.util.Resource import org.wikipedia.util.log.L import retrofit2.HttpException import java.io.IOException -import java.util.Date +import java.time.LocalDate +import java.time.ZoneId +import kotlin.time.ExperimentalTime +import kotlin.time.toJavaInstant class UserContribListViewModel(savedStateHandle: SavedStateHandle) : ViewModel() { val userContribStatsData = MutableLiveData>() @@ -41,9 +51,11 @@ class UserContribListViewModel(savedStateHandle: SavedStateHandle) : ViewModel() private val cachedContribs = mutableListOf() private var cachedContinueKey: String? = null + @OptIn(ExperimentalTime::class) val userContribFlow = Pager(PagingConfig(pageSize = 50), pagingSourceFactory = { UserContribPagingSource() }).flow.map { pagingData -> + val zoneId = ZoneId.systemDefault() pagingData.filter { if (currentQuery.isNotEmpty()) { it.comment.contains(currentQuery, true) || @@ -52,8 +64,8 @@ class UserContribListViewModel(savedStateHandle: SavedStateHandle) : ViewModel() }.map { UserContribItem(it) }.insertSeparators { before, after -> - val dateBefore = before?.item?.parsedDateTime?.toLocalDate() - val dateAfter = after?.item?.parsedDateTime?.toLocalDate() + val dateBefore = before?.item?.timestamp?.let { LocalDate.ofInstant(it.toJavaInstant(), zoneId) } + val dateAfter = after?.item?.timestamp?.let { LocalDate.ofInstant(it.toJavaInstant(), zoneId) } if (dateAfter != null && dateAfter != dateBefore) { UserContribSeparator(DateUtil.getShortDateString(dateAfter)) } else { @@ -123,5 +135,5 @@ class UserContribListViewModel(savedStateHandle: SavedStateHandle) : ViewModel() open class UserContribItemModel class UserContribItem(val item: UserContribution) : UserContribItemModel() class UserContribSeparator(val date: String) : UserContribItemModel() - class UserContribStats(val totalEdits: Int, val registrationDate: Date, val projectName: String) : UserContribItemModel() + class UserContribStats(val totalEdits: Int, val registrationDate: LocalDate, val projectName: String) : UserContribItemModel() } diff --git a/app/src/main/java/org/wikipedia/usercontrib/UserInformationDialog.kt b/app/src/main/java/org/wikipedia/usercontrib/UserInformationDialog.kt index 9950e005757..823ee7a62e9 100644 --- a/app/src/main/java/org/wikipedia/usercontrib/UserInformationDialog.kt +++ b/app/src/main/java/org/wikipedia/usercontrib/UserInformationDialog.kt @@ -20,9 +20,7 @@ import org.wikipedia.suggestededits.SuggestionsActivity import org.wikipedia.util.DateUtil import org.wikipedia.util.Resource import org.wikipedia.util.StringUtil -import java.time.LocalDateTime -import java.time.ZoneId -import java.util.Date +import java.time.LocalDate class UserInformationDialog : DialogFragment() { @@ -75,13 +73,12 @@ class UserInformationDialog : DialogFragment() { binding.dialogErrorView.isVisible = false } - private fun onSuccess(editCount: String, registrationDate: Date) { + private fun onSuccess(editCount: String, registrationDate: LocalDate) { sendPatrollerExperienceEvent() binding.userInformationContainer.isVisible = true binding.dialogProgressBar.isVisible = false binding.dialogErrorView.isVisible = false - val localDate = LocalDateTime.ofInstant(registrationDate.toInstant(), ZoneId.systemDefault()).toLocalDate() - val dateStr = DateUtil.getShortDateString(localDate) + val dateStr = DateUtil.getShortDateString(registrationDate) binding.userTenure.text = StringUtil.fromHtml(getString(R.string.patroller_tasks_edits_list_user_information_dialog_joined_date_text, dateStr)) binding.editCount.text = StringUtil.fromHtml(getString(R.string.patroller_tasks_edits_list_user_information_dialog_edit_count_text, editCount)) } diff --git a/app/src/main/java/org/wikipedia/usercontrib/UserInformationDialogViewModel.kt b/app/src/main/java/org/wikipedia/usercontrib/UserInformationDialogViewModel.kt index b9aecd1e59f..7d9f5bfb80b 100644 --- a/app/src/main/java/org/wikipedia/usercontrib/UserInformationDialogViewModel.kt +++ b/app/src/main/java/org/wikipedia/usercontrib/UserInformationDialogViewModel.kt @@ -11,12 +11,12 @@ import org.wikipedia.WikipediaApp import org.wikipedia.dataclient.ServiceFactory import org.wikipedia.dataclient.WikiSite import org.wikipedia.util.Resource -import java.util.Date +import java.time.LocalDate class UserInformationDialogViewModel(savedStateHandle: SavedStateHandle) : ViewModel() { var userName = savedStateHandle.get(UserInformationDialog.USERNAME_ARG)!! - private val _uiState = MutableStateFlow(Resource>()) + private val _uiState = MutableStateFlow(Resource>()) val uiState = _uiState.asStateFlow() init { @@ -31,7 +31,7 @@ class UserInformationDialogViewModel(savedStateHandle: SavedStateHandle) : ViewM val userInfo = ServiceFactory.get(WikiSite.forLanguageCode(WikipediaApp.instance.appOrSystemLanguageCode)).globalUserInfo(userName) userInfo.query?.globalUserInfo?.let { val editCount = String.format("%,d", it.editCount) - _uiState.value = Resource.Success(Pair(editCount, it.registrationDate)) + _uiState.value = Resource.Success(editCount to it.registrationDate) } ?: run { _uiState.value = Resource.Error(Throwable("Cannot fetch user information.")) } diff --git a/app/src/main/java/org/wikipedia/util/DateUtil.kt b/app/src/main/java/org/wikipedia/util/DateUtil.kt index cb32ceb6d51..8680ac96090 100644 --- a/app/src/main/java/org/wikipedia/util/DateUtil.kt +++ b/app/src/main/java/org/wikipedia/util/DateUtil.kt @@ -13,6 +13,7 @@ import java.text.SimpleDateFormat import java.time.Instant import java.time.LocalDate import java.time.LocalDateTime +import java.time.LocalTime import java.time.ZoneId import java.time.ZoneOffset import java.time.ZonedDateTime @@ -25,7 +26,10 @@ import java.util.GregorianCalendar import java.util.Locale import java.util.TimeZone import java.util.concurrent.ConcurrentHashMap +import kotlin.time.ExperimentalTime +import kotlin.time.toJavaInstant +@OptIn(ExperimentalTime::class) object DateUtil { private val DATE_FORMATS = ConcurrentHashMap() private val DATE_TIME_FORMATTERS = ConcurrentHashMap() @@ -55,7 +59,7 @@ object DateUtil { return getExtraShortDateString(date.time) } - fun getMDYDateString(date: Date): String { + fun getMDYDateString(date: LocalDate): String { return getDateStringWithSkeletonPattern(date, "MM/dd/yyyy") } @@ -67,7 +71,7 @@ object DateUtil { return getDateStringWithSkeletonPattern(date, "MMMM") } - fun getYearOnlyDateString(date: Date): String { + fun getYearOnlyDateString(date: LocalDate): String { return getDateStringWithSkeletonPattern(date, "yyyy") } @@ -75,26 +79,19 @@ object DateUtil { return getCachedDateFormat("yyyyMMdd", Locale.ROOT, true).format(date) } - fun getMMMMdYYYY(date: Date): String { - return getCachedDateFormat("MMMM d, yyyy", Locale.getDefault(), true).format(date) - } - private fun getExtraShortDateString(date: Date): String { return getDateStringWithSkeletonPattern(date, "MMM d") } - fun getTimeString(context: Context, date: Date): String { - val datePattern = if (DateFormat.is24HourFormat(context)) "HH:mm" else "hh:mm a" - return getDateStringWithSkeletonPattern(date, datePattern) - } - - fun getTimeString(context: Context, localDateTime: LocalDateTime): String { + fun getTimeString(context: Context, instant: kotlin.time.Instant): String { + val localTime = LocalTime.ofInstant(instant.toJavaInstant(), ZoneId.systemDefault()) val datePattern = if (DateFormat.is24HourFormat(context)) "HH:mm" else "hh:mm a" - return getDateStringWithSkeletonPattern(localDateTime, datePattern) + return getDateStringWithSkeletonPattern(localTime, datePattern) } - fun getShortDayWithTimeString(dateStr: String): String { - return getDateStringWithSkeletonPattern(iso8601DateParse(dateStr), "MMM d HH:mm") + fun getShortDayWithTimeString(instant: kotlin.time.Instant): String { + val localDateTime = LocalDateTime.ofInstant(instant.toJavaInstant(), ZoneId.systemDefault()) + return getDateStringWithSkeletonPattern(localDateTime, "MMM d HH:mm") } fun getShortDayWithTimeString(date: Date): String { @@ -102,13 +99,23 @@ object DateUtil { } fun getTimeAndDateString(context: Context, date: Date): String { + val localDateTime = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault()) + return getTimeAndDateString(context, localDateTime) + } + + fun getTimeAndDateString(context: Context, instant: kotlin.time.Instant): String { + val localDateTime = LocalDateTime.ofInstant(instant.toJavaInstant(), ZoneId.systemDefault()) + return getTimeAndDateString(context, localDateTime) + } + + fun getTimeAndDateString(context: Context, date: LocalDateTime): String { val datePattern = if (DateFormat.is24HourFormat(context)) "HH:mm, MMM d, yyyy" else "hh:mm a, MMM d, yyyy" return getDateStringWithSkeletonPattern(date, datePattern) } fun getTimeAndDateString(context: Context, dateStr: String): String { - val datePattern = if (DateFormat.is24HourFormat(context)) "HH:mm, MMM d, yyyy" else "hh:mm a, MMM d, yyyy" - return getDateStringWithSkeletonPattern(iso8601DateParse(dateStr), datePattern) + val localDateTime = iso8601LocalDateTimeParse(dateStr) + return getTimeAndDateString(context, localDateTime) } fun getDateAndTime(context: Context, date: Date): String { @@ -139,7 +146,7 @@ object DateUtil { private fun getCachedDateTimeFormatter(pattern: String, locale: Locale, utc: Boolean, skeleton: Boolean = false): DateTimeFormatter { return DATE_TIME_FORMATTERS.getOrPut(pattern) { val dtf = DateTimeFormatter.ofPattern(if (skeleton) DateFormat.getBestDateTimePattern(locale, pattern) else pattern, locale) - return if (utc) dtf.withZone(ZoneOffset.UTC) else dtf + if (utc) dtf.withZone(ZoneOffset.UTC) else dtf } } diff --git a/app/src/main/java/org/wikipedia/views/EditHistoryStatsView.kt b/app/src/main/java/org/wikipedia/views/EditHistoryStatsView.kt index 76606adc944..22326eb2630 100644 --- a/app/src/main/java/org/wikipedia/views/EditHistoryStatsView.kt +++ b/app/src/main/java/org/wikipedia/views/EditHistoryStatsView.kt @@ -16,7 +16,10 @@ import org.wikipedia.util.DateUtil import org.wikipedia.util.DimenUtil import org.wikipedia.util.FeedbackUtil import org.wikipedia.util.StringUtil -import java.time.LocalDateTime +import java.time.LocalDate +import java.time.ZoneId +import kotlin.time.ExperimentalTime +import kotlin.time.toJavaInstant class EditHistoryStatsView constructor(context: Context, attrs: AttributeSet? = null) : ConstraintLayout(context, attrs) { @@ -28,22 +31,21 @@ class EditHistoryStatsView constructor(context: Context, attrs: AttributeSet? = setPadding(padding, 0, padding, 0) } + @OptIn(ExperimentalTime::class) fun setup(pageTitle: PageTitle, editHistoryStats: EditHistoryListViewModel.EditHistoryStats?) { binding.articleTitleView.text = StringUtil.fromHtml(context.getString(R.string.page_edit_history_activity_title, "${pageTitle.displayText}")) - editHistoryStats?.let { stats -> - val timestamp = stats.revision.timeStamp - if (timestamp.isNotBlank()) { - val createdYear = DateUtil.getYearOnlyDateString(DateUtil.iso8601DateParse(timestamp)) - val localDateTime = LocalDateTime.now() - val today = DateUtil.getShortDateString(localDateTime.toLocalDate()) - val lastYear = DateUtil.getShortDateString(localDateTime.minusYears(1).toLocalDate()) - binding.editCountsView.text = context.resources.getQuantityString(R.plurals.page_edit_history_article_edits_since_year, - stats.allEdits.count, stats.allEdits.count, createdYear) - binding.statsGraphView.setData(stats.metrics.map { it.edits.toFloat() }) - binding.statsGraphView.contentDescription = context.getString(R.string.page_edit_history_metrics_content_description, lastYear, today) - FeedbackUtil.setButtonTooltip(binding.statsGraphView) - } + editHistoryStats?.revision?.timestamp?.let { timestamp -> + val date = LocalDate.ofInstant(timestamp.toJavaInstant(), ZoneId.systemDefault()) + val createdYear = DateUtil.getYearOnlyDateString(date) + val localDate = LocalDate.now() + val today = DateUtil.getShortDateString(localDate) + val lastYear = DateUtil.getShortDateString(localDate.minusYears(1)) + binding.editCountsView.text = context.resources.getQuantityString(R.plurals.page_edit_history_article_edits_since_year, + editHistoryStats.allEdits.count, editHistoryStats.allEdits.count, createdYear) + binding.statsGraphView.setData(editHistoryStats.metrics.map { it.edits.toFloat() }) + binding.statsGraphView.contentDescription = context.getString(R.string.page_edit_history_metrics_content_description, lastYear, today) + FeedbackUtil.setButtonTooltip(binding.statsGraphView) } binding.articleTitleView.movementMethod = LinkMovementMethodExt { _ -> context.startActivity(PageActivity.newIntentForNewTab(context, HistoryEntry(pageTitle, HistoryEntry.SOURCE_EDIT_HISTORY), pageTitle)) diff --git a/app/src/main/java/org/wikipedia/watchlist/WatchlistFragment.kt b/app/src/main/java/org/wikipedia/watchlist/WatchlistFragment.kt index e06b141a718..63993f658c2 100644 --- a/app/src/main/java/org/wikipedia/watchlist/WatchlistFragment.kt +++ b/app/src/main/java/org/wikipedia/watchlist/WatchlistFragment.kt @@ -49,9 +49,7 @@ import org.wikipedia.util.ResourceUtil import org.wikipedia.util.StringUtil import org.wikipedia.views.NotificationButtonView import org.wikipedia.views.SearchAndFilterActionProvider -import java.time.LocalDateTime -import java.time.ZoneId -import java.util.Date +import java.time.LocalDate class WatchlistFragment : Fragment(), WatchlistItemView.Callback, MenuProvider { private var _binding: FragmentWatchlistBinding? = null @@ -210,10 +208,9 @@ class WatchlistFragment : Fragment(), WatchlistItemView.Callback, MenuProvider { } internal inner class WatchlistDateViewHolder(view: View) : RecyclerView.ViewHolder(view) { - fun bindItem(date: Date) { + fun bindItem(date: LocalDate) { val textView = itemView.findViewById(R.id.dateText) - val localDateTime = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault()).toLocalDate() - textView.text = DateUtil.getShortDateString(localDateTime) + textView.text = DateUtil.getShortDateString(date) } } @@ -260,7 +257,7 @@ class WatchlistFragment : Fragment(), WatchlistItemView.Callback, MenuProvider { if (position == 0 && actionMode == null) { return VIEW_TYPE_SEARCH_BAR } - return if (items[position] is Date) { + return if (items[position] is LocalDate) { VIEW_TYPE_DATE } else { VIEW_TYPE_ITEM @@ -284,7 +281,7 @@ class WatchlistFragment : Fragment(), WatchlistItemView.Callback, MenuProvider { override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { when (holder) { is WatchlistSearchBarHolder -> holder.updateFilterIconAndCount() - is WatchlistDateViewHolder -> holder.bindItem(items[position] as Date) + is WatchlistDateViewHolder -> holder.bindItem(items[position] as LocalDate) else -> (holder as WatchlistItemViewHolder).bindItem((items[position] as MwQueryResult.WatchlistItem)) } } diff --git a/app/src/main/java/org/wikipedia/watchlist/WatchlistItemView.kt b/app/src/main/java/org/wikipedia/watchlist/WatchlistItemView.kt index bdafd2ce7a6..43b499a5006 100644 --- a/app/src/main/java/org/wikipedia/watchlist/WatchlistItemView.kt +++ b/app/src/main/java/org/wikipedia/watchlist/WatchlistItemView.kt @@ -17,6 +17,7 @@ import org.wikipedia.extensions.setLayoutDirectionByLang import org.wikipedia.util.DateUtil import org.wikipedia.util.ResourceUtil import org.wikipedia.util.StringUtil +import kotlin.time.ExperimentalTime class WatchlistItemView(context: Context, attrs: AttributeSet? = null) : FrameLayout(context, attrs) { val binding = ItemWatchlistBinding.inflate(LayoutInflater.from(context), this, true) @@ -55,7 +56,8 @@ class WatchlistItemView(context: Context, attrs: AttributeSet? = null) : FrameLa binding.summaryText.setTypeface(Typeface.SANS_SERIF, if (isSummaryEmpty) Typeface.ITALIC else Typeface.NORMAL) binding.summaryText.setTextColor(ResourceUtil.getThemedColor(context, if (isSummaryEmpty) R.attr.secondary_color else R.attr.primary_color)) - binding.timeText.text = DateUtil.getTimeString(context, item.date) + @OptIn(ExperimentalTime::class) + binding.timeText.text = DateUtil.getTimeString(context, item.timestamp) binding.userNameText.contentDescription = context.getString(R.string.talk_user_title, item.user) binding.userNameText.setIconResource(if (item.isAnon) R.drawable.ic_anonymous_ooui else R.drawable.ic_user_avatar) diff --git a/app/src/main/java/org/wikipedia/watchlist/WatchlistViewModel.kt b/app/src/main/java/org/wikipedia/watchlist/WatchlistViewModel.kt index e876e3950fd..be89af8b74b 100644 --- a/app/src/main/java/org/wikipedia/watchlist/WatchlistViewModel.kt +++ b/app/src/main/java/org/wikipedia/watchlist/WatchlistViewModel.kt @@ -25,8 +25,12 @@ import org.wikipedia.util.L10nUtil import org.wikipedia.util.Resource import org.wikipedia.util.StringUtil import java.io.IOException -import java.util.Calendar +import java.time.LocalDate +import java.time.ZoneId +import kotlin.time.ExperimentalTime +import kotlin.time.toJavaInstant +@OptIn(ExperimentalTime::class) class WatchlistViewModel : ViewModel() { private val handler = CoroutineExceptionHandler { _, throwable -> @@ -47,20 +51,17 @@ class WatchlistViewModel : ViewModel() { } fun updateList(searchBarPlaceholder: Boolean = true) { - finalList = mutableListOf() if (searchBarPlaceholder) { finalList.add("") // placeholder for search bar } - val calendar = Calendar.getInstance() - var curDay = -1 - + var curDate = LocalDate.EPOCH + val zoneId = ZoneId.systemDefault() val excludedWikiCodes = Prefs.watchlistExcludedWikiCodes watchlistItems.forEach { item -> - if (excludedWikiCodes.contains(item.wiki?.languageCode)) { return@forEach } @@ -73,10 +74,10 @@ class WatchlistViewModel : ViewModel() { return@forEach } - calendar.time = item.date - if (calendar.get(Calendar.DAY_OF_YEAR) != curDay) { - curDay = calendar.get(Calendar.DAY_OF_YEAR) - finalList.add(item.date) + val date = LocalDate.ofInstant(item.timestamp.toJavaInstant(), zoneId) + if (curDate != date) { + curDate = date + finalList.add(date) } finalList.add(item) @@ -98,7 +99,7 @@ class WatchlistViewModel : ViewModel() { } } }.awaitAll() - watchlistItems.sortByDescending { it.date } + watchlistItems.sortByDescending { it.timestamp } updateList(searchBarPlaceholder) } } diff --git a/app/src/test/java/org/wikipedia/edit/wikitext/WikitextClientTest.kt b/app/src/test/java/org/wikipedia/edit/wikitext/WikitextClientTest.kt index 809a3dd04c0..1b319113c85 100644 --- a/app/src/test/java/org/wikipedia/edit/wikitext/WikitextClientTest.kt +++ b/app/src/test/java/org/wikipedia/edit/wikitext/WikitextClientTest.kt @@ -6,6 +6,8 @@ import org.hamcrest.Matchers import org.junit.Test import org.wikipedia.dataclient.mwapi.MwQueryResponse import org.wikipedia.test.MockRetrofitTest +import kotlin.time.ExperimentalTime +import kotlin.time.Instant class WikitextClientTest : MockRetrofitTest() { @Test @@ -16,7 +18,9 @@ class WikitextClientTest : MockRetrofitTest() { requestWikiTextForSectionWithInfo() }.run { MatcherAssert.assertThat(query?.firstPage()?.revisions?.first()?.contentMain, Matchers.`is`("\\o/\n\ntest12\n\n3")) - MatcherAssert.assertThat(query?.firstPage()?.revisions?.first()?.timeStamp, Matchers.`is`("2018-03-18T18:10:54Z")) + @OptIn(ExperimentalTime::class) + MatcherAssert.assertThat(query?.firstPage()?.revisions?.first()?.timestamp, + Matchers.`is`(Instant.parse("2018-03-18T18:10:54Z"))) } }