added profile image and name to message
This commit is contained in:
parent
57b3ed3346
commit
b66b619354
|
@ -63,15 +63,21 @@ class MessagesConversationAdapter(context: Context) : LoadMoreSupportAdapter<Rec
|
|||
when (viewType) {
|
||||
ITEM_TYPE_TEXT_MESSAGE -> {
|
||||
val view = inflater.inflate(MessageViewHolder.layoutResource, parent, false)
|
||||
return MessageViewHolder(view, this)
|
||||
val holder = MessageViewHolder(view, this)
|
||||
holder.setup()
|
||||
return holder
|
||||
}
|
||||
ITEM_TYPE_STICKER_MESSAGE -> {
|
||||
val view = inflater.inflate(StickerMessageViewHolder.layoutResource, parent, false)
|
||||
return StickerMessageViewHolder(view, this)
|
||||
val holder = StickerMessageViewHolder(view, this)
|
||||
holder.setup()
|
||||
return holder
|
||||
}
|
||||
ITEM_TYPE_NOTICE_MESSAGE -> {
|
||||
val view = inflater.inflate(NoticeSummaryEventViewHolder.layoutResource, parent, false)
|
||||
return NoticeSummaryEventViewHolder(view, this)
|
||||
val holder = NoticeSummaryEventViewHolder(view, this)
|
||||
holder.setup()
|
||||
return holder
|
||||
}
|
||||
}
|
||||
throw UnsupportedOperationException()
|
||||
|
|
|
@ -77,60 +77,12 @@ class GetMessagesTask(
|
|||
|
||||
private fun getTwitterOfficialMessages(microBlog: MicroBlog, details: AccountDetails,
|
||||
param: RefreshMessagesTaskParam, index: Int): GetMessagesData {
|
||||
if (param.conversationId != null) return GetMessagesData(emptyList(), emptyList())
|
||||
val accountKey = details.key
|
||||
val maxId = if (param.hasMaxIds) param.maxIds?.get(index) else null
|
||||
val cursor = if (param.hasCursors) param.cursors?.get(index) else null
|
||||
val response = if (cursor != null) {
|
||||
microBlog.getUserUpdates(cursor).userEvents
|
||||
val conversationId = param.conversationId
|
||||
if (conversationId == null) {
|
||||
return getTwitterOfficialUserInbox(microBlog, details, param, index)
|
||||
} else {
|
||||
microBlog.getUserInbox(Paging().apply {
|
||||
if (maxId != null) {
|
||||
maxId(maxId)
|
||||
}
|
||||
}).userInbox
|
||||
}
|
||||
|
||||
|
||||
val respConversations = response.conversations
|
||||
val respEntries = response.entries
|
||||
val respUsers = response.users
|
||||
|
||||
if (respConversations == null || respEntries == null || respUsers == null) {
|
||||
return GetMessagesData(emptyList(), emptyList())
|
||||
}
|
||||
|
||||
val conversations = hashMapOf<String, ParcelableMessageConversation>()
|
||||
|
||||
respConversations.keys.let {
|
||||
conversations.addLocalConversations(accountKey, it)
|
||||
}
|
||||
val messages = respEntries.mapNotNull {
|
||||
ParcelableMessageUtils.fromEntry(accountKey, it, respUsers)
|
||||
}
|
||||
val messagesMap = messages.groupBy(ParcelableMessage::conversation_id)
|
||||
for ((k, v) in respConversations) {
|
||||
val message = messagesMap[k]?.maxBy(ParcelableMessage::message_timestamp) ?: continue
|
||||
val participants = respUsers.filterKeys { userId ->
|
||||
v.participants.any { it.userId == userId }
|
||||
}.values
|
||||
val conversationType = when (v.type?.toUpperCase(Locale.US)) {
|
||||
DMResponse.Conversation.Type.ONE_TO_ONE -> ConversationType.ONE_TO_ONE
|
||||
DMResponse.Conversation.Type.GROUP_DM -> ConversationType.GROUP
|
||||
else -> ConversationType.ONE_TO_ONE
|
||||
}
|
||||
val conversation = conversations.addConversation(k, details, message, participants,
|
||||
conversationType)
|
||||
conversation.conversation_name = v.name
|
||||
conversation.request_cursor = response.cursor
|
||||
conversation.conversation_extras_type = ParcelableMessageConversation.ExtrasType.TWITTER_OFFICIAL
|
||||
conversation.conversation_extras = TwitterOfficialConversationExtras().apply {
|
||||
this.minEntryId = v.minEntryId
|
||||
this.maxEntryId = v.maxEntryId
|
||||
this.status = v.status
|
||||
}
|
||||
}
|
||||
return GetMessagesData(conversations.values, messages)
|
||||
}
|
||||
|
||||
private fun getFanfouMessages(microBlog: MicroBlog, details: AccountDetails, param: RefreshMessagesTaskParam, index: Int): GetMessagesData {
|
||||
|
@ -201,6 +153,66 @@ class GetMessagesTask(
|
|||
return GetMessagesData(conversations.values, insertMessages)
|
||||
}
|
||||
|
||||
private fun getTwitterOfficialConversation(microBlog: MicroBlog, details: AccountDetails,
|
||||
conversationId: String, param: RefreshMessagesTaskParam, index: Int) {
|
||||
|
||||
}
|
||||
|
||||
private fun getTwitterOfficialUserInbox(microBlog: MicroBlog, details: AccountDetails, param: RefreshMessagesTaskParam, index: Int): GetMessagesData {
|
||||
val accountKey = details.key
|
||||
val maxId = if (param.hasMaxIds) param.maxIds?.get(index) else null
|
||||
val cursor = if (param.hasCursors) param.cursors?.get(index) else null
|
||||
val response = if (cursor != null) {
|
||||
microBlog.getUserUpdates(cursor).userEvents
|
||||
} else {
|
||||
microBlog.getUserInbox(Paging().apply {
|
||||
if (maxId != null) {
|
||||
maxId(maxId)
|
||||
}
|
||||
}).userInbox
|
||||
}
|
||||
|
||||
val respConversations = response.conversations
|
||||
val respEntries = response.entries
|
||||
val respUsers = response.users
|
||||
|
||||
if (respConversations == null || respEntries == null || respUsers == null) {
|
||||
return GetMessagesData(emptyList(), emptyList())
|
||||
}
|
||||
|
||||
val conversations = hashMapOf<String, ParcelableMessageConversation>()
|
||||
|
||||
respConversations.keys.let {
|
||||
conversations.addLocalConversations(accountKey, it)
|
||||
}
|
||||
val messages = respEntries.mapNotNull {
|
||||
ParcelableMessageUtils.fromEntry(accountKey, it, respUsers)
|
||||
}
|
||||
val messagesMap = messages.groupBy(ParcelableMessage::conversation_id)
|
||||
for ((k, v) in respConversations) {
|
||||
val message = messagesMap[k]?.maxBy(ParcelableMessage::message_timestamp) ?: continue
|
||||
val participants = respUsers.filterKeys { userId ->
|
||||
v.participants.any { it.userId == userId }
|
||||
}.values
|
||||
val conversationType = when (v.type?.toUpperCase(Locale.US)) {
|
||||
DMResponse.Conversation.Type.ONE_TO_ONE -> ConversationType.ONE_TO_ONE
|
||||
DMResponse.Conversation.Type.GROUP_DM -> ConversationType.GROUP
|
||||
else -> ConversationType.ONE_TO_ONE
|
||||
}
|
||||
val conversation = conversations.addConversation(k, details, message, participants,
|
||||
conversationType)
|
||||
conversation.conversation_name = v.name
|
||||
conversation.request_cursor = response.cursor
|
||||
conversation.conversation_extras_type = ParcelableMessageConversation.ExtrasType.TWITTER_OFFICIAL
|
||||
conversation.conversation_extras = TwitterOfficialConversationExtras().apply {
|
||||
this.minEntryId = v.minEntryId
|
||||
this.maxEntryId = v.maxEntryId
|
||||
this.status = v.status
|
||||
}
|
||||
}
|
||||
return GetMessagesData(conversations.values, messages)
|
||||
}
|
||||
|
||||
private fun getFanfouConversations(microBlog: MicroBlog, details: AccountDetails, param: RefreshMessagesTaskParam, index: Int): GetMessagesData {
|
||||
val accountKey = details.key
|
||||
val cursor = param.cursors?.get(index)
|
||||
|
|
|
@ -28,9 +28,11 @@ import android.widget.FrameLayout
|
|||
import android.widget.LinearLayout
|
||||
import android.widget.RelativeLayout
|
||||
import android.widget.TextView
|
||||
import org.mariotaku.twidere.R
|
||||
import org.mariotaku.twidere.adapter.MessagesConversationAdapter
|
||||
import org.mariotaku.twidere.extension.model.timestamp
|
||||
import org.mariotaku.twidere.model.ParcelableMessage
|
||||
import org.mariotaku.twidere.view.ProfileImageView
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 2017/2/9.
|
||||
|
@ -40,8 +42,20 @@ abstract class AbsMessageViewHolder(itemView: View, val adapter: MessagesConvers
|
|||
|
||||
protected abstract val date: TextView
|
||||
protected abstract val messageContent: View
|
||||
protected open val profileImage: ProfileImageView? = null
|
||||
protected open val nameTime: TextView? = null
|
||||
|
||||
open fun setup() {
|
||||
val textSize = adapter.textSize
|
||||
date.textSize = textSize * 0.9f
|
||||
nameTime?.textSize = textSize * 0.8f
|
||||
profileImage?.style = adapter.profileImageStyle
|
||||
}
|
||||
|
||||
open fun display(message: ParcelableMessage, showDate: Boolean) {
|
||||
val context = adapter.context
|
||||
val manager = adapter.userColorNameManager
|
||||
|
||||
setMessageContentGravity(messageContent, message.is_outgoing)
|
||||
if (showDate) {
|
||||
date.visibility = View.VISIBLE
|
||||
|
@ -50,6 +64,29 @@ abstract class AbsMessageViewHolder(itemView: View, val adapter: MessagesConvers
|
|||
} else {
|
||||
date.visibility = View.GONE
|
||||
}
|
||||
val sender = message.sender_key?.let { adapter.findUser(it) }
|
||||
|
||||
nameTime?.apply {
|
||||
val time = DateUtils.formatDateTime(context, message.timestamp, DateUtils.FORMAT_SHOW_TIME)
|
||||
if (message.is_outgoing) {
|
||||
this.text = time
|
||||
} else if (sender != null) {
|
||||
val senderName = manager.getDisplayName(sender, adapter.nameFirst)
|
||||
this.text = context.getString(R.string.message_format_sender_time, senderName, time)
|
||||
} else {
|
||||
this.text = time
|
||||
}
|
||||
}
|
||||
|
||||
profileImage?.apply {
|
||||
if (adapter.profileImageEnabled && sender != null && !message.is_outgoing) {
|
||||
this.visibility = View.VISIBLE
|
||||
adapter.mediaLoader.displayProfileImage(this, sender)
|
||||
} else {
|
||||
this.visibility = View.GONE
|
||||
adapter.mediaLoader.cancelDisplayTask(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open fun setMessageContentGravity(view: View, outgoing: Boolean) {
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
package org.mariotaku.twidere.view.holder.message
|
||||
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.text.format.DateUtils
|
||||
import android.view.View
|
||||
import kotlinx.android.synthetic.main.list_item_message_conversation_text.view.*
|
||||
import org.mariotaku.ktextension.empty
|
||||
|
@ -29,10 +28,10 @@ import org.mariotaku.messagebubbleview.library.MessageBubbleView
|
|||
import org.mariotaku.twidere.R
|
||||
import org.mariotaku.twidere.adapter.MessagesConversationAdapter
|
||||
import org.mariotaku.twidere.extension.model.applyTo
|
||||
import org.mariotaku.twidere.extension.model.timestamp
|
||||
import org.mariotaku.twidere.model.ParcelableMessage
|
||||
import org.mariotaku.twidere.model.SpanItem
|
||||
import org.mariotaku.twidere.view.FixedTextView
|
||||
import org.mariotaku.twidere.view.ProfileImageView
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 2017/2/9.
|
||||
|
@ -41,23 +40,25 @@ import org.mariotaku.twidere.view.FixedTextView
|
|||
class MessageViewHolder(itemView: View, adapter: MessagesConversationAdapter) : AbsMessageViewHolder(itemView, adapter) {
|
||||
|
||||
override val date: FixedTextView by lazy { itemView.date }
|
||||
override val messageContent: MessageBubbleView by lazy { itemView.messageContent }
|
||||
override val messageContent: View by lazy { itemView.messageContent }
|
||||
override val profileImage: ProfileImageView by lazy { itemView.profileImage }
|
||||
override val nameTime: FixedTextView by lazy { itemView.nameTime }
|
||||
|
||||
private val text by lazy { itemView.text }
|
||||
private val time by lazy { itemView.time }
|
||||
private val mediaPreview by lazy { itemView.mediaPreview }
|
||||
private val messageBubble by lazy { itemView.messageBubble }
|
||||
|
||||
init {
|
||||
override fun setup() {
|
||||
super.setup()
|
||||
val textSize = adapter.textSize
|
||||
text.textSize = textSize
|
||||
time.textSize = textSize * 0.8f
|
||||
date.textSize = textSize * 0.9f
|
||||
mediaPreview.style = adapter.mediaPreviewStyle
|
||||
}
|
||||
|
||||
override fun display(message: ParcelableMessage, showDate: Boolean) {
|
||||
super.display(message, showDate)
|
||||
messageContent.setOutgoing(message.is_outgoing)
|
||||
|
||||
messageBubble.setOutgoing(message.is_outgoing)
|
||||
|
||||
// Loop through text and spans to found non-space char count
|
||||
val hideText = run {
|
||||
|
@ -97,9 +98,6 @@ class MessageViewHolder(itemView: View, adapter: MessagesConversationAdapter) :
|
|||
View.VISIBLE
|
||||
}
|
||||
|
||||
time.text = DateUtils.formatDateTime(adapter.context, message.timestamp,
|
||||
DateUtils.FORMAT_SHOW_TIME)
|
||||
|
||||
if (message.media.isNullOrEmpty()) {
|
||||
mediaPreview.visibility = View.GONE
|
||||
} else {
|
||||
|
@ -107,13 +105,14 @@ class MessageViewHolder(itemView: View, adapter: MessagesConversationAdapter) :
|
|||
mediaPreview.displayMedia(adapter.mediaLoader, message.media, message.account_key,
|
||||
withCredentials = true, loadingHandler = adapter.mediaLoadingHandler)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val layoutResource = R.layout.list_item_message_conversation_text
|
||||
|
||||
fun MessageBubbleView.setOutgoing(outgoing: Boolean) {
|
||||
setCaretPosition(if (outgoing) MessageBubbleView.BOTTOM_END else MessageBubbleView.BOTTOM_START)
|
||||
setCaretPosition(if (outgoing) MessageBubbleView.BOTTOM_END else MessageBubbleView.TOP_START)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ import org.mariotaku.twidere.adapter.MessagesConversationAdapter
|
|||
import org.mariotaku.twidere.model.ParcelableMessage
|
||||
import org.mariotaku.twidere.model.message.StickerExtras
|
||||
import org.mariotaku.twidere.view.FixedTextView
|
||||
import org.mariotaku.twidere.view.ProfileImageView
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 2017/2/9.
|
||||
|
@ -36,6 +37,8 @@ class StickerMessageViewHolder(itemView: View, adapter: MessagesConversationAdap
|
|||
|
||||
override val date: FixedTextView by lazy { itemView.date }
|
||||
override val messageContent: RelativeLayout by lazy { itemView.messageContent }
|
||||
override val profileImage: ProfileImageView by lazy { itemView.profileImage }
|
||||
override val nameTime: FixedTextView by lazy { itemView.nameTime }
|
||||
|
||||
private val stickerIcon by lazy { itemView.stickerIcon }
|
||||
|
||||
|
|
|
@ -66,13 +66,13 @@
|
|||
android:layout_height="@dimen/element_size_normal"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:background="?selectableItemBackground"
|
||||
android:background="?selectableItemBackgroundBorderless"
|
||||
android:clickable="true"
|
||||
android:color="?android:textColorSecondary"
|
||||
android:contentDescription="@string/add_image"
|
||||
android:scaleType="centerInside"
|
||||
android:src="@drawable/ic_action_gallery"
|
||||
android:visibility="gone"/>
|
||||
android:visibility="visible"/>
|
||||
|
||||
<org.mariotaku.twidere.view.IconActionView
|
||||
android:id="@+id/sendMessage"
|
||||
|
@ -80,7 +80,7 @@
|
|||
android:layout_height="@dimen/element_size_normal"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:background="?selectableItemBackground"
|
||||
android:background="?selectableItemBackgroundBorderless"
|
||||
android:clickable="true"
|
||||
android:color="?android:textColorSecondary"
|
||||
android:contentDescription="@string/action_send"
|
||||
|
|
|
@ -41,12 +41,35 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/date">
|
||||
|
||||
<org.mariotaku.twidere.view.ProfileImageView
|
||||
android:id="@+id/profileImage"
|
||||
android:layout_width="@dimen/profile_image_size_direct_message"
|
||||
android:layout_height="@dimen/profile_image_size_direct_message"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_marginEnd="@dimen/element_spacing_normal"
|
||||
android:layout_marginRight="@dimen/element_spacing_normal"
|
||||
tools:src="@drawable/ic_profile_image_twidere"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/stickerIcon"
|
||||
android:layout_width="96dp"
|
||||
android:layout_height="96dp"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_toEndOf="@+id/profileImage"
|
||||
android:layout_toRightOf="@+id/profileImage"
|
||||
android:contentDescription="@string/content_description_sticker"
|
||||
android:scaleType="fitCenter"/>
|
||||
|
||||
<org.mariotaku.twidere.view.FixedTextView
|
||||
android:id="@+id/nameTime"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignLeft="@+id/stickerIcon"
|
||||
android:layout_alignStart="@+id/stickerIcon"
|
||||
android:layout_below="@+id/stickerIcon"
|
||||
android:layout_margin="@dimen/element_spacing_normal"
|
||||
android:textColor="?android:attr/textColorTertiary"
|
||||
tools:text="12:00"/>
|
||||
</RelativeLayout>
|
||||
|
||||
</RelativeLayout>
|
|
@ -36,31 +36,30 @@
|
|||
android:textColor="?android:textColorTertiary"
|
||||
tools:text="Yesterday"/>
|
||||
|
||||
<org.mariotaku.messagebubbleview.library.MessageBubbleView
|
||||
<RelativeLayout
|
||||
android:id="@+id/messageContent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="start"
|
||||
android:clickable="true"
|
||||
app:bubbleColor="?messageBubbleColor"
|
||||
app:caretHeight="8dp"
|
||||
app:caretPosition="topStart"
|
||||
app:caretWidth="8dp"
|
||||
app:cornerRadius="2dp">
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<RelativeLayout
|
||||
<org.mariotaku.twidere.view.ProfileImageView
|
||||
android:id="@+id/profileImage"
|
||||
android:layout_width="@dimen/profile_image_size_direct_message"
|
||||
android:layout_height="@dimen/profile_image_size_direct_message"
|
||||
android:layout_alignParentTop="true"
|
||||
tools:src="@drawable/ic_profile_image_twidere"/>
|
||||
|
||||
<org.mariotaku.messagebubbleview.library.MessageBubbleView
|
||||
android:id="@+id/messageBubble"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<org.mariotaku.twidere.view.CardMediaContainer
|
||||
android:id="@+id/mediaPreview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone">
|
||||
|
||||
<include layout="@layout/layout_card_media_preview"/>
|
||||
|
||||
</org.mariotaku.twidere.view.CardMediaContainer>
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_toEndOf="@+id/profileImage"
|
||||
android:layout_toRightOf="@+id/profileImage"
|
||||
android:clickable="true"
|
||||
app:bubbleColor="?messageBubbleColor"
|
||||
app:caretHeight="@dimen/element_spacing_normal"
|
||||
app:caretPosition="topStart"
|
||||
app:caretWidth="@dimen/element_spacing_normal"
|
||||
app:cornerRadius="2dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
|
@ -69,23 +68,37 @@
|
|||
android:orientation="vertical"
|
||||
android:padding="@dimen/element_spacing_normal">
|
||||
|
||||
<org.mariotaku.twidere.view.CardMediaContainer
|
||||
android:id="@+id/mediaPreview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone">
|
||||
|
||||
<include layout="@layout/layout_card_media_preview"/>
|
||||
|
||||
</org.mariotaku.twidere.view.CardMediaContainer>
|
||||
|
||||
|
||||
<org.mariotaku.twidere.view.TimelineContentTextView
|
||||
android:id="@+id/text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="@dimen/element_spacing_small"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
tools:text="@string/sample_status_text"/>
|
||||
|
||||
<org.mariotaku.twidere.view.FixedTextView
|
||||
android:id="@+id/time"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="?android:attr/textColorTertiary"
|
||||
tools:text="12:00"/>
|
||||
</LinearLayout>
|
||||
</RelativeLayout>
|
||||
</org.mariotaku.messagebubbleview.library.MessageBubbleView>
|
||||
</org.mariotaku.messagebubbleview.library.MessageBubbleView>
|
||||
|
||||
<org.mariotaku.twidere.view.FixedTextView
|
||||
android:id="@+id/nameTime"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignLeft="@+id/messageBubble"
|
||||
android:layout_alignStart="@+id/messageBubble"
|
||||
android:layout_below="@+id/messageBubble"
|
||||
android:layout_margin="@dimen/element_spacing_normal"
|
||||
android:textColor="?android:attr/textColorTertiary"
|
||||
tools:text="12:00"/>
|
||||
</RelativeLayout>
|
||||
</LinearLayout>
|
|
@ -97,6 +97,7 @@
|
|||
<dimen name="preferred_tab_column_width">420dp</dimen>
|
||||
|
||||
<dimen name="profile_image_size_activity_small">32dp</dimen>
|
||||
<dimen name="profile_image_size_direct_message">36dp</dimen>
|
||||
|
||||
<dimen name="settings_panel_margin_start_details">@dimen/element_size_normal</dimen>
|
||||
<dimen name="settings_panel_width_entries">240dp</dimen>
|
||||
|
|
|
@ -688,6 +688,10 @@
|
|||
</string>
|
||||
<string name="message_format_participants_leave">
|
||||
<xliff:g example="User" id="name">%1$s</xliff:g> left</string>
|
||||
<string name="message_format_sender_time">
|
||||
<xliff:g example="User" id="sender">%1$s</xliff:g> · <xliff:g
|
||||
example="12:00" id="time">%2$s</xliff:g>
|
||||
</string>
|
||||
<string name="message_join_conversation">Joined conversation.</string>
|
||||
<string name="message_permission_request_compose_location">Twidere needs location permission for adding location to tweets.</string>
|
||||
<string name="message_permission_request_save_media">Twidere needs storage permission for saving media.</string>
|
||||
|
|
Loading…
Reference in New Issue