Merge pull request #9 from Aga-C/add-custom-notifications
Added custom notifications
This commit is contained in:
commit
e5225de675
8 changed files with 148 additions and 22 deletions
|
|
@ -1,10 +1,20 @@
|
||||||
package org.fossify.smsmessenger.activities
|
package org.fossify.smsmessenger.activities
|
||||||
|
|
||||||
|
import android.app.NotificationChannel
|
||||||
|
import android.app.NotificationManager
|
||||||
|
import android.content.Intent
|
||||||
|
import android.media.AudioAttributes
|
||||||
|
import android.media.AudioManager
|
||||||
|
import android.media.RingtoneManager
|
||||||
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.provider.Settings
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.core.content.res.ResourcesCompat
|
import androidx.core.content.res.ResourcesCompat
|
||||||
import org.fossify.commons.extensions.*
|
import org.fossify.commons.extensions.*
|
||||||
import org.fossify.commons.helpers.NavigationIcon
|
import org.fossify.commons.helpers.NavigationIcon
|
||||||
import org.fossify.commons.helpers.ensureBackgroundThread
|
import org.fossify.commons.helpers.ensureBackgroundThread
|
||||||
|
import org.fossify.commons.helpers.isOreoPlus
|
||||||
import org.fossify.commons.models.SimpleContact
|
import org.fossify.commons.models.SimpleContact
|
||||||
import org.fossify.smsmessenger.adapters.ContactsAdapter
|
import org.fossify.smsmessenger.adapters.ContactsAdapter
|
||||||
import org.fossify.smsmessenger.databinding.ActivityConversationDetailsBinding
|
import org.fossify.smsmessenger.databinding.ActivityConversationDetailsBinding
|
||||||
|
|
@ -46,6 +56,9 @@ class ConversationDetailsActivity : SimpleActivity() {
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
setupTextViews()
|
setupTextViews()
|
||||||
setupParticipants()
|
setupParticipants()
|
||||||
|
if (isOreoPlus()) {
|
||||||
|
setupCustomNotifications()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -60,6 +73,60 @@ class ConversationDetailsActivity : SimpleActivity() {
|
||||||
binding.membersHeading.setTextColor(primaryColor)
|
binding.membersHeading.setTextColor(primaryColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.O)
|
||||||
|
private fun setupCustomNotifications() {
|
||||||
|
binding.apply {
|
||||||
|
notificationsHeading.beVisible()
|
||||||
|
customNotificationsHolder.beVisible()
|
||||||
|
customNotifications.isChecked = config.customNotifications.contains(threadId.toString())
|
||||||
|
customNotificationsButton.beVisibleIf(customNotifications.isChecked)
|
||||||
|
|
||||||
|
customNotificationsHolder.setOnClickListener {
|
||||||
|
customNotifications.toggle()
|
||||||
|
if (customNotifications.isChecked) {
|
||||||
|
customNotificationsButton.beVisible()
|
||||||
|
config.addCustomNotificationsByThreadId(threadId)
|
||||||
|
createNotificationChannel()
|
||||||
|
} else {
|
||||||
|
customNotificationsButton.beGone()
|
||||||
|
config.removeCustomNotificationsByThreadId(threadId)
|
||||||
|
removeNotificationChannel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customNotificationsButton.setOnClickListener {
|
||||||
|
Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS).apply {
|
||||||
|
putExtra(Settings.EXTRA_APP_PACKAGE, packageName)
|
||||||
|
putExtra(Settings.EXTRA_CHANNEL_ID, threadId.toString())
|
||||||
|
startActivity(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.O)
|
||||||
|
private fun createNotificationChannel() {
|
||||||
|
val name = conversation?.title
|
||||||
|
val audioAttributes = AudioAttributes.Builder()
|
||||||
|
.setUsage(AudioAttributes.USAGE_NOTIFICATION)
|
||||||
|
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
|
||||||
|
.setLegacyStreamType(AudioManager.STREAM_NOTIFICATION)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
NotificationChannel(threadId.toString(), name, NotificationManager.IMPORTANCE_HIGH).apply {
|
||||||
|
setBypassDnd(false)
|
||||||
|
enableLights(true)
|
||||||
|
setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION), audioAttributes)
|
||||||
|
enableVibration(true)
|
||||||
|
notificationManager.createNotificationChannel(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.O)
|
||||||
|
private fun removeNotificationChannel() {
|
||||||
|
notificationManager.deleteNotificationChannel(threadId.toString())
|
||||||
|
}
|
||||||
|
|
||||||
private fun setupTextViews() {
|
private fun setupTextViews() {
|
||||||
binding.conversationName.apply {
|
binding.conversationName.apply {
|
||||||
ResourcesCompat.getDrawable(resources, org.fossify.commons.R.drawable.ic_edit_vector, theme)?.apply {
|
ResourcesCompat.getDrawable(resources, org.fossify.commons.R.drawable.ic_edit_vector, theme)?.apply {
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package org.fossify.smsmessenger.extensions
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
|
import android.app.NotificationManager
|
||||||
import android.content.ContentResolver
|
import android.content.ContentResolver
|
||||||
import android.content.ContentValues
|
import android.content.ContentValues
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
|
@ -690,6 +691,11 @@ fun Context.deleteConversation(threadId: Long) {
|
||||||
|
|
||||||
conversationsDB.deleteThreadId(threadId)
|
conversationsDB.deleteThreadId(threadId)
|
||||||
messagesDB.deleteThreadMessages(threadId)
|
messagesDB.deleteThreadMessages(threadId)
|
||||||
|
|
||||||
|
if (config.customNotifications.contains(threadId.toString()) && isOreoPlus()) {
|
||||||
|
config.removeCustomNotificationsByThreadId(threadId)
|
||||||
|
notificationManager.deleteNotificationChannel(threadId.toString())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Context.checkAndDeleteOldRecycleBinMessages(callback: (() -> Unit)? = null) {
|
fun Context.checkAndDeleteOldRecycleBinMessages(callback: (() -> Unit)? = null) {
|
||||||
|
|
|
||||||
|
|
@ -115,4 +115,16 @@ class Config(context: Context) : BaseConfig(context) {
|
||||||
var isArchiveAvailable: Boolean
|
var isArchiveAvailable: Boolean
|
||||||
get() = prefs.getBoolean(IS_ARCHIVE_AVAILABLE, true)
|
get() = prefs.getBoolean(IS_ARCHIVE_AVAILABLE, true)
|
||||||
set(isArchiveAvailable) = prefs.edit().putBoolean(IS_ARCHIVE_AVAILABLE, isArchiveAvailable).apply()
|
set(isArchiveAvailable) = prefs.edit().putBoolean(IS_ARCHIVE_AVAILABLE, isArchiveAvailable).apply()
|
||||||
|
|
||||||
|
var customNotifications: Set<String>
|
||||||
|
get() = prefs.getStringSet(CUSTOM_NOTIFICATIONS, HashSet<String>())!!
|
||||||
|
set(customNotifications) = prefs.edit().putStringSet(CUSTOM_NOTIFICATIONS, customNotifications).apply()
|
||||||
|
|
||||||
|
fun addCustomNotificationsByThreadId(threadId: Long) {
|
||||||
|
customNotifications = customNotifications.plus(threadId.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeCustomNotificationsByThreadId(threadId: Long) {
|
||||||
|
customNotifications = customNotifications.minus(threadId.toString())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@ const val USE_RECYCLE_BIN = "use_recycle_bin"
|
||||||
const val LAST_RECYCLE_BIN_CHECK = "last_recycle_bin_check"
|
const val LAST_RECYCLE_BIN_CHECK = "last_recycle_bin_check"
|
||||||
const val IS_RECYCLE_BIN = "is_recycle_bin"
|
const val IS_RECYCLE_BIN = "is_recycle_bin"
|
||||||
const val IS_ARCHIVE_AVAILABLE = "is_archive_available"
|
const val IS_ARCHIVE_AVAILABLE = "is_archive_available"
|
||||||
|
const val CUSTOM_NOTIFICATIONS = "custom_notifications"
|
||||||
|
|
||||||
private const val PATH = "org.fossify.smsmessenger.action."
|
private const val PATH = "org.fossify.smsmessenger.action."
|
||||||
const val MARK_AS_READ = PATH + "mark_as_read"
|
const val MARK_AS_READ = PATH + "mark_as_read"
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,11 @@ class NotificationHelper(private val context: Context) {
|
||||||
sender: String?,
|
sender: String?,
|
||||||
alertOnlyOnce: Boolean = false
|
alertOnlyOnce: Boolean = false
|
||||||
) {
|
) {
|
||||||
maybeCreateChannel(name = context.getString(R.string.channel_received_sms))
|
val hasCustomNotifications = context.config.customNotifications.contains(threadId.toString())
|
||||||
|
val notificationChannelId = if (hasCustomNotifications) threadId.toString() else NOTIFICATION_CHANNEL
|
||||||
|
if (!hasCustomNotifications) {
|
||||||
|
maybeCreateChannel(notificationChannelId, context.getString(R.string.channel_received_sms))
|
||||||
|
}
|
||||||
|
|
||||||
val notificationId = threadId.hashCode()
|
val notificationId = threadId.hashCode()
|
||||||
val contentIntent = Intent(context, ThreadActivity::class.java).apply {
|
val contentIntent = Intent(context, ThreadActivity::class.java).apply {
|
||||||
|
|
@ -98,7 +102,7 @@ class NotificationHelper(private val context: Context) {
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
val builder = NotificationCompat.Builder(context, NOTIFICATION_CHANNEL).apply {
|
val builder = NotificationCompat.Builder(context, notificationChannelId).apply {
|
||||||
when (context.config.lockScreenVisibilitySetting) {
|
when (context.config.lockScreenVisibilitySetting) {
|
||||||
LOCK_SCREEN_SENDER_MESSAGE -> {
|
LOCK_SCREEN_SENDER_MESSAGE -> {
|
||||||
setLargeIcon(largeIcon)
|
setLargeIcon(largeIcon)
|
||||||
|
|
@ -129,20 +133,24 @@ class NotificationHelper(private val context: Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.addAction(org.fossify.commons.R.drawable.ic_check_vector, context.getString(R.string.mark_as_read), markAsReadPendingIntent)
|
builder.addAction(org.fossify.commons.R.drawable.ic_check_vector, context.getString(R.string.mark_as_read), markAsReadPendingIntent)
|
||||||
.setChannelId(NOTIFICATION_CHANNEL)
|
.setChannelId(notificationChannelId)
|
||||||
if (isNoReplySms) {
|
if (isNoReplySms) {
|
||||||
builder.addAction(
|
builder.addAction(
|
||||||
org.fossify.commons.R.drawable.ic_delete_vector,
|
org.fossify.commons.R.drawable.ic_delete_vector,
|
||||||
context.getString(org.fossify.commons.R.string.delete),
|
context.getString(org.fossify.commons.R.string.delete),
|
||||||
deleteSmsPendingIntent
|
deleteSmsPendingIntent
|
||||||
).setChannelId(NOTIFICATION_CHANNEL)
|
).setChannelId(notificationChannelId)
|
||||||
}
|
}
|
||||||
notificationManager.notify(notificationId, builder.build())
|
notificationManager.notify(notificationId, builder.build())
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("NewApi")
|
@SuppressLint("NewApi")
|
||||||
fun showSendingFailedNotification(recipientName: String, threadId: Long) {
|
fun showSendingFailedNotification(recipientName: String, threadId: Long) {
|
||||||
maybeCreateChannel(name = context.getString(R.string.message_not_sent_short))
|
val hasCustomNotifications = context.config.customNotifications.contains(threadId.toString())
|
||||||
|
val notificationChannelId = if (hasCustomNotifications) threadId.toString() else NOTIFICATION_CHANNEL
|
||||||
|
if (!hasCustomNotifications) {
|
||||||
|
maybeCreateChannel(notificationChannelId, context.getString(R.string.message_not_sent_short))
|
||||||
|
}
|
||||||
|
|
||||||
val notificationId = generateRandomId().hashCode()
|
val notificationId = generateRandomId().hashCode()
|
||||||
val intent = Intent(context, ThreadActivity::class.java).apply {
|
val intent = Intent(context, ThreadActivity::class.java).apply {
|
||||||
|
|
@ -152,7 +160,7 @@ class NotificationHelper(private val context: Context) {
|
||||||
|
|
||||||
val summaryText = String.format(context.getString(R.string.message_sending_error), recipientName)
|
val summaryText = String.format(context.getString(R.string.message_sending_error), recipientName)
|
||||||
val largeIcon = SimpleContactsHelper(context).getContactLetterIcon(recipientName)
|
val largeIcon = SimpleContactsHelper(context).getContactLetterIcon(recipientName)
|
||||||
val builder = NotificationCompat.Builder(context, NOTIFICATION_CHANNEL)
|
val builder = NotificationCompat.Builder(context, notificationChannelId)
|
||||||
.setContentTitle(context.getString(R.string.message_not_sent_short))
|
.setContentTitle(context.getString(R.string.message_not_sent_short))
|
||||||
.setContentText(summaryText)
|
.setContentText(summaryText)
|
||||||
.setColor(context.getProperPrimaryColor())
|
.setColor(context.getProperPrimaryColor())
|
||||||
|
|
@ -164,12 +172,12 @@ class NotificationHelper(private val context: Context) {
|
||||||
.setDefaults(Notification.DEFAULT_LIGHTS)
|
.setDefaults(Notification.DEFAULT_LIGHTS)
|
||||||
.setCategory(Notification.CATEGORY_MESSAGE)
|
.setCategory(Notification.CATEGORY_MESSAGE)
|
||||||
.setAutoCancel(true)
|
.setAutoCancel(true)
|
||||||
.setChannelId(NOTIFICATION_CHANNEL)
|
.setChannelId(notificationChannelId)
|
||||||
|
|
||||||
notificationManager.notify(notificationId, builder.build())
|
notificationManager.notify(notificationId, builder.build())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun maybeCreateChannel(name: String) {
|
private fun maybeCreateChannel(id: String, name: String) {
|
||||||
if (isOreoPlus()) {
|
if (isOreoPlus()) {
|
||||||
val audioAttributes = AudioAttributes.Builder()
|
val audioAttributes = AudioAttributes.Builder()
|
||||||
.setUsage(AudioAttributes.USAGE_NOTIFICATION)
|
.setUsage(AudioAttributes.USAGE_NOTIFICATION)
|
||||||
|
|
@ -177,7 +185,6 @@ class NotificationHelper(private val context: Context) {
|
||||||
.setLegacyStreamType(AudioManager.STREAM_NOTIFICATION)
|
.setLegacyStreamType(AudioManager.STREAM_NOTIFICATION)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
val id = NOTIFICATION_CHANNEL
|
|
||||||
val importance = IMPORTANCE_HIGH
|
val importance = IMPORTANCE_HIGH
|
||||||
NotificationChannel(id, name, importance).apply {
|
NotificationChannel(id, name, importance).apply {
|
||||||
setBypassDnd(false)
|
setBypassDnd(false)
|
||||||
|
|
|
||||||
|
|
@ -22,19 +22,55 @@
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/conversation_name_heading"
|
android:id="@+id/notifications_heading"
|
||||||
style="@style/MaterialSectionLabelStyle"
|
style="@style/SettingsSectionLabelStyle"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible"
|
||||||
|
android:text="@string/notifications" />
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:id="@+id/custom_notifications_holder"
|
||||||
|
style="@style/SettingsHolderCheckboxStyle"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
tools:visibility="visible"
|
||||||
|
android:visibility="gone">
|
||||||
|
|
||||||
|
<org.fossify.commons.views.MyAppCompatCheckbox
|
||||||
|
android:id="@+id/custom_notifications"
|
||||||
|
style="@style/SettingsCheckboxStyle"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/enable_custom_notifications" />
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
<org.fossify.commons.views.MyTextView
|
||||||
|
android:id="@+id/custom_notifications_button"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible"
|
||||||
|
style="@style/SettingsHolderTextViewOneLinerStyle"
|
||||||
|
android:text="@string/customize_notifications" />
|
||||||
|
|
||||||
|
<include
|
||||||
|
android:id="@+id/settings_conversation_notifications_divider"
|
||||||
|
layout="@layout/divider" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/conversation_name_heading"
|
||||||
|
style="@style/SettingsSectionLabelStyle"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="@dimen/activity_margin"
|
|
||||||
android:layout_marginEnd="@dimen/activity_margin"
|
|
||||||
android:text="@string/conversation_name" />
|
android:text="@string/conversation_name" />
|
||||||
|
|
||||||
<org.fossify.commons.views.MyTextView
|
<org.fossify.commons.views.MyTextView
|
||||||
android:id="@+id/conversation_name"
|
android:id="@+id/conversation_name"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="@dimen/small_margin"
|
|
||||||
android:background="?attr/selectableItemBackground"
|
android:background="?attr/selectableItemBackground"
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
android:drawableEnd="@drawable/ic_edit_vector"
|
android:drawableEnd="@drawable/ic_edit_vector"
|
||||||
|
|
@ -42,10 +78,7 @@
|
||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
android:focusable="true"
|
android:focusable="true"
|
||||||
android:maxLines="1"
|
android:maxLines="1"
|
||||||
android:paddingStart="@dimen/activity_margin"
|
style="@style/SettingsHolderTextViewOneLinerStyle"
|
||||||
android:paddingTop="@dimen/bigger_margin"
|
|
||||||
android:paddingEnd="@dimen/activity_margin"
|
|
||||||
android:paddingBottom="@dimen/bigger_margin"
|
|
||||||
android:textSize="@dimen/bigger_text_size"
|
android:textSize="@dimen/bigger_text_size"
|
||||||
tools:text="Conversation name" />
|
tools:text="Conversation name" />
|
||||||
|
|
||||||
|
|
@ -55,18 +88,16 @@
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/members_heading"
|
android:id="@+id/members_heading"
|
||||||
style="@style/MaterialSectionLabelStyle"
|
style="@style/SettingsSectionLabelStyle"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="@dimen/activity_margin"
|
|
||||||
android:layout_marginEnd="@dimen/activity_margin"
|
|
||||||
android:text="@string/members" />
|
android:text="@string/members" />
|
||||||
|
|
||||||
<org.fossify.commons.views.MyRecyclerView
|
<org.fossify.commons.views.MyRecyclerView
|
||||||
android:id="@+id/participants_recyclerview"
|
android:id="@+id/participants_recyclerview"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_marginTop="@dimen/normal_margin"
|
android:layout_marginTop="@dimen/medium_margin"
|
||||||
android:clipToPadding="false"
|
android:clipToPadding="false"
|
||||||
app:layoutManager="org.fossify.commons.views.MyLinearLayoutManager"
|
app:layoutManager="org.fossify.commons.views.MyLinearLayoutManager"
|
||||||
tools:itemCount="3"
|
tools:itemCount="3"
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,7 @@
|
||||||
<string name="mark_as_read">Oznacz jako przeczytane</string>
|
<string name="mark_as_read">Oznacz jako przeczytane</string>
|
||||||
<string name="mark_as_unread">Oznacz jako nieprzeczytane</string>
|
<string name="mark_as_unread">Oznacz jako nieprzeczytane</string>
|
||||||
<string name="me">Ja</string>
|
<string name="me">Ja</string>
|
||||||
|
<string name="enable_custom_notifications">Włącz niestandardowe powiadomienia</string>
|
||||||
<string name="unarchive">Cofnij archiwizację</string>
|
<string name="unarchive">Cofnij archiwizację</string>
|
||||||
<string name="empty_archive">Usuń wszystkie zarchiwizowane rozmowy</string>
|
<string name="empty_archive">Usuń wszystkie zarchiwizowane rozmowy</string>
|
||||||
<string name="archived_conversations">Archiwum</string>
|
<string name="archived_conversations">Archiwum</string>
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,7 @@
|
||||||
<string name="mark_as_read">Mark as Read</string>
|
<string name="mark_as_read">Mark as Read</string>
|
||||||
<string name="mark_as_unread">Mark as Unread</string>
|
<string name="mark_as_unread">Mark as Unread</string>
|
||||||
<string name="me">Me</string>
|
<string name="me">Me</string>
|
||||||
|
<string name="enable_custom_notifications">Enable custom notifications</string>
|
||||||
<!-- Archive -->
|
<!-- Archive -->
|
||||||
<string name="unarchive">Unarchive</string>
|
<string name="unarchive">Unarchive</string>
|
||||||
<string name="empty_archive">Delete all archived conversations</string>
|
<string name="empty_archive">Delete all archived conversations</string>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue