Skip to content
Original file line number Diff line number Diff line change
@@ -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(
Expand All @@ -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)
}
63 changes: 11 additions & 52 deletions app/src/main/java/org/wikipedia/games/onthisday/DateDecorator.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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<Long, Int>
private val startDate: LocalDate,
private val endDate: LocalDate,
private val scoreData: Map<LocalDate, Int>
) : 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,
Expand All @@ -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))
Expand All @@ -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<DateDecorator> {
override fun createFromParcel(parcel: Parcel): DateDecorator {
return DateDecorator(parcel)
}

override fun newArray(size: Int): Array<DateDecorator?> {
return arrayOfNulls(size)
}

fun getDateKey(year: Int, month: Int, day: Int): Long {
return (year * 10000 + month * 100 + day).toLong()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down Expand Up @@ -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)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<Long, Int> = emptyMap()
private var scoreData = emptyMap<LocalDate, Int>()

private val fragmentLifecycleCallbacks = object : FragmentManager.FragmentLifecycleCallbacks() {
@SuppressLint("RestrictedApi")
Expand All @@ -38,7 +36,7 @@ abstract class OnThisDayGameBaseFragment : Fragment() {
(calendar as MaterialCalendar<Long>?)?.addOnSelectionChangedListener(object :
OnSelectionChangedListener<Long>() {
override fun onSelectionChanged(selection: Long) {
maybeShowToastForDate(selection, scoreData)
maybeShowToastForDate(selection)
}
})
}
Expand All @@ -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<Long, Int>, 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<Long, Int>) {
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()
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<Long>(EXTRA_DATE)!!), ZoneOffset.UTC) else LocalDate.now()
var currentDate: LocalDate = savedStateHandle.get<LocalDate>(EXTRA_DATE) ?: LocalDate.now()
val currentMonth get() = currentDate.monthValue
val currentDay get() = currentDate.dayOfMonth
var isArchiveGame = false
Expand Down Expand Up @@ -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<Long, Int> {
suspend fun getDataForArchiveCalendar(
gameName: Int = WikiGames.WHICH_CAME_FIRST.ordinal,
language: String
): Map<LocalDate, Int> {
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 {
Expand Down
12 changes: 9 additions & 3 deletions app/src/main/java/org/wikipedia/settings/Prefs.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand Down Expand Up @@ -743,9 +745,13 @@ object Prefs {
get() = JsonUtil.decodeFromString<List<DonationResult>>(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()
Expand Down
Loading