feat(聊天模块): 修改聊天模块adapter

使用多itemtype代替原消息显示逻辑
This commit is contained in:
糕小菜 2024-10-26 23:37:17 +08:00
parent e81bbf516d
commit 7c2a9f0619
16 changed files with 518 additions and 93 deletions

View File

@ -20,6 +20,9 @@
android:theme="@style/Theme.KChatAndroid"
android:usesCleartextTraffic="true"
tools:targetApi="31">
<activity
android:name=".view.activity.ContactUpdatesActivity"
android:exported="false" />
<activity
android:name=".view.activity.ApproveDetailActivity"
android:exported="false" />

View File

@ -0,0 +1,22 @@
package com.kaixed.kchat.utils
/**
* @Author: kaixed
* @Date: 2024/10/25 14:38
*/
enum class MessageType(val value: Int) {
CUSTOM(0),
TIP(1),
IMAGE(2),
VOICE(3),
LOCATION(4),
EMOJI(5),
RED_PACKET(6);
companion object {
fun fromValue(value: Int): MessageType? {
return entries.find { it.value == value }
}
}
}

View File

@ -36,6 +36,7 @@ import com.kaixed.kchat.database.entity.Messages;
import com.kaixed.kchat.database.entity.Messages_;
import com.kaixed.kchat.databinding.ActivityChatBinding;
import com.kaixed.kchat.service.WebSocketService;
import com.kaixed.kchat.utils.ConstantsUtil;
import com.kaixed.kchat.utils.ImageSpanUtil;
import com.kaixed.kchat.view.adapter.ChatAdapter;
import com.kaixed.kchat.view.adapter.EmojiAdapter;
@ -53,8 +54,6 @@ import io.objectbox.Box;
import io.objectbox.query.Query;
import io.objectbox.query.QueryBuilder;
import com.kaixed.kchat.utils.ConstantsUtil;
/**
* @author hui
*/
@ -489,7 +488,7 @@ public class ChatActivity extends AppCompatActivity implements OnItemClickListen
"normal",
username,
friendId,
"normal",
"0",
true
);

View File

@ -0,0 +1,39 @@
package com.kaixed.kchat.view.activity
import android.os.Bundle
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.constraintlayout.widget.ConstraintSet
import com.kaixed.kchat.databinding.ActivityContactUpdatesBinding
import com.kaixed.kchat.utils.DensityUtil
class ContactUpdatesActivity : AppCompatActivity() {
private lateinit var binding: ActivityContactUpdatesBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
binding = ActivityContactUpdatesBinding.inflate(layoutInflater)
setContentView(binding.root)
val constraintSet = ConstraintSet()
constraintSet.clone(binding.main)
constraintSet.connect(
binding.ifvAvatar.id,
ConstraintSet.START,
ConstraintSet.PARENT_ID,
ConstraintSet.START,
DensityUtil.dpToPx(this, 10)
)
constraintSet.connect(
binding.tvContent.id,
ConstraintSet.START,
binding.ifvAvatar.id,
ConstraintSet.END,
DensityUtil.dpToPx(this, 10)
)
constraintSet.applyTo(binding.main)
}
}

View File

