Merge branch 'master' into feature/451-recycle-bin

This commit is contained in:
Ensar Sarajčić 2023-07-24 14:47:50 +02:00
commit dbf582b239
33 changed files with 570 additions and 590 deletions

View File

@ -1,7 +1,10 @@
apply plugin: 'com.android.application' plugins {
apply plugin: 'kotlin-android' id 'com.android.application'
apply plugin: 'kotlin-android-extensions' id 'kotlin-android'
apply plugin: 'kotlin-kapt' id 'kotlin-android-extensions'
id 'kotlin-kapt'
id 'org.jetbrains.kotlin.plugin.serialization' version "$kotlin_version"
}
def keystorePropertiesFile = rootProject.file("keystore.properties") def keystorePropertiesFile = rootProject.file("keystore.properties")
def keystoreProperties = new Properties() def keystoreProperties = new Properties()
@ -71,6 +74,7 @@ dependencies {
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'com.googlecode.ez-vcard:ez-vcard:0.11.3' implementation 'com.googlecode.ez-vcard:ez-vcard:0.11.3'
implementation 'androidx.lifecycle:lifecycle-process:2.5.1' implementation 'androidx.lifecycle:lifecycle-process:2.5.1'
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1"
kapt "androidx.room:room-compiler:2.5.2" kapt "androidx.room:room-compiler:2.5.2"
implementation "androidx.room:room-runtime:2.5.2" implementation "androidx.room:room-runtime:2.5.2"

View File

@ -4,3 +4,27 @@
@org.greenrobot.eventbus.Subscribe <methods>; @org.greenrobot.eventbus.Subscribe <methods>;
} }
-keep enum org.greenrobot.eventbus.ThreadMode { *; } -keep enum org.greenrobot.eventbus.ThreadMode { *; }
# Keep `Companion` object fields of serializable classes.
# This avoids serializer lookup through `getDeclaredClasses` as done for named companion objects.
-if @kotlinx.serialization.Serializable class **
-keepclassmembers class <1> {
static <1>$Companion Companion;
}
# Keep `serializer()` on companion objects (both default and named) of serializable classes.
-if @kotlinx.serialization.Serializable class ** {
static **$* *;
}
-keepclassmembers class <2>$<3> {
kotlinx.serialization.KSerializer serializer(...);
}
# Keep `INSTANCE.serializer()` of serializable objects.
-if @kotlinx.serialization.Serializable class ** {
public static ** INSTANCE;
}
-keepclassmembers class <1> {
public static <1> INSTANCE;
kotlinx.serialization.KSerializer serializer(...);
}

View File

