Merge 07c8ce4717
into 10af2815ac
This commit is contained in:
commit
1f58dd23b7
|
@ -21,13 +21,21 @@ import org.session.libsession.utilities.dynamiclanguage.DynamicLanguageActivityH
|
|||
import org.session.libsession.utilities.dynamiclanguage.DynamicLanguageContextWrapper;
|
||||
import org.thoughtcrime.securesms.conversation.v2.WindowUtil;
|
||||
import org.thoughtcrime.securesms.util.ActivityUtilitiesKt;
|
||||
import org.thoughtcrime.securesms.util.DateUtil;
|
||||
import org.thoughtcrime.securesms.util.ThemeState;
|
||||
import org.thoughtcrime.securesms.util.UiModeUtilities;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.hilt.android.AndroidEntryPoint;
|
||||
import network.loki.messenger.R;
|
||||
|
||||
@AndroidEntryPoint
|
||||
public abstract class BaseActionBarActivity extends AppCompatActivity {
|
||||
private static final String TAG = BaseActionBarActivity.class.getSimpleName();
|
||||
|
||||
@Inject DateUtil dateUtil;
|
||||
|
||||
public ThemeState currentThemeState;
|
||||
|
||||
private TextSecurePreferences getPreferences() {
|
||||
|
|
|
@ -77,12 +77,10 @@ import org.thoughtcrime.securesms.mms.GlideRequests;
|
|||
import org.thoughtcrime.securesms.mms.Slide;
|
||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||
import org.thoughtcrime.securesms.util.AttachmentUtil;
|
||||
import org.thoughtcrime.securesms.util.DateUtils;
|
||||
import org.thoughtcrime.securesms.util.SaveAttachmentTask;
|
||||
import org.thoughtcrime.securesms.util.SaveAttachmentTask.Attachment;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Locale;
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
import network.loki.messenger.R;
|
||||
|
@ -243,7 +241,7 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im
|
|||
CharSequence relativeTimeSpan;
|
||||
|
||||
if (mediaItem.date > 0) {
|
||||
relativeTimeSpan = DateUtils.getDisplayFormattedTimeSpanString(this, Locale.getDefault(), mediaItem.date);
|
||||
relativeTimeSpan = dateUtil.format(mediaItem.date);
|
||||
} else {
|
||||
relativeTimeSpan = getString(R.string.MediaPreviewActivity_draft);
|
||||
}
|
||||
|
|
|
@ -151,6 +151,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||
@Inject lateinit var storage: Storage
|
||||
@Inject lateinit var reactionDb: ReactionDatabase
|
||||
@Inject lateinit var viewModelFactory: ConversationViewModel.AssistedFactory
|
||||
@Inject lateinit var dateUtil: DateUtil
|
||||
|
||||
private val screenshotObserver by lazy {
|
||||
ScreenshotObserver(this, Handler(Looper.getMainLooper())) {
|
||||
|
@ -1730,7 +1731,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||
val body = MentionUtilities.highlightMentions(message.body, viewModel.threadId, this)
|
||||
if (TextUtils.isEmpty(body)) { continue }
|
||||
if (messageSize > 1) {
|
||||
val formattedTimestamp = DateUtils.getDisplayFormattedTimeSpanString(this, Locale.getDefault(), message.timestamp)
|
||||
val formattedTimestamp = dateUtil.format(message.timestamp)
|
||||
builder.append("$formattedTimestamp: ")
|
||||
}
|
||||
builder.append(body)
|
||||
|
|
|
@ -43,18 +43,21 @@ import org.thoughtcrime.securesms.database.model.MessageRecord;
|
|||
import org.thoughtcrime.securesms.database.model.ReactionRecord;
|
||||
import org.thoughtcrime.securesms.dependencies.DatabaseComponent;
|
||||
import org.thoughtcrime.securesms.util.AnimationCompleteListener;
|
||||
import org.thoughtcrime.securesms.util.DateUtils;
|
||||
import org.thoughtcrime.securesms.util.DateUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.hilt.android.AndroidEntryPoint;
|
||||
import kotlin.Unit;
|
||||
import network.loki.messenger.R;
|
||||
|
||||
@AndroidEntryPoint
|
||||
public final class ConversationReactionOverlay extends FrameLayout {
|
||||
|
||||
@Inject DateUtil dateUtil;
|
||||
public static final float LONG_PRESS_SCALE_FACTOR = 0.95f;
|
||||
private static final Interpolator INTERPOLATOR = new DecelerateInterpolator();
|
||||
|
||||
|
@ -169,7 +172,7 @@ public final class ConversationReactionOverlay extends FrameLayout {
|
|||
conversationBubble.setLayoutParams(new LinearLayout.LayoutParams(conversationItemSnapshot.getWidth(), conversationItemSnapshot.getHeight()));
|
||||
conversationBubble.setBackground(new BitmapDrawable(getResources(), conversationItemSnapshot));
|
||||
TextView conversationTimestamp = conversationItem.findViewById(R.id.conversation_item_timestamp);
|
||||
conversationTimestamp.setText(DateUtils.getDisplayFormattedTimeSpanString(getContext(), Locale.getDefault(), messageRecord.getTimestamp()));
|
||||
conversationTimestamp.setText(dateUtil.format(messageRecord.getTimestamp()));
|
||||
|
||||
updateConversationTimestamp(messageRecord);
|
||||
|
||||
|
|
|
@ -7,12 +7,18 @@ import android.view.View
|
|||
import android.widget.LinearLayout
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import network.loki.messenger.R
|
||||
import network.loki.messenger.databinding.ViewControlMessageBinding
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord
|
||||
import org.thoughtcrime.securesms.util.DateUtil
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class ControlMessageView : LinearLayout {
|
||||
|
||||
@Inject lateinit var dateUtil: DateUtil
|
||||
|
||||
private lateinit var binding: ViewControlMessageBinding
|
||||
|
||||
// region Lifecycle
|
||||
|
@ -28,7 +34,7 @@ class ControlMessageView : LinearLayout {
|
|||
|
||||
// region Updating
|
||||
fun bind(message: MessageRecord, previous: MessageRecord?) {
|
||||
binding.dateBreakTextView.showDateBreak(message, previous)
|
||||
binding.dateBreakTextView.showDateBreak(dateUtil, message, previous)
|
||||
binding.iconImageView.visibility = View.GONE
|
||||
var messageBody: CharSequence = message.getDisplayBody(context)
|
||||
binding.root.contentDescription= null
|
||||
|
|
|
@ -3,13 +3,12 @@ package org.thoughtcrime.securesms.conversation.v2.messages
|
|||
import android.widget.TextView
|
||||
import androidx.core.view.isVisible
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord
|
||||
import org.thoughtcrime.securesms.util.DateUtils
|
||||
import java.util.Locale
|
||||
import org.thoughtcrime.securesms.util.DateUtil
|
||||
|
||||
private const val maxTimeBetweenBreaks = 5 * 60 * 1000L // 5 minutes
|
||||
|
||||
fun TextView.showDateBreak(message: MessageRecord, previous: MessageRecord?) {
|
||||
fun TextView.showDateBreak(dateUtil: DateUtil, message: MessageRecord, previous: MessageRecord?) {
|
||||
val showDateBreak = (previous == null || message.timestamp - previous.timestamp > maxTimeBetweenBreaks)
|
||||
isVisible = showDateBreak
|
||||
text = if (showDateBreak) DateUtils.getDisplayFormattedTimeSpanString(context, Locale.getDefault(), message.timestamp) else ""
|
||||
text = if (showDateBreak) dateUtil.format(message.timestamp) else ""
|
||||
}
|
||||
|
|
|
@ -47,12 +47,8 @@ import org.thoughtcrime.securesms.database.model.MessageRecord
|
|||
import org.thoughtcrime.securesms.groups.OpenGroupManager
|
||||
import org.thoughtcrime.securesms.home.UserDetailsBottomSheet
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests
|
||||
import org.thoughtcrime.securesms.util.DateUtils
|
||||
import org.thoughtcrime.securesms.util.disableClipping
|
||||
import org.thoughtcrime.securesms.util.toDp
|
||||
import org.thoughtcrime.securesms.util.toPx
|
||||
import org.thoughtcrime.securesms.util.*
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
import javax.inject.Inject
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.min
|
||||
|
@ -62,6 +58,7 @@ import kotlin.math.sqrt
|
|||
@AndroidEntryPoint
|
||||
class VisibleMessageView : LinearLayout {
|
||||
|
||||
@Inject lateinit var dateUtil: DateUtil
|
||||
@Inject lateinit var threadDb: ThreadDatabase
|
||||
@Inject lateinit var lokiThreadDb: LokiThreadDatabase
|
||||
@Inject lateinit var lokiApiDb: LokiAPIDatabase
|
||||
|
@ -193,7 +190,7 @@ class VisibleMessageView : LinearLayout {
|
|||
binding.senderNameTextView.text = contact?.displayName(contactContext) ?: senderSessionID
|
||||
// Date break
|
||||
val showDateBreak = isStartOfMessageCluster || snIsSelected
|
||||
binding.dateBreakTextView.text = if (showDateBreak) DateUtils.getDisplayFormattedTimeSpanString(context, Locale.getDefault(), message.timestamp) else null
|
||||
binding.dateBreakTextView.text = if (showDateBreak) dateUtil.format(message.timestamp) else null
|
||||
binding.dateBreakTextView.isVisible = showDateBreak
|
||||
// Message status indicator
|
||||
if (message.isOutgoing) {
|
||||
|
|
|
@ -8,6 +8,8 @@ import org.session.libsession.utilities.AppTextSecurePreferences
|
|||
import org.session.libsession.utilities.TextSecurePreferences
|
||||
import org.thoughtcrime.securesms.repository.ConversationRepository
|
||||
import org.thoughtcrime.securesms.repository.DefaultConversationRepository
|
||||
import org.thoughtcrime.securesms.util.AndroidClock
|
||||
import org.thoughtcrime.securesms.util.Clock
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
|
@ -19,4 +21,7 @@ abstract class AppModule {
|
|||
@Binds
|
||||
abstract fun bindConversationRepository(repository: DefaultConversationRepository): ConversationRepository
|
||||
|
||||
@Binds
|
||||
abstract fun bindAndroidClock(androidClock: AndroidClock): Clock
|
||||
|
||||
}
|
|
@ -6,7 +6,6 @@ import android.graphics.Typeface
|
|||
import android.graphics.drawable.ColorDrawable
|
||||
import android.util.AttributeSet
|
||||
import android.util.TypedValue
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.widget.LinearLayout
|
||||
import androidx.core.content.ContextCompat
|
||||
|
@ -20,11 +19,13 @@ import org.thoughtcrime.securesms.database.RecipientDatabase.NOTIFY_TYPE_ALL
|
|||
import org.thoughtcrime.securesms.database.RecipientDatabase.NOTIFY_TYPE_NONE
|
||||
import org.thoughtcrime.securesms.database.model.ThreadRecord
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests
|
||||
import org.thoughtcrime.securesms.util.DateUtils
|
||||
import org.thoughtcrime.securesms.util.DateUtil
|
||||
import org.thoughtcrime.securesms.util.getAccentColor
|
||||
import java.util.Locale
|
||||
import javax.inject.Inject
|
||||
|
||||
class ConversationView : LinearLayout {
|
||||
@Inject lateinit var dateUtil: DateUtil
|
||||
|
||||
private val binding: ViewConversationBinding by lazy { ViewConversationBinding.bind(this) }
|
||||
private val screenWidth = Resources.getSystem().displayMetrics.widthPixels
|
||||
var thread: ThreadRecord? = null
|
||||
|
@ -85,7 +86,7 @@ class ConversationView : LinearLayout {
|
|||
val senderDisplayName = getUserDisplayName(thread.recipient)
|
||||
?: thread.recipient.address.toString()
|
||||
binding.conversationViewDisplayNameTextView.text = senderDisplayName
|
||||
binding.timestampTextView.text = DateUtils.getDisplayFormattedTimeSpanString(context, Locale.getDefault(), thread.date)
|
||||
binding.timestampTextView.text = dateUtil.format(thread.date)
|
||||
val recipient = thread.recipient
|
||||
binding.muteIndicatorImageView.isVisible = recipient.isMuted || recipient.notifyType != NOTIFY_TYPE_ALL
|
||||
val drawableRes = if (recipient.isMuted || recipient.notifyType == NOTIFY_TYPE_NONE) {
|
||||
|
|
|
@ -63,15 +63,8 @@ import org.thoughtcrime.securesms.mms.GlideRequests
|
|||
import org.thoughtcrime.securesms.onboarding.SeedActivity
|
||||
import org.thoughtcrime.securesms.onboarding.SeedReminderViewDelegate
|
||||
import org.thoughtcrime.securesms.preferences.SettingsActivity
|
||||
import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities
|
||||
import org.thoughtcrime.securesms.util.DateUtils
|
||||
import org.thoughtcrime.securesms.util.IP2Country
|
||||
import org.thoughtcrime.securesms.util.disableClipping
|
||||
import org.thoughtcrime.securesms.util.push
|
||||
import org.thoughtcrime.securesms.util.show
|
||||
import org.thoughtcrime.securesms.util.themeState
|
||||
import org.thoughtcrime.securesms.util.*
|
||||
import java.io.IOException
|
||||
import java.util.Locale
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
|
@ -84,6 +77,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
|
|||
private lateinit var glide: GlideRequests
|
||||
private var broadcastReceiver: BroadcastReceiver? = null
|
||||
|
||||
@Inject lateinit var dateUtil: DateUtil
|
||||
@Inject lateinit var threadDb: ThreadDatabase
|
||||
@Inject lateinit var mmsSmsDatabase: MmsSmsDatabase
|
||||
@Inject lateinit var recipientDatabase: RecipientDatabase
|
||||
|
@ -100,7 +94,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
|
|||
HomeAdapter(context = this, listener = this)
|
||||
}
|
||||
|
||||
private val globalSearchAdapter = GlobalSearchAdapter { model ->
|
||||
private val globalSearchAdapter = GlobalSearchAdapter(dateUtil) { model ->
|
||||
when (model) {
|
||||
is GlobalSearchAdapter.Model.Message -> {
|
||||
val threadId = model.messageResult.threadId
|
||||
|
@ -290,11 +284,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
|
|||
if (messageRequestCount > 0 && !textSecurePreferences.hasHiddenMessageRequests()) {
|
||||
with(ViewMessageRequestBannerBinding.inflate(layoutInflater)) {
|
||||
unreadCountTextView.text = messageRequestCount.toString()
|
||||
timestampTextView.text = DateUtils.getDisplayFormattedTimeSpanString(
|
||||
this@HomeActivity,
|
||||
Locale.getDefault(),
|
||||
threadDb.latestUnapprovedConversationTimestamp
|
||||
)
|
||||
timestampTextView.text = dateUtil.format(threadDb.latestUnapprovedConversationTimestamp)
|
||||
root.setOnClickListener { showMessageRequests() }
|
||||
root.setOnLongClickListener { hideMessageRequests(); true }
|
||||
root.layoutParams = RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.WRAP_CONTENT)
|
||||
|
|
|
@ -6,6 +6,7 @@ import android.view.ViewGroup
|
|||
import androidx.annotation.StringRes
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import network.loki.messenger.R
|
||||
import network.loki.messenger.databinding.ViewGlobalSearchHeaderBinding
|
||||
import network.loki.messenger.databinding.ViewGlobalSearchResultBinding
|
||||
|
@ -13,10 +14,14 @@ import org.session.libsession.utilities.GroupRecord
|
|||
import org.session.libsession.utilities.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.mms.GlideApp
|
||||
import org.thoughtcrime.securesms.search.model.MessageResult
|
||||
import org.thoughtcrime.securesms.util.DateUtil
|
||||
import java.security.InvalidParameterException
|
||||
import org.session.libsession.messaging.contacts.Contact as ContactModel
|
||||
|
||||
class GlobalSearchAdapter (private val modelCallback: (Model)->Unit): RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||
class GlobalSearchAdapter(
|
||||
private val dateUtil: DateUtil,
|
||||
private val modelCallback: (Model) -> Unit
|
||||
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||
|
||||
companion object {
|
||||
const val HEADER_VIEW_TYPE = 0
|
||||
|
@ -47,8 +52,10 @@ class GlobalSearchAdapter (private val modelCallback: (Model)->Unit): RecyclerVi
|
|||
} else {
|
||||
ContentView(
|
||||
LayoutInflater.from(parent.context)
|
||||
.inflate(R.layout.view_global_search_result, parent, false)
|
||||
, modelCallback)
|
||||
.inflate(R.layout.view_global_search_result, parent, false),
|
||||
dateUtil,
|
||||
modelCallback
|
||||
)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(
|
||||
|
@ -69,7 +76,7 @@ class GlobalSearchAdapter (private val modelCallback: (Model)->Unit): RecyclerVi
|
|||
}
|
||||
|
||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||
onBindViewHolder(holder,position, mutableListOf())
|
||||
onBindViewHolder(holder, position, mutableListOf())
|
||||
}
|
||||
|
||||
class HeaderView(view: View) : RecyclerView.ViewHolder(view) {
|
||||
|
@ -87,8 +94,11 @@ class GlobalSearchAdapter (private val modelCallback: (Model)->Unit): RecyclerVi
|
|||
}
|
||||
}
|
||||
|
||||
class ContentView(view: View, private val modelCallback: (Model) -> Unit) : RecyclerView.ViewHolder(view) {
|
||||
|
||||
class ContentView(
|
||||
view: View,
|
||||
private val dateUtil: DateUtil,
|
||||
private val modelCallback: (Model) -> Unit
|
||||
) : RecyclerView.ViewHolder(view) {
|
||||
val binding = ViewGlobalSearchResultBinding.bind(view).apply {
|
||||
searchResultProfilePicture.root.glide = GlideApp.with(root)
|
||||
}
|
||||
|
@ -102,7 +112,7 @@ class GlobalSearchAdapter (private val modelCallback: (Model)->Unit): RecyclerVi
|
|||
when (model) {
|
||||
is Model.GroupConversation -> bindModel(query, model)
|
||||
is Model.Contact -> bindModel(query, model)
|
||||
is Model.Message -> bindModel(query, model)
|
||||
is Model.Message -> bindModel(dateUtil, query, model)
|
||||
is Model.SavedMessages -> bindModel(model)
|
||||
is Model.Header -> throw InvalidParameterException("Can't display Model.Header as ContentView")
|
||||
}
|
||||
|
@ -112,14 +122,14 @@ class GlobalSearchAdapter (private val modelCallback: (Model)->Unit): RecyclerVi
|
|||
}
|
||||
|
||||
data class MessageModel(
|
||||
val threadRecipient: Recipient,
|
||||
val messageRecipient: Recipient,
|
||||
val messageSnippet: String
|
||||
val threadRecipient: Recipient,
|
||||
val messageRecipient: Recipient,
|
||||
val messageSnippet: String
|
||||
)
|
||||
|
||||
sealed class Model {
|
||||
data class Header(@StringRes val title: Int) : Model()
|
||||
data class SavedMessages(val currentUserPublicKey: String): Model()
|
||||
data class SavedMessages(val currentUserPublicKey: String) : Model()
|
||||
data class Contact(val contact: ContactModel) : Model()
|
||||
data class GroupConversation(val groupRecord: GroupRecord) : Model()
|
||||
data class Message(val messageResult: MessageResult, val unread: Int) : Model()
|
||||
|
|
|
@ -14,7 +14,7 @@ import org.thoughtcrime.securesms.home.search.GlobalSearchAdapter.ContentView
|
|||
import org.thoughtcrime.securesms.home.search.GlobalSearchAdapter.Model.GroupConversation
|
||||
import org.thoughtcrime.securesms.home.search.GlobalSearchAdapter.Model.Message
|
||||
import org.thoughtcrime.securesms.home.search.GlobalSearchAdapter.Model.SavedMessages
|
||||
import org.thoughtcrime.securesms.util.DateUtils
|
||||
import org.thoughtcrime.securesms.util.DateUtil
|
||||
import org.thoughtcrime.securesms.util.SearchUtil
|
||||
import java.util.Locale
|
||||
import org.thoughtcrime.securesms.home.search.GlobalSearchAdapter.Model.Contact as ContactModel
|
||||
|
@ -125,7 +125,7 @@ fun ContentView.bindModel(model: SavedMessages) {
|
|||
binding.searchResultSavedMessages.isVisible = true
|
||||
}
|
||||
|
||||
fun ContentView.bindModel(query: String?, model: Message) {
|
||||
fun ContentView.bindModel(dateUtil: DateUtil, query: String?, model: Message) {
|
||||
binding.searchResultProfilePicture.root.isVisible = true
|
||||
binding.searchResultSavedMessages.isVisible = false
|
||||
binding.searchResultTimestamp.isVisible = true
|
||||
|
@ -134,7 +134,7 @@ fun ContentView.bindModel(query: String?, model: Message) {
|
|||
// if (hasUnreads) {
|
||||
// binding.unreadCountTextView.text = model.unread.toString()
|
||||
// }
|
||||
binding.searchResultTimestamp.text = DateUtils.getDisplayFormattedTimeSpanString(binding.root.context, Locale.getDefault(), model.messageResult.sentTimestampMs)
|
||||
binding.searchResultTimestamp.text = dateUtil.format(model.messageResult.sentTimestampMs)
|
||||
binding.searchResultProfilePicture.root.update(model.messageResult.conversationRecipient)
|
||||
val textSpannable = SpannableStringBuilder()
|
||||
if (model.messageResult.conversationRecipient != model.messageResult.messageRecipient) {
|
||||
|
|
|
@ -6,16 +6,19 @@ import android.util.AttributeSet
|
|||
import android.view.LayoutInflater
|
||||
import android.widget.LinearLayout
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import network.loki.messenger.R
|
||||
import network.loki.messenger.databinding.ViewMessageRequestBinding
|
||||
import org.session.libsession.utilities.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.conversation.v2.utilities.MentionUtilities.highlightMentions
|
||||
import org.thoughtcrime.securesms.database.model.ThreadRecord
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests
|
||||
import org.thoughtcrime.securesms.util.DateUtils
|
||||
import java.util.Locale
|
||||
import org.thoughtcrime.securesms.util.DateUtil
|
||||
|
||||
@AndroidEntryPoint
|
||||
class MessageRequestView : LinearLayout {
|
||||
lateinit var dateUtil: DateUtil
|
||||
|
||||
private lateinit var binding: ViewMessageRequestBinding
|
||||
private val screenWidth = Resources.getSystem().displayMetrics.widthPixels
|
||||
var thread: ThreadRecord? = null
|
||||
|
@ -38,7 +41,7 @@ class MessageRequestView : LinearLayout {
|
|||
val senderDisplayName = getUserDisplayName(thread.recipient)
|
||||
?: thread.recipient.address.toString()
|
||||
binding.displayNameTextView.text = senderDisplayName
|
||||
binding.timestampTextView.text = DateUtils.getDisplayFormattedTimeSpanString(context, Locale.getDefault(), thread.date)
|
||||
binding.timestampTextView.text = dateUtil.format(thread.date)
|
||||
val rawSnippet = thread.getDisplayBody(context)
|
||||
val snippet = highlightMentions(rawSnippet, thread.threadId, context)
|
||||
binding.snippetTextView.text = snippet
|
||||
|
|
|
@ -73,15 +73,6 @@ object BackupUtil {
|
|||
return prefList
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getLastBackupTimeString(context: Context, locale: Locale): String {
|
||||
val timestamp = DatabaseComponent.get(context).lokiBackupFilesDatabase().getLastBackupFileTime()
|
||||
if (timestamp == null) {
|
||||
return context.getString(R.string.BackupUtil_never)
|
||||
}
|
||||
return DateUtils.getDisplayFormattedTimeSpanString(context, locale, timestamp.time)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getLastBackup(context: Context): BackupFileRecord? {
|
||||
return DatabaseComponent.get(context).lokiBackupFilesDatabase().getLastBackupFile()
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
package org.thoughtcrime.securesms.util
|
||||
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
interface Clock {
|
||||
val currentTimeMillis: Long
|
||||
}
|
||||
|
||||
@Singleton
|
||||
class AndroidClock @Inject constructor(): Clock {
|
||||
override val currentTimeMillis: Long
|
||||
get() = System.currentTimeMillis()
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
package org.thoughtcrime.securesms.util
|
||||
|
||||
import android.content.Context
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import network.loki.messenger.R
|
||||
import org.thoughtcrime.securesms.util.DateUtils.isSameDay
|
||||
import java.util.*
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class DateUtil @Inject constructor (
|
||||
@ApplicationContext private val context: Context,
|
||||
private val clock: Clock
|
||||
) {
|
||||
/**
|
||||
* If the message was sent/received on the same day we show: 12 hour time + am/pm
|
||||
* 12:43 pm
|
||||
* If the message was sent/received on the same week we show: word abbreviation day of week + 12 hour time + am/pm
|
||||
* Tue 12:34 pm
|
||||
* If the message was sent/received in the current year we show, word abbreviation of month, date (day) and 12 hour time + am/pm
|
||||
* Mar 9 12:34 pm
|
||||
* If the message was sent or received outside of the current year, then we show dd/mm/yy
|
||||
* 09/03/23
|
||||
*/
|
||||
@JvmOverloads
|
||||
fun format(timestamp: Long, locale: Locale = Locale.getDefault()): String {
|
||||
if (isWithin(timestamp, 1, TimeUnit.MINUTES)) {
|
||||
return context.getString(R.string.DateUtils_just_now)
|
||||
}
|
||||
return when {
|
||||
isToday(timestamp) -> context.hourFormat
|
||||
isWithin(timestamp, 6, TimeUnit.DAYS) -> "EEE " + context.hourFormat
|
||||
isThisYear(timestamp) -> "MMM d " + context.hourFormat
|
||||
else -> "MMM d " + context.hourFormat + ", yyyy"
|
||||
}.let { DateUtils.getFormattedDateTime(timestamp, it, locale) }
|
||||
}
|
||||
|
||||
private fun isThisYear(millis: Long): Boolean = isSameYear(millis, clock.currentTimeMillis)
|
||||
|
||||
fun isToday(time: Long): Boolean = isSameDay(time, clock.currentTimeMillis)
|
||||
|
||||
fun isWithin(millis: Long, span: Long, unit: TimeUnit): Boolean =
|
||||
clock.currentTimeMillis - millis <= unit.toMillis(span)
|
||||
}
|
||||
|
||||
fun isSameYear(millis: Long, millisOther: Long): Boolean = Calendar.getInstance().run {
|
||||
apply { timeInMillis = millis }.get(Calendar.YEAR) == apply { timeInMillis = millisOther }.get(Calendar.YEAR)
|
||||
}
|
||||
|
||||
private val Context.hourFormat: String get() = DateUtils.getHourFormat(this)
|
|
@ -45,7 +45,7 @@ public class DateUtils extends android.text.format.DateUtils {
|
|||
private static final SimpleDateFormat DAY_PRECISION_DATE_FORMAT = new SimpleDateFormat("yyyyMMdd");
|
||||
private static final SimpleDateFormat HOUR_PRECISION_DATE_FORMAT = new SimpleDateFormat("yyyyMMddHH");
|
||||
|
||||
private static boolean isWithin(final long millis, final long span, final TimeUnit unit) {
|
||||
static boolean isWithin(final long millis, final long span, final TimeUnit unit) {
|
||||
return System.currentTimeMillis() - millis <= unit.toMillis(span);
|
||||
}
|
||||
|
||||
|
@ -66,20 +66,6 @@ public class DateUtils extends android.text.format.DateUtils {
|
|||
return (DateFormat.is24HourFormat(c)) ? "HH:mm" : "hh:mm a";
|
||||
}
|
||||
|
||||
public static String getDisplayFormattedTimeSpanString(final Context c, final Locale locale, final long timestamp) {
|
||||
if (isWithin(timestamp, 1, TimeUnit.MINUTES)) {
|
||||
return c.getString(R.string.DateUtils_just_now);
|
||||
} else if (isToday(timestamp)) {
|
||||
return getFormattedDateTime(timestamp, getHourFormat(c), locale);
|
||||
} else if (isWithin(timestamp, 6, TimeUnit.DAYS)) {
|
||||
return getFormattedDateTime(timestamp, "EEE " + getHourFormat(c), locale);
|
||||
} else if (isWithin(timestamp, 365, TimeUnit.DAYS)) {
|
||||
return getFormattedDateTime(timestamp, "MMM d " + getHourFormat(c), locale);
|
||||
} else {
|
||||
return getFormattedDateTime(timestamp, "MMM d " + getHourFormat(c) + ", yyyy", locale);
|
||||
}
|
||||
}
|
||||
|
||||
public static SimpleDateFormat getDetailedDateFormatter(Context context, Locale locale) {
|
||||
String dateFormatPattern;
|
||||
|
||||
|
|
|
@ -0,0 +1,147 @@
|
|||
package org.thoughtcrime.securesms.util
|
||||
|
||||
import android.app.Application
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.Mockito
|
||||
import org.mockito.kotlin.whenever
|
||||
import org.robolectric.ParameterizedRobolectricTestRunner
|
||||
import org.robolectric.annotation.Config
|
||||
import org.robolectric.shadows.ShadowDateFormat
|
||||
import org.robolectric.shadows.ShadowSettings
|
||||
import java.util.Locale
|
||||
import java.util.TimeZone
|
||||
import java.util.Calendar
|
||||
import java.util.Calendar.JANUARY
|
||||
import java.util.Calendar.NOVEMBER
|
||||
import java.util.Calendar.DECEMBER
|
||||
|
||||
@RunWith(ParameterizedRobolectricTestRunner::class)
|
||||
@Config(application = TestApplication::class, shadows = [ShadowDateFormat::class])
|
||||
class DateUtilTest(
|
||||
private val expected: String,
|
||||
private val locale: Locale,
|
||||
private val then: Long,
|
||||
private val now: Long,
|
||||
private val is24HourTime: Boolean
|
||||
) {
|
||||
private val clock = Mockito.mock(Clock::class.java)
|
||||
private val context = ApplicationProvider.getApplicationContext<Application>()
|
||||
|
||||
private val dateUtil = DateUtil(context, clock)
|
||||
|
||||
@Test
|
||||
fun test() {
|
||||
whenever(clock.currentTimeMillis).thenReturn(now)
|
||||
ShadowSettings.set24HourTimeFormat(is24HourTime)
|
||||
val timestamp = dateUtil.format(then, locale)
|
||||
assertEquals(expected, timestamp)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val zone = TimeZone.getTimeZone("GMT")
|
||||
|
||||
@JvmStatic
|
||||
@ParameterizedRobolectricTestRunner.Parameters(name = "{index}: myMethod({0}, {1})")
|
||||
fun parameters(): List<Array<Any>> = listOf(
|
||||
timespan(
|
||||
millis(2001, JANUARY),
|
||||
millis(2001, JANUARY),
|
||||
assertion(Locale.US, "Now", "Now"),
|
||||
assertion(Locale.CHINA, "Now", "Now"),
|
||||
assertion(Locale.ENGLISH, "Now", "Now")
|
||||
),
|
||||
timespan(
|
||||
millis(2001, JANUARY),
|
||||
millis(2001, JANUARY),
|
||||
assertion(Locale.US, "Now", "Now"),
|
||||
),
|
||||
timespan(
|
||||
millis(2001, DECEMBER, 30),
|
||||
millis(2001, DECEMBER, 30, second = 59),
|
||||
assertion(Locale.US, "Now", "Now"),
|
||||
),
|
||||
timespan(
|
||||
millis(2001, DECEMBER, 30),
|
||||
millis(2001, DECEMBER, 30, minute = 1),
|
||||
assertion(Locale.US, "Now", "Now")
|
||||
),
|
||||
timespan(
|
||||
millis(2001, DECEMBER, 30),
|
||||
millis(2001, DECEMBER, 30, minute = 1, second = 1),
|
||||
assertion(Locale.US, "10:30 AM", "10:30"),
|
||||
assertion(Locale.JAPAN, "10:30 午前", "10:30")
|
||||
),
|
||||
timespan(
|
||||
millis(2001, DECEMBER, 30),
|
||||
millis(2001, DECEMBER, 30, minute = 1, second = 1),
|
||||
assertion(Locale.US, "10:30 AM", "10:30")
|
||||
),
|
||||
timespan(
|
||||
millis(2001, DECEMBER, 30, hour = 12, minute = 15),
|
||||
millis(2001, DECEMBER, 30, hour = 12, minute = 16, second = 1),
|
||||
assertion(Locale.US, "10:45 PM", "22:45")
|
||||
),
|
||||
timespan(
|
||||
millis(2001, DECEMBER, 25),
|
||||
millis(2001, DECEMBER, 30),
|
||||
assertion(Locale.US, "Tue 10:30 AM", "Tue 10:30")
|
||||
),
|
||||
timespan(
|
||||
millis(2001, NOVEMBER, 25),
|
||||
millis(2001, DECEMBER, 10),
|
||||
assertion(Locale.US, "Nov 25 10:30 AM", "Nov 25 10:30")
|
||||
),
|
||||
timespan(
|
||||
millis(2001, JANUARY),
|
||||
millis(2003, JANUARY),
|
||||
assertion(Locale.US, "Jan 1 10:30 AM, 2001", "Jan 1 10:30, 2001")
|
||||
),
|
||||
).flatten()
|
||||
|
||||
private fun assertion(
|
||||
locale: Locale,
|
||||
expected: String,
|
||||
expected24HourTime: String
|
||||
): Timespan.() -> List<Array<Any>> = {
|
||||
listOf(
|
||||
assertion(locale, expected, false),
|
||||
assertion(locale, expected24HourTime, true)
|
||||
)
|
||||
}
|
||||
|
||||
private fun timespan(
|
||||
start: Locale.() -> Long,
|
||||
end: Locale.() -> Long,
|
||||
vararg assertions: Timespan.() -> List<Array<Any>>
|
||||
): List<Array<Any>> = Timespan(start, end).run { assertions.map { it() } }.flatten()
|
||||
|
||||
private fun millis(
|
||||
year: Int,
|
||||
month: Int = 0,
|
||||
date: Int = 1,
|
||||
hour: Int = 0,
|
||||
minute: Int = 0,
|
||||
second: Int = 0
|
||||
): (Locale) -> Long = {
|
||||
Calendar.getInstance(zone, it)
|
||||
.apply { set(year, month, date, hour, minute, second) }
|
||||
.timeInMillis
|
||||
}
|
||||
|
||||
class Timespan(
|
||||
val start: (Locale) -> Long,
|
||||
val end: (Locale) -> Long
|
||||
) {
|
||||
fun assertion(
|
||||
locale: Locale,
|
||||
expected: String,
|
||||
is24HourTime: Boolean
|
||||
): Array<Any> = arrayOf(expected, locale, start(locale), end(locale), is24HourTime)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TestApplication : Application()
|
|
@ -0,0 +1 @@
|
|||
sdk=29
|
Loading…
Reference in New Issue