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/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 c33c9c9dc1b..29b195330e2 100644 --- a/app/src/main/java/org/wikipedia/games/onthisday/OnThisDayGameViewModel.kt +++ b/app/src/main/java/org/wikipedia/games/onthisday/OnThisDayGameViewModel.kt @@ -312,13 +312,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 {