@ -3,19 +3,15 @@ package com.simplemobiletools.smsmessenger.activities
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Activity import android.app.Activity
import android.app.role.RoleManager import android.app.role.RoleManager
import android.content.ActivityNotFoundException
import android.content.Intent import android.content.Intent
import android.content.pm.ShortcutInfo import android.content.pm.ShortcutInfo
import android.content.pm.ShortcutManager import android.content.pm.ShortcutManager
import android.graphics.drawable.Icon import android.graphics.drawable.Icon
import android.graphics.drawable.LayerDrawable import android.graphics.drawable.LayerDrawable
import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.provider.Telephony import android.provider.Telephony
import android.text.TextUtils import android.text.TextUtils
import android.widget.Toast
import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.coordinatorlayout.widget.CoordinatorLayout
import com.simplemobiletools.commons.dialogs.FilePickerDialog
import com.simplemobiletools.commons.dialogs.PermissionRequiredDialog import com.simplemobiletools.commons.dialogs.PermissionRequiredDialog
import com.simplemobiletools.commons.extensions.* import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.* import com.simplemobiletools.commons.helpers.*
@ -25,8 +21,6 @@ import com.simplemobiletools.smsmessenger.BuildConfig
import com.simplemobiletools.smsmessenger.R import com.simplemobiletools.smsmessenger.R
import com.simplemobiletools.smsmessenger.adapters.ConversationsAdapter import com.simplemobiletools.smsmessenger.adapters.ConversationsAdapter
import com.simplemobiletools.smsmessenger.adapters.SearchResultsAdapter import com.simplemobiletools.smsmessenger.adapters.SearchResultsAdapter
import com.simplemobiletools.smsmessenger.dialogs.ExportMessagesDialog
import com.simplemobiletools.smsmessenger.dialogs.ImportMessagesDialog
import com.simplemobiletools.smsmessenger.extensions.* import com.simplemobiletools.smsmessenger.extensions.*
import com.simplemobiletools.smsmessenger.helpers.* import com.simplemobiletools.smsmessenger.helpers.*
import com.simplemobiletools.smsmessenger.models.Conversation import com.simplemobiletools.smsmessenger.models.Conversation
@ -37,19 +31,14 @@ import kotlinx.android.synthetic.main.activity_main.*
import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode import org.greenrobot.eventbus.ThreadMode
import java.io.FileOutputStream
import java.io.OutputStream
class MainActivity : SimpleActivity() { class MainActivity : SimpleActivity() {
private val MAKE_DEFAULT_APP_REQUEST = 1 private val MAKE_DEFAULT_APP_REQUEST = 1
private val PICK_IMPORT_SOURCE_INTENT = 11
private val PICK_EXPORT_FILE_INTENT = 21
private var storedTextColor = 0 private var storedTextColor = 0
private var storedFontSize = 0 private var storedFontSize = 0
private var lastSearchedText = "" private var lastSearchedText = ""
private var bus: EventBus? = null private var bus: EventBus? = null
private val smsExporter by lazy { MessagesExporter(this) }
private var wasProtectionHandled = false private var wasProtectionHandled = false
@SuppressLint("InlinedApi") @SuppressLint("InlinedApi")
@ -176,8 +165,6 @@ class MainActivity : SimpleActivity() {
main_menu.getToolbar().setOnMenuItemClickListener { menuItem -> main_menu.getToolbar().setOnMenuItemClickListener { menuItem ->
when (menuItem.itemId) { when (menuItem.itemId) {
R.id.import_messages -> tryImportMessages()
R.id.export_messages -> tryToExportMessages()
R.id.more_apps_from_us -> launchMoreAppsFromUsIntent() R.id.more_apps_from_us -> launchMoreAppsFromUsIntent()
R.id.show_recycle_bin -> launchRecycleBin() R.id.show_recycle_bin -> launchRecycleBin()
R.id.show_archived -> launchArchivedConversations() R.id.show_archived -> launchArchivedConversations()
@ -204,11 +191,6 @@ class MainActivity : SimpleActivity() {
} else { } else {
finish() finish()
} }
} else if (requestCode == PICK_IMPORT_SOURCE_INTENT && resultCode == Activity.RESULT_OK && resultData != null && resultData.data != null) {
tryImportMessagesFromFile(resultData.data!!)
} else if (requestCode == PICK_EXPORT_FILE_INTENT && resultCode == Activity.RESULT_OK && resultData != null && resultData.data != null) {
val outputStream = contentResolver.openOutputStream(resultData.data!!)
exportMessagesTo(outputStream)
} }
} }
@ -602,119 +584,6 @@ class MainActivity : SimpleActivity() {
startAboutActivity(R.string.app_name, licenses, BuildConfig.VERSION_NAME, faqItems, true) startAboutActivity(R.string.app_name, licenses, BuildConfig.VERSION_NAME, faqItems, true)
} }
private fun tryToExportMessages() {
if (isQPlus()) {
ExportMessagesDialog(this, config.lastExportPath, true) { file ->
Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
type = JSON_MIME_TYPE
putExtra(Intent.EXTRA_TITLE, file.name)
addCategory(Intent.CATEGORY_OPENABLE)
try {
startActivityForResult(this, PICK_EXPORT_FILE_INTENT)
} catch (e: ActivityNotFoundException) {
toast(R.string.system_service_disabled, Toast.LENGTH_LONG)
} catch (e: Exception) {
showErrorToast(e)
}
}
}
} else {
handlePermission(PERMISSION_WRITE_STORAGE) {
if (it) {
ExportMessagesDialog(this, config.lastExportPath, false) { file ->
getFileOutputStream(file.toFileDirItem(this), true) { outStream ->
exportMessagesTo(outStream)
}
}
}
}
}
}
private fun exportMessagesTo(outputStream: OutputStream?) {
toast(R.string.exporting)
ensureBackgroundThread {
smsExporter.exportMessages(outputStream) {
val toastId = when (it) {
MessagesExporter.ExportResult.EXPORT_OK -> R.string.exporting_successful
else -> R.string.exporting_failed
}
toast(toastId)
}
}
}
private fun tryImportMessages() {
if (isQPlus()) {
Intent(Intent.ACTION_GET_CONTENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = JSON_MIME_TYPE
putExtra(Intent.EXTRA_MIME_TYPES, arrayOf(JSON_MIME_TYPE, XML_MIME_TYPE, TXT_MIME_TYPE))
try {
startActivityForResult(this, PICK_IMPORT_SOURCE_INTENT)
} catch (e: ActivityNotFoundException) {
toast(R.string.system_service_disabled, Toast.LENGTH_LONG)
} catch (e: Exception) {
showErrorToast(e)
}
}
} else {
handlePermission(PERMISSION_READ_STORAGE) {
if (it) {
importMessages()
}
}
}
}
private fun importMessages() {
FilePickerDialog(this) {
showImportMessagesDialog(it)
}
}
private fun showImportMessagesDialog(path: String) {
ImportMessagesDialog(this, path)
}
private fun tryImportMessagesFromFile(uri: Uri) {
when (uri.scheme) {
"file" -> showImportMessagesDialog(uri.path!!)
"content" -> {
var tempFile = getTempFile("messages", "backup.json")
if (tempFile == null) {
toast(R.string.unknown_error_occurred)
return
}
try {
val inputStream = contentResolver.openInputStream(uri)
val out = FileOutputStream(tempFile)
inputStream!!.copyTo(out)
// Check is XML and properly rename
tempFile.bufferedReader().use {
if (it.readLine().startsWith("<?xml")) {
val xmlFile = getTempFile("messages", "backup.xml")
if (xmlFile == null || tempFile?.renameTo(xmlFile) == false) {
toast(R.string.unknown_error_occurred)
return
}
tempFile = xmlFile
}
}
showImportMessagesDialog(tempFile!!.absolutePath)
} catch (e: Exception) {
showErrorToast(e)
}
}
else -> toast(R.string.invalid_file_format)
}
}
@Subscribe(threadMode = ThreadMode.MAIN) @Subscribe(threadMode = ThreadMode.MAIN)
fun refreshMessages(event: Events.RefreshMessages) { fun refreshMessages(event: Events.RefreshMessages) {
initMessenger() initMessenger()

View File

@ -2,24 +2,33 @@ package com.simplemobiletools.smsmessenger.activities
import android.annotation.TargetApi import android.annotation.TargetApi
import android.content.Intent import android.content.Intent
import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import androidx.activity.result.contract.ActivityResultContracts
import com.simplemobiletools.commons.activities.ManageBlockedNumbersActivity import com.simplemobiletools.commons.activities.ManageBlockedNumbersActivity
import com.simplemobiletools.commons.dialogs.* import com.simplemobiletools.commons.dialogs.*
import com.simplemobiletools.commons.extensions.* import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.* import com.simplemobiletools.commons.helpers.*
import com.simplemobiletools.commons.models.RadioItem import com.simplemobiletools.commons.models.RadioItem
import com.simplemobiletools.smsmessenger.R import com.simplemobiletools.smsmessenger.R
import com.simplemobiletools.smsmessenger.dialogs.ExportMessagesDialog
import com.simplemobiletools.smsmessenger.extensions.config import com.simplemobiletools.smsmessenger.extensions.config
import com.simplemobiletools.smsmessenger.extensions.emptyMessagesRecycleBin import com.simplemobiletools.smsmessenger.extensions.emptyMessagesRecycleBin
import com.simplemobiletools.smsmessenger.extensions.messagesDB import com.simplemobiletools.smsmessenger.extensions.messagesDB
import com.simplemobiletools.smsmessenger.helpers.* import com.simplemobiletools.smsmessenger.helpers.*
import com.simplemobiletools.smsmessenger.models.*
import kotlinx.android.synthetic.main.activity_settings.* import kotlinx.android.synthetic.main.activity_settings.*
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import java.util.* import java.util.*
import kotlin.system.exitProcess
class SettingsActivity : SimpleActivity() { class SettingsActivity : SimpleActivity() {
private var blockedNumbersAtPause = -1 private var blockedNumbersAtPause = -1
private var recycleBinMessages = 0 private var recycleBinMessages = 0
private val messagesFileType = "application/json"
private val messageImportFileTypes = listOf("application/json", "application/xml", "text/xml")
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
isMaterialActivity = true isMaterialActivity = true
@ -54,6 +63,8 @@ class SettingsActivity : SimpleActivity() {
setupUseRecycleBin() setupUseRecycleBin()
setupEmptyRecycleBin() setupEmptyRecycleBin()
setupAppPasswordProtection() setupAppPasswordProtection()
setupMessagesExport()
setupMessagesImport()
updateTextColors(settings_nested_scrollview) updateTextColors(settings_nested_scrollview)
if (blockedNumbersAtPause != -1 && blockedNumbersAtPause != getBlockedNumbers().hashCode()) { if (blockedNumbersAtPause != -1 && blockedNumbersAtPause != getBlockedNumbers().hashCode()) {
@ -66,12 +77,63 @@ class SettingsActivity : SimpleActivity() {
settings_outgoing_messages_label, settings_outgoing_messages_label,
settings_notifications_label, settings_notifications_label,
settings_recycle_bin_label, settings_recycle_bin_label,
settings_security_label settings_security_label,
settings_migrating_label
).forEach { ).forEach {
it.setTextColor(getProperPrimaryColor()) it.setTextColor(getProperPrimaryColor())
} }
} }
private val getContent = registerForActivityResult(ActivityResultContracts.OpenDocument()) { uri ->
if (uri != null) {
MessagesImporter(this).importMessages(uri)
}
}
private val saveDocument = registerForActivityResult(ActivityResultContracts.CreateDocument(messagesFileType)) { uri ->
if (uri != null) {
toast(R.string.exporting)
exportMessages(uri)
}
}
private fun setupMessagesExport() {
settings_export_messages_holder.setOnClickListener {
ExportMessagesDialog(this) { fileName ->
saveDocument.launch(fileName)
}
}
}
private fun setupMessagesImport() {
settings_import_messages_holder.setOnClickListener {
getContent.launch(messageImportFileTypes.toTypedArray())
}
}
private fun exportMessages(uri: Uri) {
ensureBackgroundThread {
try {
MessagesReader(this).getMessagesToExport(config.exportSms, config.exportMms) { messagesToExport ->
if (messagesToExport.isEmpty()) {
toast(R.string.no_entries_for_exporting)
return@getMessagesToExport
}
val json = Json { encodeDefaults = true }
val jsonString = json.encodeToString(messagesToExport)
val outputStream = contentResolver.openOutputStream(uri)!!
outputStream.use {
it.write(jsonString.toByteArray())
}
toast(R.string.exporting_successful)
}
} catch (e: Exception) {
showErrorToast(e)
}
}
}
override fun onPause() { override fun onPause() {
super.onPause() super.onPause()
blockedNumbersAtPause = getBlockedNumbers().hashCode() blockedNumbersAtPause = getBlockedNumbers().hashCode()
@ -104,7 +166,7 @@ class SettingsActivity : SimpleActivity() {
settings_use_english_holder.setOnClickListener { settings_use_english_holder.setOnClickListener {
settings_use_english.toggle() settings_use_english.toggle()
config.useEnglish = settings_use_english.isChecked config.useEnglish = settings_use_english.isChecked
System.exit(0) exitProcess(0)
} }
} }

View File

@ -2,42 +2,27 @@ package com.simplemobiletools.smsmessenger.dialogs
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import com.simplemobiletools.commons.dialogs.FilePickerDialog
import com.simplemobiletools.commons.extensions.* import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.smsmessenger.R import com.simplemobiletools.smsmessenger.R
import com.simplemobiletools.smsmessenger.activities.SimpleActivity import com.simplemobiletools.smsmessenger.activities.SimpleActivity
import com.simplemobiletools.smsmessenger.extensions.config import com.simplemobiletools.smsmessenger.extensions.config
import com.simplemobiletools.smsmessenger.helpers.JSON_FILE_EXTENSION import kotlinx.android.synthetic.main.dialog_export_messages.view.export_messages_filename
import kotlinx.android.synthetic.main.dialog_export_messages.view.* import kotlinx.android.synthetic.main.dialog_export_messages.view.export_mms_checkbox
import java.io.File import kotlinx.android.synthetic.main.dialog_export_messages.view.export_sms_checkbox
class ExportMessagesDialog( class ExportMessagesDialog(
private val activity: SimpleActivity, private val activity: SimpleActivity,
private val path: String, private val callback: (fileName: String) -> Unit,
private val hidePath: Boolean,
private val callback: (file: File) -> Unit,
) { ) {
private var realPath = if (path.isEmpty()) activity.internalStoragePath else path
private val config = activity.config private val config = activity.config
init { init {
val view = (activity.layoutInflater.inflate(R.layout.dialog_export_messages, null) as ViewGroup).apply { val view = (activity.layoutInflater.inflate(R.layout.dialog_export_messages, null) as ViewGroup).apply {
export_messages_folder.setText(activity.humanizePath(realPath))
export_messages_filename.setText("${activity.getString(R.string.messages)}_${activity.getCurrentFormattedDateTime()}")
export_sms_checkbox.isChecked = config.exportSms export_sms_checkbox.isChecked = config.exportSms
export_mms_checkbox.isChecked = config.exportMms export_mms_checkbox.isChecked = config.exportMms
export_messages_filename.setText(
if (hidePath) { activity.getString(R.string.messages) + "_" + activity.getCurrentFormattedDateTime()
export_messages_folder_hint.beGone() )
} else {
export_messages_folder.setOnClickListener {
activity.hideKeyboard(export_messages_filename)
FilePickerDialog(activity, realPath, false, showFAB = true) {
export_messages_folder.setText(activity.humanizePath(it))
realPath = it
}
}
}
} }
activity.getAlertDialogBuilder() activity.getAlertDialogBuilder()
@ -45,29 +30,17 @@ class ExportMessagesDialog(
.setNegativeButton(R.string.cancel, null) .setNegativeButton(R.string.cancel, null)
.apply { .apply {
activity.setupDialogStuff(view, this, R.string.export_messages) { alertDialog -> activity.setupDialogStuff(view, this, R.string.export_messages) { alertDialog ->
alertDialog.showKeyboard(view.export_messages_filename)
alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener { alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener {
config.exportSms = view.export_sms_checkbox.isChecked
config.exportMms = view.export_mms_checkbox.isChecked
val filename = view.export_messages_filename.value val filename = view.export_messages_filename.value
when { when {
filename.isEmpty() -> activity.toast(R.string.empty_name) filename.isEmpty() -> activity.toast(R.string.empty_name)
filename.isAValidFilename() -> { filename.isAValidFilename() -> {
val file = File(realPath, "$filename$JSON_FILE_EXTENSION") callback(filename)
if (!hidePath && file.exists()) {
activity.toast(R.string.name_taken)
return@setOnClickListener
}
if (!view.export_sms_checkbox.isChecked && !view.export_mms_checkbox.isChecked) {
activity.toast(R.string.no_option_selected)
return@setOnClickListener
}
config.exportSms = view.export_sms_checkbox.isChecked
config.exportMms = view.export_mms_checkbox.isChecked
config.lastExportPath = file.absolutePath.getParentPath()
callback(file)
alertDialog.dismiss() alertDialog.dismiss()
} }
else -> activity.toast(R.string.invalid_name) else -> activity.toast(R.string.invalid_name)
} }
} }

View File

@ -10,13 +10,13 @@ import com.simplemobiletools.smsmessenger.R
import com.simplemobiletools.smsmessenger.activities.SimpleActivity import com.simplemobiletools.smsmessenger.activities.SimpleActivity
import com.simplemobiletools.smsmessenger.extensions.config import com.simplemobiletools.smsmessenger.extensions.config
import com.simplemobiletools.smsmessenger.helpers.MessagesImporter import com.simplemobiletools.smsmessenger.helpers.MessagesImporter
import com.simplemobiletools.smsmessenger.helpers.MessagesImporter.ImportResult.IMPORT_OK import com.simplemobiletools.smsmessenger.models.MessagesBackup
import com.simplemobiletools.smsmessenger.helpers.MessagesImporter.ImportResult.IMPORT_PARTIAL import com.simplemobiletools.smsmessenger.models.ImportResult
import kotlinx.android.synthetic.main.dialog_import_messages.view.* import kotlinx.android.synthetic.main.dialog_import_messages.view.*
class ImportMessagesDialog( class ImportMessagesDialog(
private val activity: SimpleActivity, private val activity: SimpleActivity,
private val path: String, private val messages: List<MessagesBackup>,
) { ) {
private val config = activity.config private val config = activity.config
@ -48,7 +48,7 @@ class ImportMessagesDialog(
config.importSms = view.import_sms_checkbox.isChecked config.importSms = view.import_sms_checkbox.isChecked
config.importMms = view.import_mms_checkbox.isChecked config.importMms = view.import_mms_checkbox.isChecked
ensureBackgroundThread { ensureBackgroundThread {
MessagesImporter(activity).importMessages(path) { MessagesImporter(activity).restoreMessages(messages) {
handleParseResult(it) handleParseResult(it)
alertDialog.dismiss() alertDialog.dismiss()
} }
@ -58,11 +58,12 @@ class ImportMessagesDialog(
} }
} }
private fun handleParseResult(result: MessagesImporter.ImportResult) { private fun handleParseResult(result: ImportResult) {
activity.toast( activity.toast(
when (result) { when (result) {
IMPORT_OK -> R.string.importing_successful ImportResult.IMPORT_OK -> R.string.importing_successful
IMPORT_PARTIAL -> R.string.importing_some_entries_failed ImportResult.IMPORT_PARTIAL -> R.string.importing_some_entries_failed
ImportResult.IMPORT_FAIL -> R.string.importing_failed
else -> R.string.no_items_found else -> R.string.no_items_found
} }
) )

View File

@ -1,68 +0,0 @@
package com.simplemobiletools.smsmessenger.helpers
import android.content.Context
import com.google.gson.Gson
import com.google.gson.stream.JsonWriter
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
import com.simplemobiletools.smsmessenger.extensions.config
import com.simplemobiletools.smsmessenger.extensions.getConversationIds
import java.io.OutputStream
class MessagesExporter(private val context: Context) {
enum class ExportResult {
EXPORT_FAIL, EXPORT_OK
}
private val config = context.config
private val messageReader = MessagesReader(context)
private val gson = Gson()
fun exportMessages(outputStream: OutputStream?, onProgress: (total: Int, current: Int) -> Unit = { _, _ -> }, callback: (result: ExportResult) -> Unit) {
ensureBackgroundThread {
if (outputStream == null) {
callback.invoke(ExportResult.EXPORT_FAIL)
return@ensureBackgroundThread
}
val writer = JsonWriter(outputStream.bufferedWriter())
writer.use {
try {
var written = 0
writer.beginArray()
val conversationIds = context.getConversationIds()
val totalMessages = messageReader.getMessagesCount()
for (threadId in conversationIds) {
writer.beginObject()
if (config.exportSms && messageReader.getSmsCount() > 0) {
writer.name("sms")
writer.beginArray()
messageReader.forEachSms(threadId) {
writer.jsonValue(gson.toJson(it))
written++
onProgress.invoke(totalMessages, written)
}
writer.endArray()
}
if (config.exportMms && messageReader.getMmsCount() > 0) {
writer.name("mms")
writer.beginArray()
messageReader.forEachMms(threadId) {
writer.jsonValue(gson.toJson(it))
written++
onProgress.invoke(totalMessages, written)
}
writer.endArray()
}
writer.endObject()
}
writer.endArray()
callback.invoke(ExportResult.EXPORT_OK)
} catch (e: Exception) {
callback.invoke(ExportResult.EXPORT_FAIL)
}
}
}
}
}

View File

@ -1,172 +1,145 @@
package com.simplemobiletools.smsmessenger.helpers package com.simplemobiletools.smsmessenger.helpers
import android.content.Context import android.net.Uri
import android.util.JsonToken
import android.util.Xml import android.util.Xml
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import com.simplemobiletools.commons.extensions.showErrorToast import com.simplemobiletools.commons.extensions.showErrorToast
import com.simplemobiletools.commons.extensions.toast
import com.simplemobiletools.commons.helpers.ensureBackgroundThread import com.simplemobiletools.commons.helpers.ensureBackgroundThread
import com.simplemobiletools.smsmessenger.R
import com.simplemobiletools.smsmessenger.activities.SimpleActivity
import com.simplemobiletools.smsmessenger.dialogs.ImportMessagesDialog
import com.simplemobiletools.smsmessenger.extensions.config import com.simplemobiletools.smsmessenger.extensions.config
import com.simplemobiletools.smsmessenger.helpers.MessagesImporter.ImportResult.IMPORT_FAIL import com.simplemobiletools.smsmessenger.models.*
import com.simplemobiletools.smsmessenger.helpers.MessagesImporter.ImportResult.IMPORT_NOTHING_NEW import kotlinx.serialization.SerializationException
import com.simplemobiletools.smsmessenger.helpers.MessagesImporter.ImportResult.IMPORT_OK import kotlinx.serialization.decodeFromString
import com.simplemobiletools.smsmessenger.helpers.MessagesImporter.ImportResult.IMPORT_PARTIAL import kotlinx.serialization.json.Json
import com.simplemobiletools.smsmessenger.models.MmsBackup
import com.simplemobiletools.smsmessenger.models.SmsBackup
import org.xmlpull.v1.XmlPullParser import org.xmlpull.v1.XmlPullParser
import java.io.File
import java.io.InputStream import java.io.InputStream
class MessagesImporter(private val context: Context) {
enum class ImportResult {
IMPORT_FAIL, IMPORT_OK, IMPORT_PARTIAL, IMPORT_NOTHING_NEW
}
private val gson = Gson() class MessagesImporter(private val activity: SimpleActivity) {
private val messageWriter = MessagesWriter(context)
private val config = context.config private val messageWriter = MessagesWriter(activity)
private val config = activity.config
private var messagesImported = 0 private var messagesImported = 0
private var messagesFailed = 0 private var messagesFailed = 0
fun importMessages(path: String, onProgress: (total: Int, current: Int) -> Unit = { _, _ -> }, callback: (result: ImportResult) -> Unit) { fun importMessages(uri: Uri) {
try {
val fileType = activity.contentResolver.getType(uri).orEmpty()
val isXml = isXmlMimeType(fileType) || (uri.path?.endsWith("txt") == true && isFileXml(uri))
if (isXml) {
activity.toast(R.string.importing)
getInputStreamFromUri(uri)!!.importXml()
} else {
importJson(uri)
}
} catch (e: Exception) {
activity.showErrorToast(e)
}
}
private fun importJson(uri: Uri) {
try {
val jsonString = activity.contentResolver.openInputStream(uri)!!.use { inputStream ->
inputStream.bufferedReader().readText()
}
val deserializedList = Json.decodeFromString<List<MessagesBackup>>(jsonString)
if (deserializedList.isEmpty()) {
activity.toast(R.string.no_entries_for_importing)
return
}
ImportMessagesDialog(activity, deserializedList)
} catch (e: SerializationException) {
activity.toast(R.string.invalid_file_format)
} catch (e: IllegalArgumentException) {
activity.toast(R.string.invalid_file_format)
} catch (e: Exception) {
activity.showErrorToast(e)
}
}
fun restoreMessages(messagesBackup: List<MessagesBackup>, callback: (ImportResult) -> Unit) {
ensureBackgroundThread { ensureBackgroundThread {
try { try {
val isXml = if (path.endsWith("txt")) { messagesBackup.forEach { message ->
// Need to read the first line to determine if it is xml try {
val tempStream = getInputStreamForPath(path) if (message.backupType == BackupType.SMS && config.importSms) {
tempStream.bufferedReader().use { messageWriter.writeSmsMessage(message as SmsBackup)
it.readLine().startsWith("<?xml") messagesImported++
} else if (message.backupType == BackupType.MMS && config.importMms) {
messageWriter.writeMmsMessage(message as MmsBackup)
messagesImported++
}
} catch (e: Exception) {
activity.showErrorToast(e)
messagesFailed++
} }
} else {
path.endsWith("xml")
} }
refreshMessages()
val inputStream = getInputStreamForPath(path)
if (isXml) {
inputStream.importXml()
} else {
inputStream.importJson()
}
} catch (e: Exception) { } catch (e: Exception) {
context.showErrorToast(e) activity.showErrorToast(e)
messagesFailed++
} }
callback.invoke( callback.invoke(
when { when {
messagesImported == 0 && messagesFailed == 0 -> IMPORT_NOTHING_NEW messagesImported == 0 && messagesFailed == 0 -> ImportResult.IMPORT_NOTHING_NEW
messagesFailed > 0 && messagesImported > 0 -> IMPORT_PARTIAL messagesFailed > 0 && messagesImported > 0 -> ImportResult.IMPORT_PARTIAL
messagesFailed > 0 -> IMPORT_FAIL messagesFailed > 0 -> ImportResult.IMPORT_FAIL
else -> IMPORT_OK else -> ImportResult.IMPORT_OK
} }
) )
} }
} }
private fun getInputStreamForPath(path: String): InputStream {
return if (path.contains("/")) {
File(path).inputStream()
} else {
context.assets.open(path)
}
}
private fun InputStream.importJson() {
bufferedReader().use { reader ->
val jsonReader = gson.newJsonReader(reader)
val smsMessageType = object : TypeToken<SmsBackup>() {}.type
val mmsMessageType = object : TypeToken<MmsBackup>() {}.type
jsonReader.beginArray()
while (jsonReader.hasNext()) {
jsonReader.beginObject()
while (jsonReader.hasNext()) {
val nextToken = jsonReader.peek()
if (nextToken.ordinal == JsonToken.NAME.ordinal) {
val msgType = jsonReader.nextName()
if ((!msgType.equals("sms") && !msgType.equals("mms")) ||
(msgType.equals("sms") && !config.importSms) ||
(msgType.equals("mms") && !config.importMms)
) {
jsonReader.skipValue()
continue
}
jsonReader.beginArray()
while (jsonReader.hasNext()) {
try {
if (msgType.equals("sms")) {
val message = gson.fromJson<SmsBackup>(jsonReader, smsMessageType)
messageWriter.writeSmsMessage(message)
} else {
val message = gson.fromJson<MmsBackup>(jsonReader, mmsMessageType)
messageWriter.writeMmsMessage(message)
}
messagesImported++
} catch (e: Exception) {
context.showErrorToast(e)
messagesFailed++
}
}
jsonReader.endArray()
} else {
jsonReader.skipValue()
}
}
jsonReader.endObject()
refreshMessages()
}
jsonReader.endArray()
}
}
private fun InputStream.importXml() { private fun InputStream.importXml() {
bufferedReader().use { reader -> try {
val xmlParser = Xml.newPullParser().apply { bufferedReader().use { reader ->
setInput(reader) val xmlParser = Xml.newPullParser().apply {
} setInput(reader)
xmlParser.nextTag()
xmlParser.require(XmlPullParser.START_TAG, null, "smses")
var depth = 1
while (depth != 0) {
when (xmlParser.next()) {
XmlPullParser.END_TAG -> depth--
XmlPullParser.START_TAG -> depth++
} }
if (xmlParser.eventType != XmlPullParser.START_TAG) { xmlParser.nextTag()
continue xmlParser.require(XmlPullParser.START_TAG, null, "smses")
}
try { var depth = 1
if (xmlParser.name == "sms") { while (depth != 0) {
if (config.importSms) { when (xmlParser.next()) {
val message = xmlParser.readSms() XmlPullParser.END_TAG -> depth--
messageWriter.writeSmsMessage(message) XmlPullParser.START_TAG -> depth++
messagesImported++ }
if (xmlParser.eventType != XmlPullParser.START_TAG) {
continue
}
try {
if (xmlParser.name == "sms") {
if (config.importSms) {
val message = xmlParser.readSms()
messageWriter.writeSmsMessage(message)
messagesImported++
} else {
xmlParser.skip()
}
} else { } else {
xmlParser.skip() xmlParser.skip()
} }
} else { } catch (e: Exception) {
xmlParser.skip() activity.showErrorToast(e)
messagesFailed++
} }
} catch (e: Exception) {
context.showErrorToast(e)
messagesFailed++
} }
refreshMessages()
} }
when {
refreshMessages() messagesFailed > 0 && messagesImported > 0 -> activity.toast(R.string.importing_some_entries_failed)
messagesFailed > 0 -> activity.toast(R.string.importing_failed)
else -> activity.toast(R.string.importing_successful)
}
} catch (_: Exception) {
activity.toast(R.string.invalid_file_format)
} }
} }
@ -200,4 +173,27 @@ class MessagesImporter(private val context: Context) {
} }
} }
} }
private fun getInputStreamFromUri(uri: Uri): InputStream? {
return try {
activity.contentResolver.openInputStream(uri)
} catch (e: Exception) {
null
}
}
private fun isFileXml(uri: Uri): Boolean {
val inputStream = getInputStreamFromUri(uri)
return inputStream?.bufferedReader()?.use { reader ->
reader.readLine()?.startsWith("<?xml") ?: false
} ?: false
}
private fun isXmlMimeType(mimeType: String): Boolean {
return mimeType.equals("application/xml", ignoreCase = true) || mimeType.equals("text/xml", ignoreCase = true)
}
private fun isJsonMimeType(mimeType: String): Boolean {
return mimeType.equals("application/json", ignoreCase = true)
}
} }

View File

@ -9,15 +9,30 @@ import android.util.Base64
import com.simplemobiletools.commons.extensions.* import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.isQPlus import com.simplemobiletools.commons.helpers.isQPlus
import com.simplemobiletools.commons.helpers.isRPlus import com.simplemobiletools.commons.helpers.isRPlus
import com.simplemobiletools.smsmessenger.models.MmsAddress import com.simplemobiletools.smsmessenger.extensions.getConversationIds
import com.simplemobiletools.smsmessenger.models.MmsBackup import com.simplemobiletools.smsmessenger.models.*
import com.simplemobiletools.smsmessenger.models.MmsPart
import com.simplemobiletools.smsmessenger.models.SmsBackup
import java.io.IOException import java.io.IOException
import java.io.InputStream import java.io.InputStream
class MessagesReader(private val context: Context) { class MessagesReader(private val context: Context) {
fun forEachSms(threadId: Long, block: (SmsBackup) -> Unit) {
fun getMessagesToExport(
getSms: Boolean, getMms: Boolean, callback: (messages: List<MessagesBackup>) -> Unit
) {
val conversationIds = context.getConversationIds()
var smsMessages = listOf<SmsBackup>()
var mmsMessages = listOf<MmsBackup>()
if (getSms) {
smsMessages = getSmsMessages(conversationIds)
}
if (getMms) {
mmsMessages = getMmsMessages(conversationIds)
}
callback(smsMessages + mmsMessages)
}
private fun getSmsMessages(threadIds: List<Long>): List<SmsBackup> {
val projection = arrayOf( val projection = arrayOf(
Sms.SUBSCRIPTION_ID, Sms.SUBSCRIPTION_ID,
Sms.ADDRESS, Sms.ADDRESS,
@ -33,25 +48,28 @@ class MessagesReader(private val context: Context) {
) )
val selection = "${Sms.THREAD_ID} = ?" val selection = "${Sms.THREAD_ID} = ?"
val selectionArgs = arrayOf(threadId.toString()) val smsList = mutableListOf<SmsBackup>()
context.queryCursor(Sms.CONTENT_URI, projection, selection, selectionArgs) { cursor ->
val subscriptionId = cursor.getLongValue(Sms.SUBSCRIPTION_ID) threadIds.map { it.toString() }.forEach { threadId ->
val address = cursor.getStringValue(Sms.ADDRESS) context.queryCursor(Sms.CONTENT_URI, projection, selection, arrayOf(threadId)) { cursor ->
val body = cursor.getStringValueOrNull(Sms.BODY) val subscriptionId = cursor.getLongValue(Sms.SUBSCRIPTION_ID)
val date = cursor.getLongValue(Sms.DATE) val address = cursor.getStringValue(Sms.ADDRESS)
val dateSent = cursor.getLongValue(Sms.DATE_SENT) val body = cursor.getStringValueOrNull(Sms.BODY)
val locked = cursor.getIntValue(Sms.LOCKED) val date = cursor.getLongValue(Sms.DATE)
val protocol = cursor.getStringValueOrNull(Sms.PROTOCOL) val dateSent = cursor.getLongValue(Sms.DATE_SENT)
val read = cursor.getIntValue(Sms.READ) val locked = cursor.getIntValue(Sms.DATE_SENT)
val status = cursor.getIntValue(Sms.STATUS) val protocol = cursor.getStringValueOrNull(Sms.PROTOCOL)
val type = cursor.getIntValue(Sms.TYPE) val read = cursor.getIntValue(Sms.READ)
val serviceCenter = cursor.getStringValueOrNull(Sms.SERVICE_CENTER) val status = cursor.getIntValue(Sms.STATUS)
block(SmsBackup(subscriptionId, address, body, date, dateSent, locked, protocol, read, status, type, serviceCenter)) val type = cursor.getIntValue(Sms.TYPE)
val serviceCenter = cursor.getStringValueOrNull(Sms.SERVICE_CENTER)
smsList.add(SmsBackup(subscriptionId, address, body, date, dateSent, locked, protocol, read, status, type, serviceCenter))
}
} }
return smsList
} }
// all mms from simple sms are non-text messages private fun getMmsMessages(threadIds: List<Long>, includeTextOnlyAttachment: Boolean = false): List<MmsBackup> {
fun forEachMms(threadId: Long, includeTextOnlyAttachment: Boolean = false, block: (MmsBackup) -> Unit) {
val projection = arrayOf( val projection = arrayOf(
Mms._ID, Mms._ID,
Mms.CREATOR, Mms.CREATOR,
@ -71,65 +89,67 @@ class MessagesReader(private val context: Context) {
Mms.SUBSCRIPTION_ID, Mms.SUBSCRIPTION_ID,
Mms.TRANSACTION_ID Mms.TRANSACTION_ID
) )
val selection = if (includeTextOnlyAttachment) { val selection = if (includeTextOnlyAttachment) {
"${Mms.THREAD_ID} = ? AND ${Mms.TEXT_ONLY} = ?" "${Mms.THREAD_ID} = ? AND ${Mms.TEXT_ONLY} = ?"
} else { } else {
"${Mms.THREAD_ID} = ?" "${Mms.THREAD_ID} = ?"
} }
val mmsList = mutableListOf<MmsBackup>()
val selectionArgs = if (includeTextOnlyAttachment) { threadIds.map { it.toString() }.forEach { threadId ->
arrayOf(threadId.toString(), "1") val selectionArgs = if (includeTextOnlyAttachment) {
} else { arrayOf(threadId, "1")
arrayOf(threadId.toString()) } else {
} arrayOf(threadId)
}
context.queryCursor(Mms.CONTENT_URI, projection, selection, selectionArgs) { cursor ->
val mmsId = cursor.getLongValue(Mms._ID)
val creator = cursor.getStringValueOrNull(Mms.CREATOR)
val contentType = cursor.getStringValueOrNull(Mms.CONTENT_TYPE)
val deliveryReport = cursor.getIntValue(Mms.DELIVERY_REPORT)
val date = cursor.getLongValue(Mms.DATE)
val dateSent = cursor.getLongValue(Mms.DATE_SENT)
val locked = cursor.getIntValue(Mms.LOCKED)
val messageType = cursor.getIntValue(Mms.MESSAGE_TYPE)
val messageBox = cursor.getIntValue(Mms.MESSAGE_BOX)
val read = cursor.getIntValue(Mms.READ)
val readReport = cursor.getIntValue(Mms.READ_REPORT)
val seen = cursor.getIntValue(Mms.SEEN)
val textOnly = cursor.getIntValue(Mms.TEXT_ONLY)
val status = cursor.getStringValueOrNull(Mms.STATUS)
val subject = cursor.getStringValueOrNull(Mms.SUBJECT)
val subjectCharSet = cursor.getStringValueOrNull(Mms.SUBJECT_CHARSET)
val subscriptionId = cursor.getLongValue(Mms.SUBSCRIPTION_ID)
val transactionId = cursor.getStringValueOrNull(Mms.TRANSACTION_ID)
context.queryCursor(Mms.CONTENT_URI, projection, selection, selectionArgs) { cursor -> val parts = getParts(mmsId)
val mmsId = cursor.getLongValue(Mms._ID) val addresses = getMmsAddresses(mmsId)
val creator = cursor.getStringValueOrNull(Mms.CREATOR) mmsList.add(
val contentType = cursor.getStringValueOrNull(Mms.CONTENT_TYPE) MmsBackup(
val deliveryReport = cursor.getIntValue(Mms.DELIVERY_REPORT) creator,
val date = cursor.getLongValue(Mms.DATE) contentType,
val dateSent = cursor.getLongValue(Mms.DATE_SENT) deliveryReport,
val locked = cursor.getIntValue(Mms.LOCKED) date,
val messageType = cursor.getIntValue(Mms.MESSAGE_TYPE) dateSent,
val messageBox = cursor.getIntValue(Mms.MESSAGE_BOX) locked,
val read = cursor.getIntValue(Mms.READ) messageType,
val readReport = cursor.getIntValue(Mms.READ_REPORT) messageBox,
val seen = cursor.getIntValue(Mms.SEEN) read,
val textOnly = cursor.getIntValue(Mms.TEXT_ONLY) readReport,
val status = cursor.getStringValueOrNull(Mms.STATUS) seen,
val subject = cursor.getStringValueOrNull(Mms.SUBJECT) textOnly,
val subjectCharSet = cursor.getStringValueOrNull(Mms.SUBJECT_CHARSET) status,
val subscriptionId = cursor.getLongValue(Mms.SUBSCRIPTION_ID) subject,
val transactionId = cursor.getStringValueOrNull(Mms.TRANSACTION_ID) subjectCharSet,
subscriptionId,
val parts = getParts(mmsId) transactionId,
val addresses = getMmsAddresses(mmsId) addresses,
block( parts
MmsBackup( )
creator,
contentType,
deliveryReport,
date,
dateSent,
locked,
messageType,
messageBox,
read,
readReport,
seen,
textOnly,
status,
subject,
subjectCharSet,
subscriptionId,
transactionId,
addresses,
parts
) )
) }
} }
return mmsList
} }
@SuppressLint("NewApi") @SuppressLint("NewApi")

View File

@ -0,0 +1,13 @@
package com.simplemobiletools.smsmessenger.models
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
enum class BackupType {
@SerialName("sms")
SMS,
@SerialName("mms")
MMS,
}

View File

@ -0,0 +1,5 @@
package com.simplemobiletools.smsmessenger.models
enum class ImportResult {
IMPORT_FAIL, IMPORT_OK, IMPORT_PARTIAL, IMPORT_NOTHING_NEW
}

View File

@ -0,0 +1,24 @@
package com.simplemobiletools.smsmessenger.models
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.SerializationException
import kotlinx.serialization.json.*
@Serializable(with = BackupSerializer::class)
sealed class MessagesBackup() {
@SerialName("backupType")
abstract val backupType: BackupType
}
object BackupSerializer :
JsonContentPolymorphicSerializer<MessagesBackup>(MessagesBackup::class) {
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<out MessagesBackup> {
return when (element.jsonObject["backupType"]?.jsonPrimitive?.content) {
"sms" -> SmsBackup.serializer()
"mms" -> MmsBackup.serializer()
else -> throw SerializationException("ERROR: No Serializer found. Serialization failed.")
}
}
}

View File

@ -4,7 +4,9 @@ import android.content.ContentValues
import android.provider.Telephony import android.provider.Telephony
import androidx.core.content.contentValuesOf import androidx.core.content.contentValuesOf
import com.google.gson.annotations.SerializedName import com.google.gson.annotations.SerializedName
import kotlinx.serialization.Serializable
@Serializable
data class MmsAddress( data class MmsAddress(
@SerializedName("address") @SerializedName("address")
val address: String, val address: String,

View File

@ -4,7 +4,9 @@ import android.content.ContentValues
import android.provider.Telephony import android.provider.Telephony
import androidx.core.content.contentValuesOf import androidx.core.content.contentValuesOf
import com.google.gson.annotations.SerializedName import com.google.gson.annotations.SerializedName
import kotlinx.serialization.Serializable
@Serializable
data class MmsBackup( data class MmsBackup(
@SerializedName("creator") @SerializedName("creator")
val creator: String?, val creator: String?,
@ -44,7 +46,9 @@ data class MmsBackup(
val addresses: List<MmsAddress>, val addresses: List<MmsAddress>,
@SerializedName("parts") @SerializedName("parts")
val parts: List<MmsPart>, val parts: List<MmsPart>,
) {
override val backupType: BackupType = BackupType.MMS,
): MessagesBackup() {
fun toContentValues(): ContentValues { fun toContentValues(): ContentValues {
return contentValuesOf( return contentValuesOf(

View File

@ -4,7 +4,9 @@ import android.content.ContentValues
import android.provider.Telephony import android.provider.Telephony
import androidx.core.content.contentValuesOf import androidx.core.content.contentValuesOf
import com.google.gson.annotations.SerializedName import com.google.gson.annotations.SerializedName
import kotlinx.serialization.Serializable
@Serializable
data class MmsPart( data class MmsPart(
@SerializedName("cd") @SerializedName("cd")
val contentDisposition: String?, val contentDisposition: String?,

View File

@ -5,7 +5,9 @@ import android.content.ContentValues
import android.provider.Telephony import android.provider.Telephony
import androidx.core.content.contentValuesOf import androidx.core.content.contentValuesOf
import com.google.gson.annotations.SerializedName import com.google.gson.annotations.SerializedName
import kotlinx.serialization.Serializable
@Serializable
data class SmsBackup( data class SmsBackup(
@SerializedName("sub_id") @SerializedName("sub_id")
val subscriptionId: Long, val subscriptionId: Long,
@ -28,8 +30,10 @@ data class SmsBackup(
@SerializedName("type") @SerializedName("type")
val type: Int, val type: Int,
@SerializedName("service_center") @SerializedName("service_center")
val serviceCenter: String? val serviceCenter: String?,
) {
override val backupType: BackupType = BackupType.SMS,
): MessagesBackup() {
fun toContentValues(): ContentValues { fun toContentValues(): ContentValues {
return contentValuesOf( return contentValuesOf(

View File

@ -431,6 +431,47 @@
android:text="@string/password_protect_whole_app" /> android:text="@string/password_protect_whole_app" />
</RelativeLayout> </RelativeLayout>
<include
android:id="@+id/settings_migrating_divider"
layout="@layout/divider" />
<TextView
android:id="@+id/settings_migrating_label"
style="@style/SettingsSectionLabelStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/migrating" />
<RelativeLayout
android:id="@+id/settings_export_messages_holder"
style="@style/SettingsHolderTextViewOneLinerStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.simplemobiletools.commons.views.MyTextView
android:id="@+id/settings_export_messages"
style="@style/SettingsTextLabelStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/export_messages" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/settings_import_messages_holder"
style="@style/SettingsHolderTextViewOneLinerStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.simplemobiletools.commons.views.MyTextView
android:id="@+id/settings_import_messages"
style="@style/SettingsTextLabelStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/import_messages" />
</RelativeLayout>
</LinearLayout> </LinearLayout>
</androidx.core.widget.NestedScrollView> </androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout> </androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -14,21 +14,6 @@
android:paddingTop="@dimen/activity_margin" android:paddingTop="@dimen/activity_margin"
android:paddingEnd="@dimen/activity_margin"> android:paddingEnd="@dimen/activity_margin">
<com.simplemobiletools.commons.views.MyTextInputLayout
android:id="@+id/export_messages_folder_hint"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/activity_margin"
android:hint="@string/folder">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/export_messages_folder"
style="@style/UnclickableEditText"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</com.simplemobiletools.commons.views.MyTextInputLayout>
<com.simplemobiletools.commons.views.MyTextInputLayout <com.simplemobiletools.commons.views.MyTextInputLayout
android:id="@+id/export_messages_filename_hint" android:id="@+id/export_messages_filename_hint"
android:layout_width="match_parent" android:layout_width="match_parent"

View File

@ -3,16 +3,6 @@
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
tools:ignore="AppCompatResource,AlwaysShowAction"> tools:ignore="AppCompatResource,AlwaysShowAction">
<item
android:id="@+id/import_messages"
android:showAsAction="never"
android:title="@string/import_messages"
app:showAsAction="never" />
<item
android:id="@+id/export_messages"
android:showAsAction="never"
android:title="@string/export_messages"
app:showAsAction="never" />
<item <item
android:id="@+id/show_recycle_bin" android:id="@+id/show_recycle_bin"
android:showAsAction="never" android:showAsAction="never"

View File

@ -63,14 +63,14 @@
<string name="mark_as_unread">وضع علامة كغير مقروءة</string> <string name="mark_as_unread">وضع علامة كغير مقروءة</string>
<string name="me">Me</string> <string name="me">Me</string>
<!-- Archive --> <!-- Archive -->
<string name="unarchive">Unarchive</string> <string name="unarchive">إلغاء الأرشفة</string>
<string name="empty_archive">Delete all archived conversations</string> <string name="empty_archive">حذف جميع المحادثات المؤرشفة</string>
<string name="archived_conversations">Archive</string> <string name="archived_conversations">أرشيف</string>
<string name="show_archived_conversations">Show archived conversations</string> <string name="show_archived_conversations">إظهار المحادثات المؤرشفة</string>
<string name="archive">Archive</string> <string name="archive">أرشيف</string>
<string name="no_archived_conversations">No archived conversations have been found</string> <string name="no_archived_conversations">لم يتم العثور على محادثات مؤرشفة</string>
<string name="archive_emptied_successfully">The archive has been emptied successfully</string> <string name="archive_emptied_successfully">تم إفراغ الأرشيف بنجاح</string>
<string name="empty_archive_confirmation">Are you sure you want to empty the archive? All archived conversations will be permanently lost.</string> <string name="empty_archive_confirmation">هل أنت متأكد من أنك تريد إفراغ الأرشيف؟ ستفقد جميع المحادثات المؤرشفة نهائيا.</string>
<!-- Recycle bin --> <!-- Recycle bin -->
<string name="restore">Restore</string> <string name="restore">Restore</string>
<string name="restore_all_messages">Restore all messages</string> <string name="restore_all_messages">Restore all messages</string>
@ -78,7 +78,7 @@
<string name="skip_the_recycle_bin_messages">Skip the Recycle Bin, delete messages directly</string> <string name="skip_the_recycle_bin_messages">Skip the Recycle Bin, delete messages directly</string>
<!-- Confirmation dialog --> <!-- Confirmation dialog -->
<string name="delete_whole_conversation_confirmation">هل أنت متأكد أنك تريد حذف كافة رسائل هذه المحادثة؟</string> <string name="delete_whole_conversation_confirmation">هل أنت متأكد أنك تريد حذف كافة رسائل هذه المحادثة؟</string>
<string name="archive_confirmation">Are you sure you want to archive %s?</string> <string name="archive_confirmation">هل أنت متأكد من أنك تريد أرشفة %s؟</string>
<string name="restore_whole_conversation_confirmation">Are you sure you want to restore all messages of this conversation?</string> <string name="restore_whole_conversation_confirmation">Are you sure you want to restore all messages of this conversation?</string>
<string name="restore_confirmation">Are you sure you want to restore %s?</string> <string name="restore_confirmation">Are you sure you want to restore %s?</string>
<!-- Are you sure you want to delete 5 conversations? --> <!-- Are you sure you want to delete 5 conversations? -->

View File

@ -47,11 +47,11 @@
<string name="update_message">Nachricht aktualisieren</string> <string name="update_message">Nachricht aktualisieren</string>
<string name="send_now">Jetzt senden</string> <string name="send_now">Jetzt senden</string>
<!-- Message details --> <!-- Message details -->
<string name="message_details">Message details</string> <string name="message_details">Nachrichtendetails</string>
<string name="message_details_sender">Sender</string> <string name="message_details_sender">Absender</string>
<string name="message_details_receiver">Receiver</string> <string name="message_details_receiver">Empfänger</string>
<string name="message_details_sent_at">Sent at</string> <string name="message_details_sent_at">Gesendet am</string>
<string name="message_details_received_at">Received at</string> <string name="message_details_received_at">Empfangen am</string>
<!-- Notifications --> <!-- Notifications -->
<string name="channel_received_sms">Empfangene SMS</string> <string name="channel_received_sms">Empfangene SMS</string>
<string name="new_message">Neue Nachricht</string> <string name="new_message">Neue Nachricht</string>
@ -59,14 +59,14 @@
<string name="mark_as_unread">Als ungelesen markieren</string> <string name="mark_as_unread">Als ungelesen markieren</string>
<string name="me">Ich</string> <string name="me">Ich</string>
<!-- Archive --> <!-- Archive -->
<string name="unarchive">Unarchive</string> <string name="unarchive">Dearchivieren</string>
<string name="empty_archive">Delete all archived conversations</string> <string name="empty_archive">Alle archivierten Unterhaltungen löschen</string>
<string name="archived_conversations">Archive</string> <string name="archived_conversations">Archiv</string>
<string name="show_archived_conversations">Show archived conversations</string> <string name="show_archived_conversations">Archivierte Unterhaltungen anzeigen</string>
<string name="archive">Archive</string> <string name="archive">Archivieren</string>
<string name="no_archived_conversations">No archived conversations have been found</string> <string name="no_archived_conversations">Es wurden keine archivierten Unterhaltungen gefunden</string>
<string name="archive_emptied_successfully">The archive has been emptied successfully</string> <string name="archive_emptied_successfully">Das Archiv wurde erfolgreich geleert</string>
<string name="empty_archive_confirmation">Are you sure you want to empty the archive? All archived conversations will be permanently lost.</string> <string name="empty_archive_confirmation">Das Archiv wirklich leeren\? Alle archivierten Unterhaltungen sind dann unwiederbringlich gelöscht.</string>
<!-- Recycle bin --> <!-- Recycle bin -->
<string name="restore">Restore</string> <string name="restore">Restore</string>
<string name="restore_all_messages">Restore all messages</string> <string name="restore_all_messages">Restore all messages</string>
@ -74,7 +74,7 @@
<string name="skip_the_recycle_bin_messages">Skip the Recycle Bin, delete messages directly</string> <string name="skip_the_recycle_bin_messages">Skip the Recycle Bin, delete messages directly</string>
<!-- Confirmation dialog --> <!-- Confirmation dialog -->
<string name="delete_whole_conversation_confirmation">Sollen wirklich alle Nachrichten dieser Unterhaltung gelöscht werden\?</string> <string name="delete_whole_conversation_confirmation">Sollen wirklich alle Nachrichten dieser Unterhaltung gelöscht werden\?</string>
<string name="archive_confirmation">Are you sure you want to archive %s?</string> <string name="archive_confirmation">%s wirklich archivieren\?</string>
<string name="restore_whole_conversation_confirmation">Are you sure you want to restore all messages of this conversation?</string> <string name="restore_whole_conversation_confirmation">Are you sure you want to restore all messages of this conversation?</string>
<string name="restore_confirmation">Are you sure you want to restore %s?</string> <string name="restore_confirmation">Are you sure you want to restore %s?</string>
<!-- Are you sure you want to delete 5 conversations? --> <!-- Are you sure you want to delete 5 conversations? -->
@ -88,11 +88,11 @@
<item quantity="other">%d Nachrichten</item> <item quantity="other">%d Nachrichten</item>
</plurals> </plurals>
<!-- Settings --> <!-- Settings -->
<string name="keyword">Keyword</string> <string name="keyword">Schlüsselwort</string>
<string name="blocked_keywords">Blocked keywords</string> <string name="blocked_keywords">Blockierte Schlüsselwörter</string>
<string name="manage_blocked_keywords">Manage blocked keywords</string> <string name="manage_blocked_keywords">Blockierte Schlüsselwörter verwalten</string>
<string name="not_blocking_keywords">You are not blocking any keywords. You may add keywords here to block all messages containing them.</string> <string name="not_blocking_keywords">Du hast keine Schlüsselwörter blockiert. Du kannst hier Schlüsselwörter hinzufügen, um alle Nachrichten zu blockieren, die sie enthalten.</string>
<string name="add_a_blocked_keyword">Add a blocked keyword</string> <string name="add_a_blocked_keyword">Ein zu blockierendes Schlüsselwort hinzufügen</string>
<string name="lock_screen_visibility">Sichtbarkeit von Benachrichtigungen auf dem Sperrbildschirm</string> <string name="lock_screen_visibility">Sichtbarkeit von Benachrichtigungen auf dem Sperrbildschirm</string>
<string name="sender_and_message">Absender und Nachricht</string> <string name="sender_and_message">Absender und Nachricht</string>
<string name="sender_only">Nur Absender</string> <string name="sender_only">Nur Absender</string>

View File

@ -59,14 +59,14 @@
<string name="mark_as_unread">Σήμανση ως Μη Αναγνωσμένο</string> <string name="mark_as_unread">Σήμανση ως Μη Αναγνωσμένο</string>
<string name="me">Εγώ</string> <string name="me">Εγώ</string>
<!-- Archive --> <!-- Archive -->
<string name="unarchive">Unarchive</string> <string name="unarchive">Μη αρχειοθέτηση</string>
<string name="empty_archive">Delete all archived conversations</string> <string name="empty_archive">Διαγραφή όλων των αρχειοθετημένων συνομιλιών</string>
<string name="archived_conversations">Archive</string> <string name="archived_conversations">Αρχειοθέτηση</string>
<string name="show_archived_conversations">Show archived conversations</string> <string name="show_archived_conversations">Εμφάνιση αρχειοθετημένων συνομιλιών</string>
<string name="archive">Archive</string> <string name="archive">Αρχειοθέτηση</string>
<string name="no_archived_conversations">No archived conversations have been found</string> <string name="no_archived_conversations">Δεν βρέθηκαν αρχειοθετημένες συνομιλίες</string>
<string name="archive_emptied_successfully">The archive has been emptied successfully</string> <string name="archive_emptied_successfully">Το αρχείο άδειασε με επιτυχία</string>
<string name="empty_archive_confirmation">Are you sure you want to empty the archive? All archived conversations will be permanently lost.</string> <string name="empty_archive_confirmation">Είστε σίγουροι ότι θέλετε να αδειάσετε το αρχείο; Όλες οι αρχειοθετημένες συνομιλίες θα χαθούν οριστικά.</string>
<!-- Recycle bin --> <!-- Recycle bin -->
<string name="restore">Restore</string> <string name="restore">Restore</string>
<string name="restore_all_messages">Restore all messages</string> <string name="restore_all_messages">Restore all messages</string>
@ -74,7 +74,7 @@
<string name="skip_the_recycle_bin_messages">Skip the Recycle Bin, delete messages directly</string> <string name="skip_the_recycle_bin_messages">Skip the Recycle Bin, delete messages directly</string>
<!-- Confirmation dialog --> <!-- Confirmation dialog -->
<string name="delete_whole_conversation_confirmation">Είστε βέβαιοι ότι θέλετε να διαγράψετε όλα τα μηνύματα αυτής της συνομιλίας;</string> <string name="delete_whole_conversation_confirmation">Είστε βέβαιοι ότι θέλετε να διαγράψετε όλα τα μηνύματα αυτής της συνομιλίας;</string>
<string name="archive_confirmation">Are you sure you want to archive %s?</string> <string name="archive_confirmation">Σίγουρα θέλετε να αρχειοθετήσετε %s;</string>
<string name="restore_whole_conversation_confirmation">Are you sure you want to restore all messages of this conversation?</string> <string name="restore_whole_conversation_confirmation">Are you sure you want to restore all messages of this conversation?</string>
<string name="restore_confirmation">Are you sure you want to restore %s?</string> <string name="restore_confirmation">Are you sure you want to restore %s?</string>
<!-- Are you sure you want to delete 5 conversations? --> <!-- Are you sure you want to delete 5 conversations? -->

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="app_name">Lihtne SMS Sõnumitooja</string> <string name="app_name">Lihtne SMS sõnumiklient</string>
<string name="app_launcher_name">SMS Sõnumitooja</string> <string name="app_launcher_name">SMS sõnumid</string>
<string name="type_a_message">Kirjuta sõnum…</string> <string name="type_a_message">Kirjuta sõnum…</string>
<string name="message_not_sent_short">Sõnum ei ole saadetud</string> <string name="message_not_sent_short">Sõnum ei ole saadetud</string>
<string name="message_not_sent_touch_retry">Saamata. Uuesti saatmiseks puuduta.</string> <string name="message_not_sent_touch_retry">Saamata. Uuesti saatmiseks puuduta.</string>
@ -16,7 +16,7 @@
<string name="no_reply_support">Sõnumi saatja ei toeta vastuste võimalust</string> <string name="no_reply_support">Sõnumi saatja ei toeta vastuste võimalust</string>
<string name="draft">Kavand</string> <string name="draft">Kavand</string>
<string name="sending">Saadame…</string> <string name="sending">Saadame…</string>
<string name="pin_conversation">Kinnitage ülaossa</string> <string name="pin_conversation">Kinnita ülaossa</string>
<string name="unpin_conversation">Eemalda kinnitus</string> <string name="unpin_conversation">Eemalda kinnitus</string>
<string name="forward_message">Edasta</string> <string name="forward_message">Edasta</string>
<string name="compress_error">Pildi muutmine valitud suurusesse ei õnnestu</string> <string name="compress_error">Pildi muutmine valitud suurusesse ei õnnestu</string>
@ -25,7 +25,7 @@
<!-- vCard --> <!-- vCard -->
<plurals name="and_other_contacts"> <plurals name="and_other_contacts">
<item quantity="one">ja %d muud</item> <item quantity="one">ja %d muud</item>
<item quantity="other">ja %d teised</item> <item quantity="other">ja %d muud</item>
</plurals> </plurals>
<!-- New conversation --> <!-- New conversation -->
<string name="new_conversation">Uus vestlus</string> <string name="new_conversation">Uus vestlus</string>
@ -48,10 +48,10 @@
<string name="send_now">Saada kohe</string> <string name="send_now">Saada kohe</string>
<!-- Message details --> <!-- Message details -->
<string name="message_details">Message details</string> <string name="message_details">Message details</string>
<string name="message_details_sender">Sender</string> <string name="message_details_sender">Saatja</string>
<string name="message_details_receiver">Receiver</string> <string name="message_details_receiver">Saaja</string>
<string name="message_details_sent_at">Sent at</string> <string name="message_details_sent_at">Saatmise aeg</string>
<string name="message_details_received_at">Received at</string> <string name="message_details_received_at">Saabumise aeg</string>
<!-- Notifications --> <!-- Notifications -->
<string name="channel_received_sms">Vastuvõetud SMS</string> <string name="channel_received_sms">Vastuvõetud SMS</string>
<string name="new_message">Uus sõnum</string> <string name="new_message">Uus sõnum</string>
@ -59,14 +59,14 @@
<string name="mark_as_unread">Märgi mitteloetuks</string> <string name="mark_as_unread">Märgi mitteloetuks</string>
<string name="me">Mina</string> <string name="me">Mina</string>
<!-- Archive --> <!-- Archive -->
<string name="unarchive">Unarchive</string> <string name="unarchive">Eemalda arhiivist</string>
<string name="empty_archive">Delete all archived conversations</string> <string name="empty_archive">Kustuta kõik arhiveeritud vestlused</string>
<string name="archived_conversations">Archive</string> <string name="archived_conversations">Vestluste arhiiv</string>
<string name="show_archived_conversations">Show archived conversations</string> <string name="show_archived_conversations">Näita arhiveeritud vestlusi</string>
<string name="archive">Archive</string> <string name="archive">Arhiiv</string>
<string name="no_archived_conversations">No archived conversations have been found</string> <string name="no_archived_conversations">Arhiveeritud vestlusi ei leidu</string>
<string name="archive_emptied_successfully">The archive has been emptied successfully</string> <string name="archive_emptied_successfully">Arhiiv on edukalt tühjendatud</string>
<string name="empty_archive_confirmation">Are you sure you want to empty the archive? All archived conversations will be permanently lost.</string> <string name="empty_archive_confirmation">Kas kindlasti soovid arhiivi tühjendada\? Kõik arhiveeritud vestlused lähevad jäädavalt kaotsi.</string>
<!-- Recycle bin --> <!-- Recycle bin -->
<string name="restore">Restore</string> <string name="restore">Restore</string>
<string name="restore_all_messages">Restore all messages</string> <string name="restore_all_messages">Restore all messages</string>
@ -74,7 +74,7 @@
<string name="skip_the_recycle_bin_messages">Skip the Recycle Bin, delete messages directly</string> <string name="skip_the_recycle_bin_messages">Skip the Recycle Bin, delete messages directly</string>
<!-- Confirmation dialog --> <!-- Confirmation dialog -->
<string name="delete_whole_conversation_confirmation">Kas oled kindel, et soovid kustutada kõik selle vestluse sõnumid\?</string> <string name="delete_whole_conversation_confirmation">Kas oled kindel, et soovid kustutada kõik selle vestluse sõnumid\?</string>
<string name="archive_confirmation">Are you sure you want to archive %s?</string> <string name="archive_confirmation">Kas sa oled kindel, et soovid lisada „%s“ arhiivi\?</string>
<string name="restore_whole_conversation_confirmation">Are you sure you want to restore all messages of this conversation?</string> <string name="restore_whole_conversation_confirmation">Are you sure you want to restore all messages of this conversation?</string>
<string name="restore_confirmation">Are you sure you want to restore %s?</string> <string name="restore_confirmation">Are you sure you want to restore %s?</string>
<!-- Are you sure you want to delete 5 conversations? --> <!-- Are you sure you want to delete 5 conversations? -->
@ -85,14 +85,14 @@
<!-- Are you sure you want to delete 5 messages? --> <!-- Are you sure you want to delete 5 messages? -->
<plurals name="delete_messages"> <plurals name="delete_messages">
<item quantity="one">%d sõnum</item> <item quantity="one">%d sõnum</item>
<item quantity="other">%d sõnumeid</item> <item quantity="other">%d sõnumit</item>
</plurals> </plurals>
<!-- Settings --> <!-- Settings -->
<string name="keyword">Keyword</string> <string name="keyword">Märksõna</string>
<string name="blocked_keywords">Blocked keywords</string> <string name="blocked_keywords">Blokeeritud märksõnad</string>
<string name="manage_blocked_keywords">Manage blocked keywords</string> <string name="manage_blocked_keywords">Halda blokeeritud märksõnu</string>
<string name="not_blocking_keywords">You are not blocking any keywords. You may add keywords here to block all messages containing them.</string> <string name="not_blocking_keywords">Sa hetkel ei blokeeri ühtegi märksõna. Võid siia lisada märksõnu, et blokeerida kõik neid sisaldavad sõnumid.</string>
<string name="add_a_blocked_keyword">Add a blocked keyword</string> <string name="add_a_blocked_keyword">Lisa blokeeritud märksõna</string>
<string name="lock_screen_visibility">Teavituse nähtavus lukustusvaates</string> <string name="lock_screen_visibility">Teavituse nähtavus lukustusvaates</string>
<string name="sender_and_message">Saatja ja sõnum</string> <string name="sender_and_message">Saatja ja sõnum</string>
<string name="sender_only">Ainult saatja</string> <string name="sender_only">Ainult saatja</string>
@ -103,15 +103,15 @@
<string name="mms_file_size_limit_none">Piirangut ei ole</string> <string name="mms_file_size_limit_none">Piirangut ei ole</string>
<string name="outgoing_messages">Väljuvad sõnumid</string> <string name="outgoing_messages">Väljuvad sõnumid</string>
<string name="group_message_mms">Saada rühmasõnumid MMS-sõnumitena</string> <string name="group_message_mms">Saada rühmasõnumid MMS-sõnumitena</string>
<string name="send_long_message_mms">Pikkade sõnumite saatmine MMS-ina</string> <string name="send_long_message_mms">Saada pikad sõnumid MMS-ina</string>
<!-- Export / Import --> <!-- Export / Import -->
<string name="messages">Sõnumid</string> <string name="messages">Sõnumid</string>
<string name="export_messages">Sõnumite eksportimine</string> <string name="export_messages">Ekspordi sõnumid</string>
<string name="export_sms">Ekspordi SMS</string> <string name="export_sms">Ekspordi tekstisõnumid</string>
<string name="export_mms">Ekspordi MMS</string> <string name="export_mms">Ekspordi MMS-sõnumid</string>
<string name="import_messages">Sõnumite importimine</string> <string name="import_messages">Impordi sõnumid</string>
<string name="import_sms">Impordi SMS</string> <string name="import_sms">Impordi tekstisõnumid</string>
<string name="import_mms">Impordi MMS</string> <string name="import_mms">Impordi MMS-sõnumid</string>
<string name="no_option_selected">Palun vali vähemalt üks kirje</string> <string name="no_option_selected">Palun vali vähemalt üks kirje</string>
<!-- Errors --> <!-- Errors -->
<string name="empty_destination_address">Kui number on sisestamata, siis sõnumit saata ei saa</string> <string name="empty_destination_address">Kui number on sisestamata, siis sõnumit saata ei saa</string>

View File

@ -59,14 +59,14 @@
<string name="mark_as_unread">Als ongelezen markeren</string> <string name="mark_as_unread">Als ongelezen markeren</string>
<string name="me">Ik</string> <string name="me">Ik</string>
<!-- Archive --> <!-- Archive -->
<string name="unarchive">Unarchive</string> <string name="unarchive">Herstellen</string>
<string name="empty_archive">Delete all archived conversations</string> <string name="empty_archive">Alle gearchiveerde gesprekken verwijderen</string>
<string name="archived_conversations">Archive</string> <string name="archived_conversations">Archief</string>
<string name="show_archived_conversations">Show archived conversations</string> <string name="show_archived_conversations">Gearchiveerde gesprekken tonen</string>
<string name="archive">Archive</string> <string name="archive">Archiveren</string>
<string name="no_archived_conversations">No archived conversations have been found</string> <string name="no_archived_conversations">Geen gearchiveerde gesprekken gevonden</string>
<string name="archive_emptied_successfully">The archive has been emptied successfully</string> <string name="archive_emptied_successfully">Archief is gewist</string>
<string name="empty_archive_confirmation">Are you sure you want to empty the archive? All archived conversations will be permanently lost.</string> <string name="empty_archive_confirmation">Het archief wissen\? Alle gearchiveerde gesprekken zullen permanent verwijderd worden.</string>
<!-- Recycle bin --> <!-- Recycle bin -->
<string name="restore">Restore</string> <string name="restore">Restore</string>
<string name="restore_all_messages">Restore all messages</string> <string name="restore_all_messages">Restore all messages</string>
@ -74,7 +74,7 @@
<string name="skip_the_recycle_bin_messages">Skip the Recycle Bin, delete messages directly</string> <string name="skip_the_recycle_bin_messages">Skip the Recycle Bin, delete messages directly</string>
<!-- Confirmation dialog --> <!-- Confirmation dialog -->
<string name="delete_whole_conversation_confirmation">Alle berichten in dit gesprek verwijderen\?</string> <string name="delete_whole_conversation_confirmation">Alle berichten in dit gesprek verwijderen\?</string>
<string name="archive_confirmation">Are you sure you want to archive %s?</string> <string name="archive_confirmation">%s archiveren\?</string>
<string name="restore_whole_conversation_confirmation">Are you sure you want to restore all messages of this conversation?</string> <string name="restore_whole_conversation_confirmation">Are you sure you want to restore all messages of this conversation?</string>
<string name="restore_confirmation">Are you sure you want to restore %s?</string> <string name="restore_confirmation">Are you sure you want to restore %s?</string>
<!-- Are you sure you want to delete 5 conversations? --> <!-- Are you sure you want to delete 5 conversations? -->

View File

@ -61,14 +61,14 @@
<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>
<!-- Archive --> <!-- Archive -->
<string name="unarchive">Unarchive</string> <string name="unarchive">Cofnij archiwizację</string>
<string name="empty_archive">Delete all archived conversations</string> <string name="empty_archive">Usuń wszystkie zarchiwizowane rozmowy</string>
<string name="archived_conversations">Archive</string> <string name="archived_conversations">Archiwum</string>
<string name="show_archived_conversations">Show archived conversations</string> <string name="show_archived_conversations">Pokaż zarchiwizowane rozmowy</string>
<string name="archive">Archive</string> <string name="archive">Zarchiwizuj</string>
<string name="no_archived_conversations">No archived conversations have been found</string> <string name="no_archived_conversations">Nie znaleziono zarchiwizowanych rozmów</string>
<string name="archive_emptied_successfully">The archive has been emptied successfully</string> <string name="archive_emptied_successfully">Archiwum zostało opróżnione</string>
<string name="empty_archive_confirmation">Are you sure you want to empty the archive? All archived conversations will be permanently lost.</string> <string name="empty_archive_confirmation">Czy opróżnić archiwum\? Wszystkie zarchiwizowane rozmowy zostaną utracone bezpowrotnie.</string>
<!-- Recycle bin --> <!-- Recycle bin -->
<string name="restore">Restore</string> <string name="restore">Restore</string>
<string name="restore_all_messages">Restore all messages</string> <string name="restore_all_messages">Restore all messages</string>
@ -76,7 +76,7 @@
<string name="skip_the_recycle_bin_messages">Skip the Recycle Bin, delete messages directly</string> <string name="skip_the_recycle_bin_messages">Skip the Recycle Bin, delete messages directly</string>
<!-- Confirmation dialog --> <!-- Confirmation dialog -->
<string name="delete_whole_conversation_confirmation">Czy usunąć wszystkie wiadomości z tej rozmowy\?</string> <string name="delete_whole_conversation_confirmation">Czy usunąć wszystkie wiadomości z tej rozmowy\?</string>
<string name="archive_confirmation">Are you sure you want to archive %s?</string> <string name="archive_confirmation">Czy zarchiwizować %s\?</string>
<string name="restore_whole_conversation_confirmation">Are you sure you want to restore all messages of this conversation?</string> <string name="restore_whole_conversation_confirmation">Are you sure you want to restore all messages of this conversation?</string>
<string name="restore_confirmation">Are you sure you want to restore %s?</string> <string name="restore_confirmation">Are you sure you want to restore %s?</string>
<!-- Are you sure you want to delete 5 conversations? --> <!-- Are you sure you want to delete 5 conversations? -->

View File

@ -61,11 +61,11 @@
<string name="mark_as_unread">Не прочитано</string> <string name="mark_as_unread">Не прочитано</string>
<string name="me">Я</string> <string name="me">Я</string>
<!-- Archive --> <!-- Archive -->
<string name="unarchive">Unarchive</string> <string name="unarchive">Разархивировать</string>
<string name="empty_archive">Delete all archived conversations</string> <string name="empty_archive">Delete all archived conversations</string>
<string name="archived_conversations">Archive</string> <string name="archived_conversations">Archive</string>
<string name="show_archived_conversations">Show archived conversations</string> <string name="show_archived_conversations">Show archived conversations</string>
<string name="archive">Archive</string> <string name="archive">Архивировать</string>
<string name="no_archived_conversations">No archived conversations have been found</string> <string name="no_archived_conversations">No archived conversations have been found</string>
<string name="archive_emptied_successfully">The archive has been emptied successfully</string> <string name="archive_emptied_successfully">The archive has been emptied successfully</string>
<string name="empty_archive_confirmation">Are you sure you want to empty the archive? All archived conversations will be permanently lost.</string> <string name="empty_archive_confirmation">Are you sure you want to empty the archive? All archived conversations will be permanently lost.</string>
@ -141,4 +141,4 @@
Haven't found some strings? There's more at Haven't found some strings? There's more at
https://github.com/SimpleMobileTools/Simple-Commons/tree/master/commons/src/main/res https://github.com/SimpleMobileTools/Simple-Commons/tree/master/commons/src/main/res
--> -->
</resources> </resources>

View File

@ -59,14 +59,14 @@
<string name="mark_as_unread">Okunmadı olarak işaretle</string> <string name="mark_as_unread">Okunmadı olarak işaretle</string>
<string name="me">Ben</string> <string name="me">Ben</string>
<!-- Archive --> <!-- Archive -->
<string name="unarchive">Unarchive</string> <string name="unarchive">Arşivden çıkar</string>
<string name="empty_archive">Delete all archived conversations</string> <string name="empty_archive">Arşivlenen tüm görüşmeleri sil</string>
<string name="archived_conversations">Archive</string> <string name="archived_conversations">Arşiv</string>
<string name="show_archived_conversations">Show archived conversations</string> <string name="show_archived_conversations">Arşivlenen görüşmeleri göster</string>
<string name="archive">Archive</string> <string name="archive">Arşiv</string>
<string name="no_archived_conversations">No archived conversations have been found</string> <string name="no_archived_conversations">Arşivlenen görüşme bulunamadı</string>
<string name="archive_emptied_successfully">The archive has been emptied successfully</string> <string name="archive_emptied_successfully">Arşiv başarıyla boşaltıldı</string>
<string name="empty_archive_confirmation">Are you sure you want to empty the archive? All archived conversations will be permanently lost.</string> <string name="empty_archive_confirmation">Arşivi boşaltmak istediğinizden emin misiniz\? Arşivlenen tüm görüşmeler kalıcı olarak kaybolacak.</string>
<!-- Recycle bin --> <!-- Recycle bin -->
<string name="restore">Restore</string> <string name="restore">Restore</string>
<string name="restore_all_messages">Restore all messages</string> <string name="restore_all_messages">Restore all messages</string>
@ -74,7 +74,7 @@
<string name="skip_the_recycle_bin_messages">Skip the Recycle Bin, delete messages directly</string> <string name="skip_the_recycle_bin_messages">Skip the Recycle Bin, delete messages directly</string>
<!-- Confirmation dialog --> <!-- Confirmation dialog -->
<string name="delete_whole_conversation_confirmation">Bu görüşmenin tüm mesajlarını silmek istediğinizden emin misiniz\?</string> <string name="delete_whole_conversation_confirmation">Bu görüşmenin tüm mesajlarını silmek istediğinizden emin misiniz\?</string>
<string name="archive_confirmation">Are you sure you want to archive %s?</string> <string name="archive_confirmation">%s arşivlemek istediğinizden emin misiniz\?</string>
<string name="restore_whole_conversation_confirmation">Are you sure you want to restore all messages of this conversation?</string> <string name="restore_whole_conversation_confirmation">Are you sure you want to restore all messages of this conversation?</string>
<string name="restore_confirmation">Are you sure you want to restore %s?</string> <string name="restore_confirmation">Are you sure you want to restore %s?</string>
<!-- Are you sure you want to delete 5 conversations? --> <!-- Are you sure you want to delete 5 conversations? -->

View File

@ -58,14 +58,14 @@
<string name="mark_as_unread">标记为未读</string> <string name="mark_as_unread">标记为未读</string>
<string name="me">自己</string> <string name="me">自己</string>
<!-- Archive --> <!-- Archive -->
<string name="unarchive">Unarchive</string> <string name="unarchive">解压缩</string>
<string name="empty_archive">Delete all archived conversations</string> <string name="empty_archive">删除所有已归档对话</string>
<string name="archived_conversations">Archive</string> <string name="archived_conversations">归档</string>
<string name="show_archived_conversations">Show archived conversations</string> <string name="show_archived_conversations">显示已归档对话</string>
<string name="archive">Archive</string> <string name="archive">归档</string>
<string name="no_archived_conversations">No archived conversations have been found</string> <string name="no_archived_conversations">尚未找到已归档对话</string>
<string name="archive_emptied_successfully">The archive has been emptied successfully</string> <string name="archive_emptied_successfully">已成功清空归档</string>
<string name="empty_archive_confirmation">Are you sure you want to empty the archive? All archived conversations will be permanently lost.</string> <string name="empty_archive_confirmation">你确定要清空归档吗?所有已归档对话将永久丢失。</string>
<!-- Recycle bin --> <!-- Recycle bin -->
<string name="restore">Restore</string> <string name="restore">Restore</string>
<string name="restore_all_messages">Restore all messages</string> <string name="restore_all_messages">Restore all messages</string>
@ -73,7 +73,7 @@
<string name="skip_the_recycle_bin_messages">Skip the Recycle Bin, delete messages directly</string> <string name="skip_the_recycle_bin_messages">Skip the Recycle Bin, delete messages directly</string>
<!-- Confirmation dialog --> <!-- Confirmation dialog -->
<string name="delete_whole_conversation_confirmation">您确定要删除此对话的所有消息吗\?</string> <string name="delete_whole_conversation_confirmation">您确定要删除此对话的所有消息吗\?</string>
<string name="archive_confirmation">Are you sure you want to archive %s?</string> <string name="archive_confirmation">你确定要归档 %s 吗?</string>
<string name="restore_whole_conversation_confirmation">Are you sure you want to restore all messages of this conversation?</string> <string name="restore_whole_conversation_confirmation">Are you sure you want to restore all messages of this conversation?</string>
<string name="restore_confirmation">Are you sure you want to restore %s?</string> <string name="restore_confirmation">Are you sure you want to restore %s?</string>
<!-- Are you sure you want to delete 5 conversations? --> <!-- Are you sure you want to delete 5 conversations? -->

View File

@ -1,10 +1,10 @@
Eine großartige Möglichkeit, mit Ihren Verwandten in Kontakt zu bleiben, indem Sie sowohl SMS- als auch MMS-Nachrichten senden. Die App verarbeitet auch Gruppen-Messaging ordnungsgemäß, genau wie das Blockieren von Nummern von Android 7+. Bleiben Sie mit allen Ihren Kontakten über die Messaging-App auf Ihrem Telefon in Kontakt. Es war noch nie einfacher, Fotos zu teilen, Emojis zu senden oder einfach nur schnell Hallo zu sagen. Mit Ihren Nachrichten können Sie so viel tun, z. B. Konversationen stummschalten oder bestimmten Kontakten spezielle Nachrichtentöne zuweisen. Mit dieser SMS- und Gruppen-Messaging-App können Sie die täglichen privaten Nachrichten und Gruppen-Nachrichten auf unterhaltsame Weise genießen. Eine großartige Möglichkeit, mit Ihren Verwandten in Kontakt zu bleiben, indem Sie sowohl SMS- als auch MMS-Nachrichten senden. Die App verarbeitet auch Gruppen-Messaging ordnungsgemäß, genau wie das Blockieren von Nummern von Android 7+. Bleiben Sie mit allen Ihren Kontakten über die Messaging-App auf Ihrem Telefon in Kontakt. Es war noch nie einfacher, Fotos zu teilen, Emojis zu senden oder einfach nur schnell Hallo zu sagen. Mit Ihren Nachrichten können Sie so viel tun, z. B. Unterhaltungen stummschalten oder bestimmten Kontakten spezielle Nachrichtentöne zuweisen. Mit dieser SMS- und Gruppen-Messaging-App können Sie die täglichen privaten Nachrichten und Gruppen-Nachrichten auf unterhaltsame Weise genießen.
Es bietet viele Datumsformate zur Auswahl, damit Sie sich bei der Verwendung wohl fühlen. Sie können auch zwischen dem 12- und 24-Stunden-Zeitformat wechseln. Diese App bietet Ihnen auch die Flexibilität der SMS-Sicherung. Auf diese Weise müssen Sie die Nachrichten nicht auf einem externen Gerät speichern oder andere Hardware zum Speichern verwenden. Mit dieser SMS-Sicherungsfunktion können Sie Textnachrichten und MMS-Daten effizient speichern, ohne den internen Speicher zu belasten. Es bietet viele Datumsformate zur Auswahl, damit Sie sich bei der Verwendung wohl fühlen. Sie können auch zwischen dem 12- und 24-Stunden-Zeitformat wechseln. Diese App bietet Ihnen auch die Flexibilität der SMS-Sicherung. Auf diese Weise müssen Sie die Nachrichten nicht auf einem externen Gerät speichern oder andere Hardware zum Speichern verwenden. Mit dieser SMS-Sicherungsfunktion können Sie Textnachrichten und MMS-Daten effizient speichern, ohne den internen Speicher zu belasten.
Diese Messaging-App hat im Vergleich zur Konkurrenz eine wirklich winzige App-Größe, wodurch sie wirklich schnell heruntergeladen werden kann. Die SMS-Sicherungstechnik ist hilfreich, wenn Sie Ihr Gerät wechseln müssen oder es gestohlen wird. Auf diese Weise können Sie die Textnachricht sowohl aus Gruppennachrichten als auch aus privaten Nachrichten abrufen, indem Sie die SMS-Sicherung in dieser Messaging-App verwenden. Diese Messaging-App hat im Vergleich zur Konkurrenz eine wirklich winzige App-Größe, wodurch sie wirklich schnell heruntergeladen werden kann. Die SMS-Sicherungstechnik ist hilfreich, wenn Sie Ihr Gerät wechseln müssen oder es gestohlen wird. Auf diese Weise können Sie die Textnachricht sowohl aus Gruppennachrichten als auch aus privaten Nachrichten abrufen, indem Sie die SMS-Sicherung in dieser Messaging-App verwenden.
Die Blockierungsfunktion hilft, unerwünschte Nachrichten leicht zu verhindern, Sie können auch alle Nachrichten von nicht gespeicherten Kontakten blockieren. Blockierte Nummern können zur einfachen Sicherung sowohl exportiert als auch importiert werden. Alle Konversationen können einfach in eine Datei exportiert werden, um auch ein einfaches Backup zu erstellen oder zwischen Geräten zu migrieren. Die Blockierungsfunktion hilft, unerwünschte Nachrichten leicht zu verhindern, Sie können auch alle Nachrichten von nicht gespeicherten Kontakten blockieren. Blockierte Nummern können zur einfachen Sicherung sowohl exportiert als auch importiert werden. Alle Unterhaltungen können einfach in eine Datei exportiert werden, um auch ein einfaches Backup zu erstellen oder zwischen Geräten zu migrieren.
Sie können auch anpassen, welcher Teil der Nachricht auf dem Sperrbildschirm sichtbar ist. Sie können wählen, ob nur der Absender angezeigt werden soll, die Nachricht oder nichts für mehr Privatsphäre. Sie können auch anpassen, welcher Teil der Nachricht auf dem Sperrbildschirm sichtbar ist. Sie können wählen, ob nur der Absender angezeigt werden soll, die Nachricht oder nichts für mehr Privatsphäre.

View File

@ -1 +1 @@
Lihtne SMS Sõnumitooja Lihtne SMS sõnumiklient

View File

@ -0,0 +1,27 @@
這是一個與親人保持聯繫的好方法,可以發送簡訊和多媒體簡訊。這個應用程式還能正確處理群組訊息,就像 Android 7+ 的阻擋號碼功能一樣。使用手機上的簡訊應用程式與所有聯絡人保持聯繫,分享照片、發送表情符號或者簡單問候從未如此輕鬆。您可以進行很多有趣的簡訊操作,例如靜音對話、為特定聯絡人設定特殊簡訊提示音。這個簡訊和群組訊息應用程式讓您以更有趣的方式享受每天的私人訊息和群組訊息。
它提供多種日期格式供您選擇讓您在使用時感到舒適。您還可以在12小時和24小時制之間切換。這個應用程式還提供簡訊備份的靈活性這樣您就不需要將訊息保存在任何外部設備上也不需要使用其他硬體來進行保存。這個簡訊備份功能將幫助您高效地保存簡訊和多媒體訊息的數據而不會給內部存儲帶來負擔。
與競爭相比,這個訊息應用程式的大小非常小,下載速度非常快。當您需要更換設備或遭到竊取時,簡訊備份技術非常有用。這樣,您可以使用這個訊息應用程式中的簡訊備份輕鬆檢索群組訊息和私人訊息。
阻擋功能能夠輕鬆防止不需要的訊息,您還可以阻擋所有非存儲聯絡人的訊息。被阻擋的號碼可以導出和導入,以便輕鬆備份。所有對話都可以輕鬆導出到文件進行簡單備份,或在設備之間遷移。
您還可以自定義訊息在鎖定畫面上的顯示部分。您可以選擇只顯示發送者、訊息內容,或者為增強隱私而不顯示任何內容。
這個訊息應用程式還提供用戶快速高效地搜索訊息的能力。不再需要滾動所有私人訊息和群組訊息對話才能找到所需的訊息。只需使用這個簡訊應用程式進行搜索,即可輕鬆獲得所需的訊息。
它具有原生設計和暗色主題,提供出色的用戶體驗,使用起來輕鬆自如。相比其他應用程式,缺乏網路訪問可以為您提供更多隱私、安全性和穩定性。
不含廣告或不必要的權限。它是完全開源的,提供可自定義的顏色。
請查看完整的 Simple Tools 套件:
https://www.simplemobiletools.com
Facebook
https://www.facebook.com/simplemobiletools
Reddit
https://www.reddit.com/r/SimpleMobileTools
Telegram
https://t.me/SimpleMobileTools

View File

@ -0,0 +1 @@
Android 上的簡訊 (SMS) 和多媒體簡訊 (MMS) 應用程式,能快速發送訊息,介面美觀

View File

@ -0,0 +1 @@
簡易簡訊