@ -38,6 +38,7 @@ import java.util.List;
import io.objectbox.Box;
/**
* @Author: kaixed
* @Date: 2024/5/26 10:02
@ -118,7 +119,7 @@ public class MainActivity extends AppCompatActivity implements OnChatListItemCli
return;
}
webSocketService.getLiveData().observe(this, messages -> {
if ("normal".equals(messages.getType())) {
if ("0".equals(messages.getType())) {
processMessage(messages);
}
});
@ -286,6 +287,10 @@ public class MainActivity extends AppCompatActivity implements OnChatListItemCli
Intent intent = new Intent(MainActivity.this, AddFriendsActivity.class);
startActivity(intent);
break;
case 4:
Intent intent2 = new Intent(MainActivity.this, ContactUpdatesActivity.class);
startActivity(intent2);
break;
case 6:
Intent intent1 = new Intent(MainActivity.this, FriendListActivity.class);
startActivity(intent1);

View File

@ -1,65 +1,110 @@
package com.kaixed.kchat.view.adapter
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.drawable.ColorDrawable
import android.text.SpannableString
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.PopupWindow
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintSet
import androidx.recyclerview.widget.RecyclerView
import androidx.window.layout.WindowMetrics
import androidx.window.layout.WindowMetricsCalculator
import com.kaixed.kchat.R
import com.kaixed.kchat.database.entity.Messages
import com.kaixed.kchat.database.ObjectBox
import com.kaixed.kchat.database.UserManager
import com.kaixed.kchat.utils.Constants.TYPE_ITEM_MINE
import com.kaixed.kchat.utils.Constants.TYPE_ITEM_OTHER
import com.kaixed.kchat.utils.Constants.TYPE_MESSAGE_WITHDRAW
import com.kaixed.kchat.utils.ImageSpanUtil
import com.kaixed.kchat.databinding.ChatRecycleItemMineBinding
import com.kaixed.kchat.databinding.ChatRecycleItemOtherBinding
import com.kaixed.kchat.databinding.ChatRecycleItemWithdrawBinding
import com.kaixed.kchat.database.entity.Messages
import com.kaixed.kchat.databinding.ChatRecycleItemCustomBinding
import com.kaixed.kchat.databinding.ChatRecycleItemEmojiBinding
import com.kaixed.kchat.databinding.ChatRecycleItemImageBinding
import com.kaixed.kchat.databinding.ChatRecycleItemLocationBinding
import com.kaixed.kchat.databinding.ChatRecycleItemRedPacketBinding
import com.kaixed.kchat.databinding.ChatRecycleItemTipBinding
import com.kaixed.kchat.databinding.ChatRecycleItemVoiceBinding
import com.kaixed.kchat.databinding.PopwindowsBinding
import java.util.LinkedList
import com.kaixed.kchat.utils.DensityUtil
import com.kaixed.kchat.utils.ImageSpanUtil
import com.kaixed.kchat.utils.MessageType
import io.objectbox.Box
import java.util.LinkedList
class ChatAdapter(
private val context: Context,
private val messages: LinkedList<Messages>
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
companion object {
private const val ITEM_TYPE_CUSTOM = 0
private const val ITEM_TYPE_TIP = 1
private const val ITEM_TYPE_IMAGE = 2
private const val ITEM_TYPE_VOICE = 3
private const val ITEM_TYPE_LOCATION = 4
private const val ITEM_TYPE_EMOJI = 5
private const val ITEM_TYPE_RED_PACKET = 6
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) {
TYPE_ITEM_MINE -> {
val binding = ChatRecycleItemMineBinding.inflate(
ITEM_TYPE_TIP -> {
val binding = ChatRecycleItemTipBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
MineViewHolder(binding)
TipViewHolder(binding)
}
TYPE_ITEM_OTHER -> {
val binding = ChatRecycleItemOtherBinding.inflate(
ITEM_TYPE_IMAGE -> {
val binding = ChatRecycleItemImageBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
OtherViewHolder(binding)
ImageViewHolder(binding)
}
ITEM_TYPE_VOICE -> {
val binding = ChatRecycleItemVoiceBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
VoiceViewHolder(binding)
}
ITEM_TYPE_LOCATION -> {
val binding = ChatRecycleItemLocationBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
LocationViewHolder(binding)
}
ITEM_TYPE_EMOJI -> {
val binding = ChatRecycleItemEmojiBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
EmojiViewHolder(binding)
}
ITEM_TYPE_RED_PACKET -> {
val binding = ChatRecycleItemRedPacketBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
RedPacketViewHolder(binding)
}
else -> {
val binding = ChatRecycleItemWithdrawBinding.inflate(
val binding = ChatRecycleItemCustomBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
WithdrawViewHolder(binding)
CustomViewHolder(binding)
}
}
}
@ -68,39 +113,158 @@ class ChatAdapter(
val singleMessage = messages[position]
when (holder) {
is MineViewHolder -> {
val spannableString = ImageSpanUtil.setEmojiSpan(
context,
singleMessage.content,
holder.binding.tvMineContent.textSize.toInt()
)
holder.binding.tvMineContent.text = spannableString
holder.binding.tvMineContent.setOnLongClickListener {
showPopupWindow(it, position)
true
}
is TipViewHolder -> {
holder.bindData(singleMessage)
}
is OtherViewHolder -> {
val spannableString = ImageSpanUtil.setEmojiSpan(
context,
singleMessage.content,
holder.binding.tvOtherContent.textSize.toInt()
)
holder.binding.tvOtherContent.text = spannableString
holder.binding.tvOtherContent.setOnLongClickListener {
showPopupWindow(it, position)
true
}
is CustomViewHolder -> {
holder.bindData(singleMessage, context)
}
is WithdrawViewHolder -> {
holder.bindData(singleMessage.senderId)
is ImageViewHolder -> {
holder.bindData(singleMessage)
}
is VoiceViewHolder -> {
holder.bindData(singleMessage)
}
is LocationViewHolder -> {
holder.bindData(singleMessage)
}
is EmojiViewHolder -> {
holder.bindData(singleMessage)
}
is RedPacketViewHolder -> {
holder.bindData(singleMessage)
}
}
holder.itemView.setOnLongClickListener {
showPopupWindow(it, position)
true
}
}
class TipViewHolder(val binding: ChatRecycleItemTipBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bindData(message: Messages) {
if (message.show)
binding.tvTip.text = binding.root.context.getString(
R.string.withdraw_message_format,
message.senderId
)
}
}
class CustomViewHolder(val binding: ChatRecycleItemCustomBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bindData(message: Messages, context: Context) {
val isMine: Boolean = message.senderId == UserManager.getUsername()
setViewPosition(binding, isMine, context).applyTo(binding.main)
binding.tvMessageContent.text = ImageSpanUtil.setEmojiSpan(context, message.content, 12)
}
private fun setViewPosition(
binding: ChatRecycleItemCustomBinding,
isMine: Boolean,
context: Context
): ConstraintSet {
val constraintSet = ConstraintSet()
constraintSet.clone(binding.main)
val avatarId: Int = binding.ifvAvatar.id
val messageId = binding.tvMessageContent.id
val margin = DensityUtil.dpToPx(context, 10)
val (avatarConnectionStart, avatarConnectionEnd) = if (isMine) {
ConstraintSet.END to ConstraintSet.START
} else {
ConstraintSet.START to ConstraintSet.END
}
constraintSet.connect(
avatarId, avatarConnectionStart,
ConstraintSet.PARENT_ID, avatarConnectionStart,
margin
)
constraintSet.connect(
messageId, avatarConnectionStart,
avatarId, avatarConnectionEnd,
margin
)
return constraintSet
}
}
class ImageViewHolder(val binding: ChatRecycleItemImageBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bindData(message: Messages) {
}
}
class VoiceViewHolder(val binding: ChatRecycleItemVoiceBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bindData(message: Messages) {
}
}
class LocationViewHolder(val binding: ChatRecycleItemLocationBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bindData(message: Messages) {
}
}
class EmojiViewHolder(val binding: ChatRecycleItemEmojiBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bindData(message: Messages) {
}
}
class RedPacketViewHolder(val binding: ChatRecycleItemRedPacketBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bindData(message: Messages) {
}
}
override fun getItemViewType(position: Int): Int {
val message = messages[position]
return when (MessageType.fromValue(message.type.toInt())) {
MessageType.TIP -> ITEM_TYPE_TIP
MessageType.IMAGE -> ITEM_TYPE_IMAGE
MessageType.VOICE -> ITEM_TYPE_VOICE
MessageType.LOCATION -> ITEM_TYPE_LOCATION
MessageType.EMOJI -> ITEM_TYPE_EMOJI
MessageType.RED_PACKET -> ITEM_TYPE_RED_PACKET
MessageType.CUSTOM -> ITEM_TYPE_CUSTOM
null -> ITEM_TYPE_CUSTOM
}
}
private fun updateDb(message: Messages) {
val messagesBox: Box<Messages> = ObjectBox.get().boxFor(Messages::class.java)
messagesBox.put(message)
}
override fun getItemCount(): Int = messages.size
private fun showPopupWindow(view: View, position: Int) {
val binding: PopwindowsBinding = PopwindowsBinding.inflate(LayoutInflater.from(context))
val popupWindow: PopupWindow = PopupWindow(
@ -158,34 +322,4 @@ class ChatAdapter(
popupWindow.dismiss()
}
}
private fun updateDb(message: Messages) {
val messagesBox: Box<Messages> = ObjectBox.get().boxFor(Messages::class.java)
messagesBox.put(message)
}
override fun getItemViewType(position: Int): Int {
val curUser = UserManager.getUsername()
return if (messages[position].status == "withdraw") {
TYPE_MESSAGE_WITHDRAW
} else {
if (curUser == messages[position].senderId) TYPE_ITEM_MINE else TYPE_ITEM_OTHER
}
}
override fun getItemCount(): Int = messages.size
class MineViewHolder(val binding: ChatRecycleItemMineBinding) :
RecyclerView.ViewHolder(binding.root)
class OtherViewHolder(val binding: ChatRecycleItemOtherBinding) :
RecyclerView.ViewHolder(binding.root)
class WithdrawViewHolder(val binding: ChatRecycleItemWithdrawBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bindData(nickname: String) {
binding.tvUserNickname.text = nickname
}
}
}

View File

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context=".view.activity.ContactUpdatesActivity">
<androidx.constraintlayout.utils.widget.ImageFilterView
android:id="@+id/ifv_avatar"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginVertical="20dp"
android:layout_marginStart="10dp"
android:layout_marginEnd="10dp"
android:src="@drawable/ic_avatar"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:roundPercent="0.3" />
<TextView
android:id="@+id/tv_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
android:background="@drawable/chat_send_btn_bac_mine"
android:gravity="center_vertical"
android:maxWidth="250dp"
android:minHeight="40dp"
android:paddingHorizontal="12dp"
android:paddingVertical="6dp"
android:text="你好你好"
android:textColor="@color/white"
app:layout_constraintBottom_toBottomOf="@id/ifv_avatar"
app:layout_constraintTop_toTopOf="@id/ifv_avatar" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.constraintlayout.utils.widget.ImageFilterView
android:id="@+id/ifv_avatar"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginVertical="20dp"
android:layout_marginEnd="10dp"
android:src="@drawable/ic_avatar"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:roundPercent="0.3" />
<TextView
android:id="@+id/tv_message_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
android:background="@drawable/chat_send_btn_bac_mine"
android:gravity="center_vertical"
android:maxWidth="250dp"
android:minHeight="40dp"
android:paddingHorizontal="12dp"
android:paddingVertical="6dp"
android:text="你好你好"
android:textColor="@color/white"
app:layout_constraintBottom_toBottomOf="@id/ifv_avatar"
app:layout_constraintTop_toTopOf="@id/ifv_avatar" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingVertical="10dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="这是一个小tip"
android:textColor="@color/black"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingVertical="10dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="这是一个小tip"
android:textColor="@color/black"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingVertical="10dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="这是一个小tip"
android:textColor="@color/black"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingVertical="10dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="这是一个小tip"
android:textColor="@color/black"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.constraintlayout.utils.widget.ImageFilterView
android:id="@+id/ifv_avatar"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginVertical="20dp"
android:layout_marginStart="10dp"
android:layout_marginEnd="10dp"
android:src="@drawable/ic_avatar"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:roundPercent="0.3" />
<TextView
android:id="@+id/tv_mine_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
android:background="@drawable/chat_send_btn_bac_mine"
android:gravity="center_vertical"
android:maxWidth="250dp"
android:minHeight="40dp"
android:paddingHorizontal="12dp"
android:paddingVertical="6dp"
android:text="你好你好"
android:textColor="@color/white"
app:layout_constraintEnd_toStartOf="@id/ifv_avatar"
app:layout_constraintTop_toTopOf="@id/ifv_avatar" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingVertical="10dp">
<TextView
android:id="@+id/tv_tip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="这是一个小tip"
android:textColor="@color/black"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingVertical="10dp">
<androidx.constraintlayout.utils.widget.ImageFilterView
android:id="@+id/ifv_avatar"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginEnd="10dp"
android:src="@drawable/ic_avatar"
app:layout_constraintTop_toTopOf="parent"
app:roundPercent="0.3" />
<ImageView
android:id="@+id/iv_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:maxWidth="200dp"
android:maxHeight="200dp"
app:layout_constraintStart_toEndOf="@id/ifv_avatar"
app:layout_constraintTop_toTopOf="@id/ifv_avatar" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -3,18 +3,7 @@
<string name="signature">平安顺遂,喜乐无忧</string>
<string name="name">kaixed</string>
<string name="setting">设置</string>
<!-- Preference Titles -->
<string name="messages_header">Messages</string>
<string name="sync_header">Sync</string>
<!-- Messages Preferences -->
<string name="signature_title">Your signature</string>
<string name="reply_title">Default reply action</string>
<string name="withdraw_message_format">%1$s 撤回了一条消息</string>
<!-- Sync Preferences -->
<string name="sync_title">Sync email periodically</string>
<string name="attachment_title">Download incoming attachments</string>
<string name="attachment_summary_on">Automatically download attachments for incoming emails
</string>
<string name="attachment_summary_off">Only download attachments when manually requested</string>
</resources>