Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 43cf0d78 authored by cketti's avatar cketti
Browse files

Merge pull request #4780

Use day of week in message list
parents 5702dcbb da350055
Loading
Loading
Loading
Loading
+4 −3
Original line number Diff line number Diff line
@@ -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
@@ -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
@@ -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)
@@ -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)

+4 −1
Original line number Diff line number Diff line
@@ -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;
@@ -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;
@@ -475,7 +477,8 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
                layoutInflater,
                ContactPicture.getContactPictureLoader(),
                this,
                getMessageListAppearance()
                getMessageListAppearance(),
                new RelativeDateTimeFormatter(requireContext(), Clock.INSTANCE)
        );

        if (singleFolderMode) {
+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]
+10 −7
Original line number Diff line number Diff line
@@ -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
@@ -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
@@ -461,7 +463,8 @@ class MessageListAdapterTest : RobolectricTest() {
            layoutInflater = LayoutInflater.from(context),
            contactsPictureLoader = contactsPictureLoader,
            listItemListener = listItemListener,
                appearance = appearance
            appearance = appearance,
            relativeDateTimeFormatter = RelativeDateTimeFormatter(context, Clock.INSTANCE)
        )
    }

+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()
}