diff --git a/app/src/main/java/org/wikipedia/games/db/DailyGameHistory.kt b/app/src/main/java/org/wikipedia/games/db/DailyGameHistory.kt index a3f2e4ac7d2..4776c85bbed 100644 --- a/app/src/main/java/org/wikipedia/games/db/DailyGameHistory.kt +++ b/app/src/main/java/org/wikipedia/games/db/DailyGameHistory.kt @@ -1,7 +1,9 @@ package org.wikipedia.games.db import androidx.room.Entity +import androidx.room.Ignore import androidx.room.PrimaryKey +import java.time.LocalDate @Entity data class DailyGameHistory( @@ -14,4 +16,7 @@ data class DailyGameHistory( var score: Int, var playType: Int, var gameData: String? -) +) { + @Ignore + val date: LocalDate = LocalDate.of(year, month, day) +} diff --git a/app/src/main/java/org/wikipedia/games/onthisday/DateDecorator.kt b/app/src/main/java/org/wikipedia/games/onthisday/DateDecorator.kt index 36d4e7b3c85..063216c19c9 100644 --- a/app/src/main/java/org/wikipedia/games/onthisday/DateDecorator.kt +++ b/app/src/main/java/org/wikipedia/games/onthisday/DateDecorator.kt @@ -2,31 +2,18 @@ package org.wikipedia.games.onthisday import android.content.Context import android.content.res.ColorStateList -import android.os.Parcel -import android.os.Parcelable import androidx.core.content.ContextCompat import com.google.android.material.datepicker.DayViewDecorator +import kotlinx.parcelize.Parcelize import org.wikipedia.R -import java.util.Calendar -import java.util.Date +import java.time.LocalDate +@Parcelize class DateDecorator( - private val startDate: Date, - private val endDate: Date, - private val scoreData: Map + private val startDate: LocalDate, + private val endDate: LocalDate, + private val scoreData: Map ) : DayViewDecorator() { - - private val calendar = Calendar.getInstance() - - private fun isDateInRange(year: Int, month: Int, day: Int): Boolean { - synchronized(calendar) { - calendar.set(year, month, day, 0, 0, 0) - calendar.set(Calendar.MILLISECOND, 0) - - return !calendar.before(startDate) && !calendar.after(endDate) - } - } - override fun getBackgroundColor( context: Context, year: Int, @@ -35,14 +22,12 @@ class DateDecorator( valid: Boolean, selected: Boolean ): ColorStateList? { - if (!isDateInRange(year, month, day)) { + val date = LocalDate.of(year, month + 1, day) + if (date !in startDate..endDate) { return null } - val dateKey = getDateKey(year, month + 1, day) - val score = scoreData[dateKey] - - return when (score) { + return when (scoreData[date]) { 0, 1, 2 -> ColorStateList.valueOf(ContextCompat.getColor(context, R.color.yellow200)) 3, 4 -> ColorStateList.valueOf(ContextCompat.getColor(context, R.color.orange200)) 5 -> ColorStateList.valueOf(ContextCompat.getColor(context, R.color.green600)) @@ -58,36 +43,10 @@ class DateDecorator( valid: Boolean, selected: Boolean ): ColorStateList? { - val dateKey = getDateKey(year, month + 1, day) - val score = scoreData[dateKey] - - return when (score) { + val date = LocalDate.of(year, month + 1, day) + return when (scoreData[date]) { null -> super.getTextColor(context, year, month, day, valid, selected) else -> ColorStateList.valueOf(ContextCompat.getColor(context, R.color.gray700)) } } - - constructor(parcel: Parcel) : this( - Date(), - Date(), - hashMapOf() - ) - - override fun describeContents(): Int { return 0 } - - override fun writeToParcel(dest: Parcel, flags: Int) {} - - companion object CREATOR : Parcelable.Creator { - override fun createFromParcel(parcel: Parcel): DateDecorator { - return DateDecorator(parcel) - } - - override fun newArray(size: Int): Array { - return arrayOfNulls(size) - } - - fun getDateKey(year: Int, month: Int, day: Int): Long { - return (year * 10000 + month * 100 + day).toLong() - } - } } diff --git a/app/src/main/java/org/wikipedia/games/onthisday/OnThisDayGameActivity.kt b/app/src/main/java/org/wikipedia/games/onthisday/OnThisDayGameActivity.kt index 374aa7577c0..24ff1eac961 100644 --- a/app/src/main/java/org/wikipedia/games/onthisday/OnThisDayGameActivity.kt +++ b/app/src/main/java/org/wikipedia/games/onthisday/OnThisDayGameActivity.kt @@ -32,9 +32,6 @@ import org.wikipedia.util.DimenUtil import org.wikipedia.util.FeedbackUtil import org.wikipedia.util.Resource import org.wikipedia.util.UriUtil -import java.time.LocalDate -import java.time.ZoneOffset -import java.time.format.DateTimeFormatter class OnThisDayGameActivity : BaseActivity(), BaseActivity.Callback { @@ -219,18 +216,10 @@ class OnThisDayGameActivity : BaseActivity(), BaseActivity.Callback { companion object { fun newIntent(context: Context, invokeSource: Constants.InvokeSource, wikiSite: WikiSite): Intent { - val intent = Intent(context, OnThisDayGameActivity::class.java) + return Intent(context, OnThisDayGameActivity::class.java) .putExtra(Constants.ARG_WIKISITE, wikiSite) .putExtra(Constants.INTENT_EXTRA_INVOKE_SOURCE, invokeSource) - if (Prefs.lastOtdGameDateOverride.isNotEmpty()) { - val date = try { - LocalDate.parse(Prefs.lastOtdGameDateOverride, DateTimeFormatter.ISO_LOCAL_DATE) - } catch (_: Exception) { - LocalDate.now() - } - intent.putExtra(OnThisDayGameViewModel.EXTRA_DATE, date.atStartOfDay().toInstant(ZoneOffset.UTC).epochSecond) - } - return intent + .putExtra(OnThisDayGameViewModel.EXTRA_DATE, Prefs.lastOtdGameDateOverride) } } } diff --git a/app/src/main/java/org/wikipedia/games/onthisday/OnThisDayGameBaseFragment.kt b/app/src/main/java/org/wikipedia/games/onthisday/OnThisDayGameBaseFragment.kt index 65f052b4f87..9089c4d72f4 100644 --- a/app/src/main/java/org/wikipedia/games/onthisday/OnThisDayGameBaseFragment.kt +++ b/app/src/main/java/org/wikipedia/games/onthisday/OnThisDayGameBaseFragment.kt @@ -19,15 +19,13 @@ import org.wikipedia.analytics.eventplatform.WikiGamesEvent import org.wikipedia.games.onthisday.OnThisDayGameViewModel.Companion.LANG_CODES_SUPPORTED import org.wikipedia.games.onthisday.OnThisDayGameViewModel.Companion.dateReleasedForLang import org.wikipedia.util.log.L +import java.time.Instant import java.time.LocalDate import java.time.ZoneId -import java.time.ZoneOffset -import java.util.Calendar -import java.util.Date -import java.util.TimeZone +import java.time.temporal.ChronoUnit abstract class OnThisDayGameBaseFragment : Fragment() { - private var scoreData: Map = emptyMap() + private var scoreData = emptyMap() private val fragmentLifecycleCallbacks = object : FragmentManager.FragmentLifecycleCallbacks() { @SuppressLint("RestrictedApi") @@ -38,7 +36,7 @@ abstract class OnThisDayGameBaseFragment : Fragment() { (calendar as MaterialCalendar?)?.addOnSelectionChangedListener(object : OnSelectionChangedListener() { override fun onSelectionChanged(selection: Long) { - maybeShowToastForDate(selection, scoreData) + maybeShowToastForDate(selection) } }) } @@ -58,90 +56,66 @@ abstract class OnThisDayGameBaseFragment : Fragment() { protected fun prepareAndOpenArchiveCalendar(viewModel: OnThisDayGameViewModel) { lifecycleScope.launch { val startDateBasedOnLanguage = LANG_CODES_SUPPORTED.associateWith { dateReleasedForLang(it) } - val localDate = startDateBasedOnLanguage[viewModel.wikiSite.languageCode] - val startDate = Date.from(localDate?.atStartOfDay(ZoneId.systemDefault())?.toInstant()) + val startDate = startDateBasedOnLanguage[viewModel.wikiSite.languageCode] ?: return@launch scoreData = viewModel.getDataForArchiveCalendar(language = viewModel.wikiSite.languageCode) - showArchiveCalendar( - startDate, - Date(), - scoreData, - onDateSelected = { selectedDateInMillis -> - handleDateSelection(selectedDateInMillis) - } - ) + showArchiveCalendar(startDate) } } - private fun showArchiveCalendar(startDate: Date, endDate: Date, scoreData: Map, onDateSelected: (Long) -> Unit) { - val startTimeInMillis = startDate.time - val endTimeInMillis = endDate.time + private fun showArchiveCalendar(startDate: LocalDate) { + val startInstant = startDate.atStartOfDay(ZoneId.systemDefault()).toInstant() + val startTimeInMillis = startInstant.toEpochMilli() + val endInstant = Instant.now() + val endTimeInMillis = endInstant.toEpochMilli() + val oneDayBeforeStart = startInstant.minus(1, ChronoUnit.DAYS).toEpochMilli() val calendarConstraints = CalendarConstraints.Builder() - .setStart(startDate.time) + .setStart(startTimeInMillis) .setEnd(endTimeInMillis) .setValidator( CompositeDateValidator.allOf( listOf( - DateValidatorPointForward.from(startTimeInMillis - (24 * 60 * 60 * 1000)), + DateValidatorPointForward.from(oneDayBeforeStart), DateValidatorPointBackward.before(endTimeInMillis) ) ) ) .build() + val endDate = LocalDate.ofInstant(endInstant, ZoneId.systemDefault()) val datePicker = MaterialDatePicker.Builder.datePicker() .setTitleText(getString(R.string.on_this_day_game_archive_calendar_title)) .setTheme(R.style.MaterialDatePickerStyle) - .setDayViewDecorator( - DateDecorator( - startDate, - endDate, - scoreData - ) - ) + .setDayViewDecorator(DateDecorator(startDate, endDate, scoreData)) .setCalendarConstraints(calendarConstraints) .setSelection(endTimeInMillis) .build() .apply { - addOnPositiveButtonClickListener { selectedDateInMillis -> - onDateSelected(selectedDateInMillis) - } + addOnPositiveButtonClickListener(::handleDateSelection) } datePicker.show(childFragmentManager, "datePicker") } private fun handleDateSelection(selectedDateInMillis: Long) { - val calendar = Calendar.getInstance(TimeZone.getTimeZone(ZoneOffset.UTC)) - calendar.timeInMillis = selectedDateInMillis - val year = calendar.get(Calendar.YEAR) - val month = calendar.get(Calendar.MONTH) + 1 - val day = calendar.get(Calendar.DAY_OF_MONTH) - val scoreDataKey = DateDecorator.getDateKey(year, month, day) - if (scoreData[scoreDataKey] != null) { + val localDate = LocalDate.ofInstant(Instant.ofEpochMilli(selectedDateInMillis), + ZoneId.systemDefault()) + if (scoreData[localDate] != null) { return } WikiGamesEvent.submit("date_select", "game_play", slideName = "archive_calendar") - onArchiveDateSelected(LocalDate.of(year, month, day)) + onArchiveDateSelected(localDate) } abstract fun onArchiveDateSelected(date: LocalDate) - private fun maybeShowToastForDate(selectedDateInMillis: Long, scoreData: Map) { - val calendar = Calendar.getInstance(TimeZone.getTimeZone(ZoneOffset.UTC)) - calendar.timeInMillis = selectedDateInMillis - val year = calendar.get(Calendar.YEAR) - val month = calendar.get(Calendar.MONTH) - val day = calendar.get(Calendar.DAY_OF_MONTH) - val scoreDataKey = DateDecorator.getDateKey(year, month + 1, day) - if (scoreData[scoreDataKey] != null) { - Toast.makeText( - requireContext(), - getString( - R.string.on_this_day_game_score_toast_message, - scoreData[scoreDataKey], - OnThisDayGameViewModel.MAX_QUESTIONS - ), Toast.LENGTH_SHORT - ).show() + private fun maybeShowToastForDate(selectedDateInMillis: Long) { + val localDate = LocalDate.ofInstant(Instant.ofEpochMilli(selectedDateInMillis), + ZoneId.systemDefault()) + val score = scoreData[localDate] + if (score != null) { + val message = getString(R.string.on_this_day_game_score_toast_message, score, + OnThisDayGameViewModel.MAX_QUESTIONS) + Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT).show() } } diff --git a/app/src/main/java/org/wikipedia/games/onthisday/OnThisDayGameViewModel.kt b/app/src/main/java/org/wikipedia/games/onthisday/OnThisDayGameViewModel.kt index fae912eda6c..ba3363719a4 100644 --- a/app/src/main/java/org/wikipedia/games/onthisday/OnThisDayGameViewModel.kt +++ b/app/src/main/java/org/wikipedia/games/onthisday/OnThisDayGameViewModel.kt @@ -27,9 +27,7 @@ import org.wikipedia.settings.Prefs import org.wikipedia.util.ReleaseUtil import org.wikipedia.util.Resource import org.wikipedia.util.log.L -import java.time.Instant import java.time.LocalDate -import java.time.ZoneOffset import java.time.format.DateTimeFormatter import kotlin.math.abs import kotlin.math.max @@ -49,7 +47,7 @@ class OnThisDayGameViewModel(savedStateHandle: SavedStateHandle) : ViewModel() { private lateinit var currentState: GameState private val overrideDate = savedStateHandle.contains(EXTRA_DATE) - var currentDate = if (overrideDate) LocalDate.ofInstant(Instant.ofEpochSecond(savedStateHandle.get(EXTRA_DATE)!!), ZoneOffset.UTC) else LocalDate.now() + var currentDate: LocalDate = savedStateHandle.get(EXTRA_DATE) ?: LocalDate.now() val currentMonth get() = currentDate.monthValue val currentDay get() = currentDate.dayOfMonth var isArchiveGame = false @@ -312,13 +310,12 @@ class OnThisDayGameViewModel(savedStateHandle: SavedStateHandle) : ViewModel() { return event.pages.firstOrNull { !it.thumbnailUrl.isNullOrEmpty() }?.thumbnailUrl } - suspend fun getDataForArchiveCalendar(gameName: Int = WikiGames.WHICH_CAME_FIRST.ordinal, language: String): Map { + suspend fun getDataForArchiveCalendar( + gameName: Int = WikiGames.WHICH_CAME_FIRST.ordinal, + language: String + ): Map { val history = AppDatabase.instance.dailyGameHistoryDao().getGameHistory(gameName, language) - val map = history.associate { - val scoreKey = DateDecorator.getDateKey(it.year, it.month, it.day) - scoreKey to it.score - } - return map + return history.associate { it.date to it.score } } fun getCurrentGameState(): GameState { diff --git a/app/src/main/java/org/wikipedia/settings/Prefs.kt b/app/src/main/java/org/wikipedia/settings/Prefs.kt index 57c7b53c1be..9cc8d28fd7f 100644 --- a/app/src/main/java/org/wikipedia/settings/Prefs.kt +++ b/app/src/main/java/org/wikipedia/settings/Prefs.kt @@ -31,6 +31,8 @@ import org.wikipedia.util.StringUtil import org.wikipedia.watchlist.WatchlistFilterTypes import org.wikipedia.yearinreview.YearInReviewModel import org.wikipedia.yearinreview.YearInReviewSurveyState +import java.time.LocalDate +import java.time.format.DateTimeParseException import java.util.Date /** Shared preferences utility for convenient POJO access. */ @@ -739,9 +741,13 @@ object Prefs { get() = JsonUtil.decodeFromString>(PrefsIoUtil.getString(R.string.preference_key_donation_results, null)).orEmpty() set(value) = PrefsIoUtil.setString(R.string.preference_key_donation_results, JsonUtil.encodeToString(value)) - var lastOtdGameDateOverride - get() = PrefsIoUtil.getString(R.string.preference_key_otd_game_date_override, null).orEmpty() - set(value) = PrefsIoUtil.setString(R.string.preference_key_otd_game_date_override, value) + var lastOtdGameDateOverride: LocalDate? + get() = try { + LocalDate.parse(PrefsIoUtil.getString(R.string.preference_key_otd_game_date_override, null).orEmpty()) + } catch (_: DateTimeParseException) { + null + } + set(value) = PrefsIoUtil.setString(R.string.preference_key_otd_game_date_override, value?.toString()) var otdGameState get() = PrefsIoUtil.getString(R.string.preference_key_otd_game_state, null).orEmpty()