-
Notifications
You must be signed in to change notification settings - Fork 527
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fixes #3841 'time_ago' & quantity plurals should be combined #4533
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @mbobiosio. I took a first pass and had some comments--PTAL.
Also, CI checks are failing--please fix.
@@ -16,10 +16,14 @@ | |||
import org.oppia.android.util.system.OppiaClock; | |||
import org.oppia.android.util.system.OppiaClockInjectorProvider; | |||
|
|||
/** Holds all custom binding adapters that bind to [TextView]. */ | |||
/** | |||
* Holds all custom binding adapters that bind to [TextView]. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why this change? Per https://developer.android.com/kotlin/style-guide#formatting_2 the previous version is acceptable (and is what we use for such cases). While I linked to the KDoc one, the same also applies for Java: https://google.github.io/styleguide/javaguide.html#s7.1-javadoc-formatting.
Ditto for the others.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Corrected this
return resourceHandler.getStringInLocaleWithWrapping( | ||
R.string.time_ago, | ||
resourceHandler.getQuantityStringInLocaleWithWrapping( | ||
return resourceHandler.getQuantityStringInLocaleWithWrapping( | ||
pluralsResId, count, String.valueOf(count) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this indentation still correct?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes this is correct
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) | ||
fragment.startActivity(intent) | ||
} | ||
fragment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not opposed to including this & other cleanup changes in this PR, but something to keep in mind in the future is that it's not always best practice to include unrelated changes in a PR to the central thing the PR is trying to accomplish. Doing so actually introduces some risk in that it can cause the PR to need to be reverted if the refactor has an unexpected behavior change, or it can be difficult to revert the PR due to a larger number of changes than what's actually necessary to accomplish its goal.
} | ||
|
||
companion object { | ||
fun currentDate() = Calendar.getInstance().timeInMillis |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this being used vs. passing in the current time? I think we conventionally use OppiaClock for calculating the current time, so I'm trying to understand this difference.
|
||
import android.content.Context | ||
import android.widget.Toast |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unused import--please remove.
<string name="minute_ago">a minute ago</string> | ||
<string name="minutes_ago">%s minutes ago</string> | ||
<string name="hour_ago">an hour ago</string> | ||
<string name="hours_ago">%s hours ago</string> | ||
<string name="days_ago">%s days ago</string> | ||
<string name="month_ago">a month ago</string> | ||
<string name="months_ago">%s months ago</string> | ||
<string name="year_ago">a year ago</string> | ||
<string name="years_ago">%s years ago</string> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are these still needed? It seems like your change is only using the new plurals.
Also, are there any old strings that can now be removed in favor of the new solution?
* Returns the readable string of the duration from the provided time in [Long]. | ||
*/ | ||
fun timeAgoFromTimestamp(timestamp: Long, referenceTime: Long): String { | ||
val diff = (currentDate() - timestamp) / MILLI_SECONDS |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For this & the constants below, could we use TimeUnit
, instead? That both avoids having to do time math (which can be easily misunderstood and/or written), and may help improve readability. An example of what this might look like:
val diffSeconds = TimeUnit.MILLISECONDS.toSeconds(currentTimeMillis - timestampMillis)
return when {
diffSeconds < TimeUnit.MINUTES.toSeconds(1) -> ...
diffSeconds < TimeUnit.HOURS.toSeconds(1) -> ...
...
}
I think TimeUnit
could also simplify your diffTo*
functions by providing easier conversions. There might even be a way to generalize this slightly, e.g. something like:
private val timestampFormatters by lazy {
listOf(
TimestampFormatter(threshold = TimeMoment.ONE_MINUTE, timeUnit = TimeUnit.SECONDS, formatTimestamp = this::formatSecondsTimestamp),
TimestampFormatter(threshold = TimeMoment.ONE_HOUR, timeUnit = TimeUnit.MINUTES, formatTimestamp = this::formatMinutesTimestamp),
...
)
}
fun formatTimeAgoTimestamp(timestampMillis: Long): String {
val timestamp = TimeMoment(amount = timestampMillis, unit = TimeUnit.MILLISECONDS)
val currentTime = TimeMoment(amount = oppiaClock.getCurrentTimeMillis(), unit = TimeUnit.MILLISECONDS)
val timeSpent = currentTime - timestamp
return timestampFormatters.firstOrNull {
it.canFormatWithinThreshold(timeSpent)
}?.formatString(timeSpent) ?: error("Encountered impossible time: $timeSpent (for time: $timestamp)")
}
private class TimestampFormatter(val threshold: TimeSpan, val timeUnit: TimeUnit, val formatTimestamp: (TimeMoment) -> String) {
fun canFormatWithinThreshold(timestamp: TimeMoment): Boolean = timestamp.isEarlierThan(threshold)
fun format(timestamp: TimeMoment): String = formatTimestamp(timestamp.convertTo(timeUnit))
}
private fun formatSecondsTimestamp(seconds: TimeMoment): String =
resourceHandler.getStringInLocale(R.string.just_now)
private fun formatMinutesTimestamp(minutes: TimeMoment): String {
return resourceHandler.getQuantityStringInLocaleWithWrapping(
R.plurals.minutes_ago, minutes.asInt, minutes.asString
)
}
...
// A simple alternative for Java 8-only time utilities.
private data class TimeMoment(val amount: Long, val unit: TimeUnit) {
val inMilliseconds: Long
get() = TimeUnit.MILLISECONDS.convert(amount, unit)
val asInt: Int
get() = amount.toInt()
val asString: String
get() = amount.toString()
fun isEarlierThan(other: TimeMoment): Boolean = inMilliseconds < other.inMilliseconds
fun convertTo(unit: TimeUnit): TimeMoment = TimeMoment(unit.convert(amount, this.unit), unit)
operator fun minus(other: TimeMoment): TimeMoment =
TimeMoment(inMilliseconds - other.inMilliseconds, TimeUnit.MILLISECONDS)
companion object {
val ONE_MINUTE = TimeMoment(amount = 1, unit = TimeUnit.MINUTES)
val ONE_HOUR = TimeMoment(amount = 1, unit = TimeUnit.HOURS)
...
}
}
(As this is just a concept, more work & reformatting is needed).
/** | ||
* Returns the readable string of the duration from the provided time in [Long]. | ||
*/ | ||
fun timeAgoFromTimestamp(timestamp: Long, referenceTime: Long): String { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Method names should be verbs or verb phrases describing what's happening (e.g. formatting, or converting, in this case).
/** | ||
* Returns the readable string of the duration from the provided time in [Long]. | ||
*/ | ||
fun timeAgoFromTimestamp(timestamp: Long, referenceTime: Long): String { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add new tests for this method to cover all possible pluralizations that should now be supported.
import org.oppia.android.app.translation.AppLanguageActivityInjectorProvider | ||
import org.oppia.android.app.translation.AppLanguageResourceHandler | ||
|
||
@BindingAdapter("profile:lastVisited") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are these new binding adapters needed for this PR?
Hi @mbobiosio, I'm going to mark this PR as stale because it hasn't had any updates for 7 days. If no further activity occurs within 7 days, it will be automatically closed so that others can take up the issue. |
Explanation
Fixes #3841
Fixes #3842
This PR includes the required update to combine strings together
Essential Checklist
For UI-specific PRs only
If your PR includes UI-related changes, then: