Loading app/ui/legacy/src/main/java/com/fsck/k9/fragment/MessageListAdapter.kt +4 −3 Original line number Diff line number Diff line Loading @@ -8,7 +8,6 @@ import android.graphics.Typeface import android.graphics.drawable.Drawable import android.text.Spannable import android.text.SpannableStringBuilder import android.text.format.DateUtils import android.text.style.AbsoluteSizeSpan import android.text.style.ForegroundColorSpan import android.text.style.StyleSpan Loading @@ -26,6 +25,7 @@ import com.fsck.k9.contacts.ContactPictureLoader import com.fsck.k9.controller.MessageReference import com.fsck.k9.mail.Address import com.fsck.k9.ui.R import com.fsck.k9.ui.helper.RelativeDateTimeFormatter import com.fsck.k9.ui.messagelist.MessageListAppearance import com.fsck.k9.ui.messagelist.MessageListItem import com.fsck.k9.ui.resolveColorAttribute Loading @@ -39,7 +39,8 @@ class MessageListAdapter internal constructor( private val layoutInflater: LayoutInflater, private val contactsPictureLoader: ContactPictureLoader, private val listItemListener: MessageListItemActionListener, private val appearance: MessageListAppearance private val appearance: MessageListAppearance, private val relativeDateTimeFormatter: RelativeDateTimeFormatter ) : BaseAdapter() { private val forwardedIcon: Drawable = theme.resolveDrawableAttribute(R.attr.messageListForwarded) Loading Loading @@ -153,7 +154,7 @@ class MessageListAdapter internal constructor( with(message) { val maybeBoldTypeface = if (isRead) Typeface.NORMAL else Typeface.BOLD val displayDate = DateUtils.getRelativeTimeSpanString(context, messageDate) val displayDate = relativeDateTimeFormatter.formatDate(messageDate) val displayThreadCount = if (appearance.showingThreadedList) threadCount else 0 val subject = MlfUtils.buildSubject(subject, res.getString(R.string.general_no_subject), displayThreadCount) Loading app/ui/legacy/src/main/java/com/fsck/k9/fragment/MessageListFragment.java +4 −1 Original line number Diff line number Diff line Loading @@ -40,6 +40,7 @@ import androidx.localbroadcastmanager.content.LocalBroadcastManager; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import com.fsck.k9.Account; import com.fsck.k9.Account.SortType; import com.fsck.k9.Clock; import com.fsck.k9.DI; import com.fsck.k9.K9; import com.fsck.k9.Preferences; Loading @@ -60,6 +61,7 @@ import com.fsck.k9.search.LocalSearch; import com.fsck.k9.ui.R; import com.fsck.k9.ui.folders.FolderNameFormatter; import com.fsck.k9.ui.folders.FolderNameFormatterFactory; import com.fsck.k9.ui.helper.RelativeDateTimeFormatter; import com.fsck.k9.ui.messagelist.MessageListAppearance; import com.fsck.k9.ui.messagelist.MessageListConfig; import com.fsck.k9.ui.messagelist.MessageListFragmentDiContainer; Loading Loading @@ -475,7 +477,8 @@ public class MessageListFragment extends Fragment implements OnItemClickListener layoutInflater, ContactPicture.getContactPictureLoader(), this, getMessageListAppearance() getMessageListAppearance(), new RelativeDateTimeFormatter(requireContext(), Clock.INSTANCE) ); if (singleFolderMode) { Loading app/ui/legacy/src/main/java/com/fsck/k9/ui/helper/RelativeDateTimeFormatter.kt 0 → 100644 +47 −0 Original line number Diff line number Diff line package com.fsck.k9.ui.helper import android.content.Context import android.text.format.DateUtils import android.text.format.DateUtils.FORMAT_ABBREV_MONTH import android.text.format.DateUtils.FORMAT_ABBREV_WEEKDAY import android.text.format.DateUtils.FORMAT_NUMERIC_DATE import android.text.format.DateUtils.FORMAT_SHOW_DATE import android.text.format.DateUtils.FORMAT_SHOW_TIME import android.text.format.DateUtils.FORMAT_SHOW_WEEKDAY import android.text.format.DateUtils.FORMAT_SHOW_YEAR import com.fsck.k9.Clock import java.util.Calendar import java.util.Calendar.DAY_OF_WEEK import java.util.Calendar.YEAR /** * Formatter to describe timestamps as a time relative to now. */ class RelativeDateTimeFormatter(private val context: Context, private val clock: Clock) { fun formatDate(timestamp: Long): String { val now = clock.time.toCalendar() val date = timestamp.toCalendar() val format = when { date.isToday() -> FORMAT_SHOW_TIME date.isWithinPastSevenDaysOf(now) -> FORMAT_SHOW_WEEKDAY or FORMAT_ABBREV_WEEKDAY date.isSameYearAs(now) -> FORMAT_SHOW_DATE or FORMAT_ABBREV_MONTH else -> FORMAT_SHOW_DATE or FORMAT_SHOW_YEAR or FORMAT_NUMERIC_DATE } return DateUtils.formatDateRange(context, timestamp, timestamp, format) } } private fun Long.toCalendar(): Calendar { val calendar = Calendar.getInstance() calendar.timeInMillis = this return calendar } private fun Calendar.isToday() = DateUtils.isToday(this.timeInMillis) private fun Calendar.isWithinPastSevenDaysOf(other: Calendar) = this.before(other) && DateUtils.WEEK_IN_MILLIS > other.timeInMillis - this.timeInMillis && this[DAY_OF_WEEK] != other[DAY_OF_WEEK] private fun Calendar.isSameYearAs(other: Calendar) = this[YEAR] == other[YEAR] app/ui/legacy/src/test/java/com/fsck/k9/fragment/MessageListAdapterTest.kt +10 −7 Original line number Diff line number Diff line Loading @@ -13,6 +13,7 @@ import androidx.appcompat.app.AppCompatActivity import androidx.core.view.isGone import androidx.core.view.isVisible import com.fsck.k9.Account import com.fsck.k9.Clock import com.fsck.k9.FontSizes import com.fsck.k9.FontSizes.FONT_DEFAULT import com.fsck.k9.FontSizes.LARGE Loading @@ -21,6 +22,7 @@ import com.fsck.k9.contacts.ContactPictureLoader import com.fsck.k9.mail.Address import com.fsck.k9.textString import com.fsck.k9.ui.R import com.fsck.k9.ui.helper.RelativeDateTimeFormatter import com.fsck.k9.ui.messagelist.MessageListAppearance import com.fsck.k9.ui.messagelist.MessageListItem import com.nhaarman.mockitokotlin2.mock Loading Loading @@ -461,7 +463,8 @@ class MessageListAdapterTest : RobolectricTest() { layoutInflater = LayoutInflater.from(context), contactsPictureLoader = contactsPictureLoader, listItemListener = listItemListener, appearance = appearance appearance = appearance, relativeDateTimeFormatter = RelativeDateTimeFormatter(context, Clock.INSTANCE) ) } Loading app/ui/legacy/src/test/java/com/fsck/k9/ui/helper/RelativeDateTimeFormatterTest.kt 0 → 100644 +132 −0 Original line number Diff line number Diff line package com.fsck.k9.ui.helper import android.os.SystemClock import com.fsck.k9.Clock import com.fsck.k9.RobolectricTest import com.nhaarman.mockitokotlin2.whenever import java.time.LocalDate import java.time.LocalDateTime import java.time.ZoneId import java.util.TimeZone import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Test import org.mockito.Mockito import org.robolectric.RuntimeEnvironment import org.robolectric.annotation.Config @Config(qualifiers = "en") class RelativeDateTimeFormatterTest : RobolectricTest() { private val context = RuntimeEnvironment.application.applicationContext private val clock = Mockito.mock(Clock::class.java) private val dateTimeFormatter = RelativeDateTimeFormatter(context, clock) private val zoneId = "Europe/Berlin" @Before fun setUp() { TimeZone.setDefault(TimeZone.getTimeZone(zoneId)) } @Test fun inFiveMinutesOnNextDay_shouldReturnDay() { setClockTo("2020-05-17T23:58") val date = "2020-05-18T00:03".toEpochMillis() val displayDate = dateTimeFormatter.formatDate(date) assertEquals("May 18", displayDate) } @Test fun oneMinuteAgo_shouldReturnTime() { setClockTo("2020-05-17T15:42") val date = "2020-05-17T15:41".toEpochMillis() val displayDate = dateTimeFormatter.formatDate(date) assertEquals("3:41 PM", displayDate) } @Test fun sixHoursAgo_shouldReturnTime() { setClockTo("2020-05-17T15:42") val date = "2020-05-17T09:42".toEpochMillis() val displayDate = dateTimeFormatter.formatDate(date) assertEquals("9:42 AM", displayDate) } @Test fun yesterday_shouldReturnWeekday() { setClockTo("2020-05-17T15:42") val date = "2020-05-16T15:42".toEpochMillis() val displayDate = dateTimeFormatter.formatDate(date) assertEquals("Sat", displayDate) } @Test fun sixDaysAgo_shouldReturnWeekday() { setClockTo("2020-05-17T15:42") val date = "2020-05-11T09:42".toEpochMillis() val displayDate = dateTimeFormatter.formatDate(date) assertEquals("Mon", displayDate) } @Test fun sixDaysAndTwentyHours_shouldReturnDay() { setClockTo("2020-05-17T15:42") val date = "2020-05-10T17:42".toEpochMillis() val displayDate = dateTimeFormatter.formatDate(date) assertEquals("May 10", displayDate) } @Test fun sevenDaysAndTwoHours_shouldReturnDay() { setClockTo("2020-05-17T15:42") val date = "2020-05-10T13:42".toEpochMillis() val displayDate = dateTimeFormatter.formatDate(date) assertEquals("May 10", displayDate) } @Test fun startOfYear_shouldReturnDay() { setClockTo("2020-05-17T15:42") val date = LocalDate.parse("2020-01-01").atStartOfDay().toEpochMillis() val displayDate = dateTimeFormatter.formatDate(date) assertEquals("Jan 1", displayDate) } @Test fun endOfLastYear_shouldReturnDate() { setClockTo("2020-05-17T15:42") val date = LocalDateTime.parse("2019-12-31T23:59").toEpochMillis() val displayDate = dateTimeFormatter.formatDate(date) assertEquals("12/31/2019", displayDate) } private fun setClockTo(time: String) { val dateTime = LocalDateTime.parse(time) val timeInMillis = dateTime.toEpochMillis() SystemClock.setCurrentTimeMillis(timeInMillis) // Is handled by ShadowSystemClock whenever(clock.time).thenReturn(timeInMillis) } private fun String.toEpochMillis() = LocalDateTime.parse(this).toEpochMillis() private fun LocalDateTime.toEpochMillis() = this.atZone(ZoneId.of(zoneId)).toInstant().toEpochMilli() } Loading
app/ui/legacy/src/main/java/com/fsck/k9/fragment/MessageListAdapter.kt +4 −3 Original line number Diff line number Diff line Loading @@ -8,7 +8,6 @@ import android.graphics.Typeface import android.graphics.drawable.Drawable import android.text.Spannable import android.text.SpannableStringBuilder import android.text.format.DateUtils import android.text.style.AbsoluteSizeSpan import android.text.style.ForegroundColorSpan import android.text.style.StyleSpan Loading @@ -26,6 +25,7 @@ import com.fsck.k9.contacts.ContactPictureLoader import com.fsck.k9.controller.MessageReference import com.fsck.k9.mail.Address import com.fsck.k9.ui.R import com.fsck.k9.ui.helper.RelativeDateTimeFormatter import com.fsck.k9.ui.messagelist.MessageListAppearance import com.fsck.k9.ui.messagelist.MessageListItem import com.fsck.k9.ui.resolveColorAttribute Loading @@ -39,7 +39,8 @@ class MessageListAdapter internal constructor( private val layoutInflater: LayoutInflater, private val contactsPictureLoader: ContactPictureLoader, private val listItemListener: MessageListItemActionListener, private val appearance: MessageListAppearance private val appearance: MessageListAppearance, private val relativeDateTimeFormatter: RelativeDateTimeFormatter ) : BaseAdapter() { private val forwardedIcon: Drawable = theme.resolveDrawableAttribute(R.attr.messageListForwarded) Loading Loading @@ -153,7 +154,7 @@ class MessageListAdapter internal constructor( with(message) { val maybeBoldTypeface = if (isRead) Typeface.NORMAL else Typeface.BOLD val displayDate = DateUtils.getRelativeTimeSpanString(context, messageDate) val displayDate = relativeDateTimeFormatter.formatDate(messageDate) val displayThreadCount = if (appearance.showingThreadedList) threadCount else 0 val subject = MlfUtils.buildSubject(subject, res.getString(R.string.general_no_subject), displayThreadCount) Loading
app/ui/legacy/src/main/java/com/fsck/k9/fragment/MessageListFragment.java +4 −1 Original line number Diff line number Diff line Loading @@ -40,6 +40,7 @@ import androidx.localbroadcastmanager.content.LocalBroadcastManager; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import com.fsck.k9.Account; import com.fsck.k9.Account.SortType; import com.fsck.k9.Clock; import com.fsck.k9.DI; import com.fsck.k9.K9; import com.fsck.k9.Preferences; Loading @@ -60,6 +61,7 @@ import com.fsck.k9.search.LocalSearch; import com.fsck.k9.ui.R; import com.fsck.k9.ui.folders.FolderNameFormatter; import com.fsck.k9.ui.folders.FolderNameFormatterFactory; import com.fsck.k9.ui.helper.RelativeDateTimeFormatter; import com.fsck.k9.ui.messagelist.MessageListAppearance; import com.fsck.k9.ui.messagelist.MessageListConfig; import com.fsck.k9.ui.messagelist.MessageListFragmentDiContainer; Loading Loading @@ -475,7 +477,8 @@ public class MessageListFragment extends Fragment implements OnItemClickListener layoutInflater, ContactPicture.getContactPictureLoader(), this, getMessageListAppearance() getMessageListAppearance(), new RelativeDateTimeFormatter(requireContext(), Clock.INSTANCE) ); if (singleFolderMode) { Loading
app/ui/legacy/src/main/java/com/fsck/k9/ui/helper/RelativeDateTimeFormatter.kt 0 → 100644 +47 −0 Original line number Diff line number Diff line package com.fsck.k9.ui.helper import android.content.Context import android.text.format.DateUtils import android.text.format.DateUtils.FORMAT_ABBREV_MONTH import android.text.format.DateUtils.FORMAT_ABBREV_WEEKDAY import android.text.format.DateUtils.FORMAT_NUMERIC_DATE import android.text.format.DateUtils.FORMAT_SHOW_DATE import android.text.format.DateUtils.FORMAT_SHOW_TIME import android.text.format.DateUtils.FORMAT_SHOW_WEEKDAY import android.text.format.DateUtils.FORMAT_SHOW_YEAR import com.fsck.k9.Clock import java.util.Calendar import java.util.Calendar.DAY_OF_WEEK import java.util.Calendar.YEAR /** * Formatter to describe timestamps as a time relative to now. */ class RelativeDateTimeFormatter(private val context: Context, private val clock: Clock) { fun formatDate(timestamp: Long): String { val now = clock.time.toCalendar() val date = timestamp.toCalendar() val format = when { date.isToday() -> FORMAT_SHOW_TIME date.isWithinPastSevenDaysOf(now) -> FORMAT_SHOW_WEEKDAY or FORMAT_ABBREV_WEEKDAY date.isSameYearAs(now) -> FORMAT_SHOW_DATE or FORMAT_ABBREV_MONTH else -> FORMAT_SHOW_DATE or FORMAT_SHOW_YEAR or FORMAT_NUMERIC_DATE } return DateUtils.formatDateRange(context, timestamp, timestamp, format) } } private fun Long.toCalendar(): Calendar { val calendar = Calendar.getInstance() calendar.timeInMillis = this return calendar } private fun Calendar.isToday() = DateUtils.isToday(this.timeInMillis) private fun Calendar.isWithinPastSevenDaysOf(other: Calendar) = this.before(other) && DateUtils.WEEK_IN_MILLIS > other.timeInMillis - this.timeInMillis && this[DAY_OF_WEEK] != other[DAY_OF_WEEK] private fun Calendar.isSameYearAs(other: Calendar) = this[YEAR] == other[YEAR]
app/ui/legacy/src/test/java/com/fsck/k9/fragment/MessageListAdapterTest.kt +10 −7 Original line number Diff line number Diff line Loading @@ -13,6 +13,7 @@ import androidx.appcompat.app.AppCompatActivity import androidx.core.view.isGone import androidx.core.view.isVisible import com.fsck.k9.Account import com.fsck.k9.Clock import com.fsck.k9.FontSizes import com.fsck.k9.FontSizes.FONT_DEFAULT import com.fsck.k9.FontSizes.LARGE Loading @@ -21,6 +22,7 @@ import com.fsck.k9.contacts.ContactPictureLoader import com.fsck.k9.mail.Address import com.fsck.k9.textString import com.fsck.k9.ui.R import com.fsck.k9.ui.helper.RelativeDateTimeFormatter import com.fsck.k9.ui.messagelist.MessageListAppearance import com.fsck.k9.ui.messagelist.MessageListItem import com.nhaarman.mockitokotlin2.mock Loading Loading @@ -461,7 +463,8 @@ class MessageListAdapterTest : RobolectricTest() { layoutInflater = LayoutInflater.from(context), contactsPictureLoader = contactsPictureLoader, listItemListener = listItemListener, appearance = appearance appearance = appearance, relativeDateTimeFormatter = RelativeDateTimeFormatter(context, Clock.INSTANCE) ) } Loading
app/ui/legacy/src/test/java/com/fsck/k9/ui/helper/RelativeDateTimeFormatterTest.kt 0 → 100644 +132 −0 Original line number Diff line number Diff line package com.fsck.k9.ui.helper import android.os.SystemClock import com.fsck.k9.Clock import com.fsck.k9.RobolectricTest import com.nhaarman.mockitokotlin2.whenever import java.time.LocalDate import java.time.LocalDateTime import java.time.ZoneId import java.util.TimeZone import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Test import org.mockito.Mockito import org.robolectric.RuntimeEnvironment import org.robolectric.annotation.Config @Config(qualifiers = "en") class RelativeDateTimeFormatterTest : RobolectricTest() { private val context = RuntimeEnvironment.application.applicationContext private val clock = Mockito.mock(Clock::class.java) private val dateTimeFormatter = RelativeDateTimeFormatter(context, clock) private val zoneId = "Europe/Berlin" @Before fun setUp() { TimeZone.setDefault(TimeZone.getTimeZone(zoneId)) } @Test fun inFiveMinutesOnNextDay_shouldReturnDay() { setClockTo("2020-05-17T23:58") val date = "2020-05-18T00:03".toEpochMillis() val displayDate = dateTimeFormatter.formatDate(date) assertEquals("May 18", displayDate) } @Test fun oneMinuteAgo_shouldReturnTime() { setClockTo("2020-05-17T15:42") val date = "2020-05-17T15:41".toEpochMillis() val displayDate = dateTimeFormatter.formatDate(date) assertEquals("3:41 PM", displayDate) } @Test fun sixHoursAgo_shouldReturnTime() { setClockTo("2020-05-17T15:42") val date = "2020-05-17T09:42".toEpochMillis() val displayDate = dateTimeFormatter.formatDate(date) assertEquals("9:42 AM", displayDate) } @Test fun yesterday_shouldReturnWeekday() { setClockTo("2020-05-17T15:42") val date = "2020-05-16T15:42".toEpochMillis() val displayDate = dateTimeFormatter.formatDate(date) assertEquals("Sat", displayDate) } @Test fun sixDaysAgo_shouldReturnWeekday() { setClockTo("2020-05-17T15:42") val date = "2020-05-11T09:42".toEpochMillis() val displayDate = dateTimeFormatter.formatDate(date) assertEquals("Mon", displayDate) } @Test fun sixDaysAndTwentyHours_shouldReturnDay() { setClockTo("2020-05-17T15:42") val date = "2020-05-10T17:42".toEpochMillis() val displayDate = dateTimeFormatter.formatDate(date) assertEquals("May 10", displayDate) } @Test fun sevenDaysAndTwoHours_shouldReturnDay() { setClockTo("2020-05-17T15:42") val date = "2020-05-10T13:42".toEpochMillis() val displayDate = dateTimeFormatter.formatDate(date) assertEquals("May 10", displayDate) } @Test fun startOfYear_shouldReturnDay() { setClockTo("2020-05-17T15:42") val date = LocalDate.parse("2020-01-01").atStartOfDay().toEpochMillis() val displayDate = dateTimeFormatter.formatDate(date) assertEquals("Jan 1", displayDate) } @Test fun endOfLastYear_shouldReturnDate() { setClockTo("2020-05-17T15:42") val date = LocalDateTime.parse("2019-12-31T23:59").toEpochMillis() val displayDate = dateTimeFormatter.formatDate(date) assertEquals("12/31/2019", displayDate) } private fun setClockTo(time: String) { val dateTime = LocalDateTime.parse(time) val timeInMillis = dateTime.toEpochMillis() SystemClock.setCurrentTimeMillis(timeInMillis) // Is handled by ShadowSystemClock whenever(clock.time).thenReturn(timeInMillis) } private fun String.toEpochMillis() = LocalDateTime.parse(this).toEpochMillis() private fun LocalDateTime.toEpochMillis() = this.atZone(ZoneId.of(zoneId)).toInstant().toEpochMilli() }