FEATURE: Import and export blocked keywords

This commit is contained in:
ronniedroid 2024-01-30 15:42:42 +03:00
parent 0058d6c6f9
commit 958644b290
9 changed files with 365 additions and 5 deletions

View file

@ -1,21 +1,36 @@
package org.fossify.messages.activities package org.fossify.messages.activities
import android.app.Activity
import android.content.ActivityNotFoundException
import android.content.Intent
import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.widget.Toast
import org.fossify.commons.activities.BaseSimpleActivity import org.fossify.commons.activities.BaseSimpleActivity
import org.fossify.commons.dialogs.ExportBlockedNumbersDialog
import org.fossify.commons.dialogs.FilePickerDialog
import org.fossify.commons.extensions.* import org.fossify.commons.extensions.*
import org.fossify.commons.helpers.APP_ICON_IDS import org.fossify.commons.helpers.*
import org.fossify.commons.helpers.APP_LAUNCHER_NAME
import org.fossify.commons.helpers.NavigationIcon
import org.fossify.commons.helpers.ensureBackgroundThread
import org.fossify.commons.interfaces.RefreshRecyclerViewListener import org.fossify.commons.interfaces.RefreshRecyclerViewListener
import org.fossify.messages.R import org.fossify.messages.R
import org.fossify.messages.databinding.ActivityManageBlockedKeywordsBinding import org.fossify.messages.databinding.ActivityManageBlockedKeywordsBinding
import org.fossify.messages.dialogs.AddBlockedKeywordDialog import org.fossify.messages.dialogs.AddBlockedKeywordDialog
import org.fossify.messages.dialogs.ExportBlockedKeywordsDialog
import org.fossify.messages.dialogs.ManageBlockedKeywordsAdapter import org.fossify.messages.dialogs.ManageBlockedKeywordsAdapter
import org.fossify.messages.extensions.config import org.fossify.messages.extensions.config
import org.fossify.messages.extensions.toArrayList import org.fossify.messages.extensions.toArrayList
import org.fossify.messages.helpers.BlockedKeywordsExporter
import org.fossify.messages.helpers.BlockedKeywordsImporter
import java.io.FileOutputStream
import java.io.OutputStream
class ManageBlockedKeywordsActivity : BaseSimpleActivity(), RefreshRecyclerViewListener { class ManageBlockedKeywordsActivity : BaseSimpleActivity(), RefreshRecyclerViewListener {
private companion object {
private const val PICK_IMPORT_SOURCE_INTENT = 11
private const val PICK_EXPORT_FILE_INTENT = 21
}
override fun getAppIconIDs() = intent.getIntegerArrayListExtra(APP_ICON_IDS) ?: ArrayList() override fun getAppIconIDs() = intent.getIntegerArrayListExtra(APP_ICON_IDS) ?: ArrayList()
override fun getAppLauncherName() = intent.getStringExtra(APP_LAUNCHER_NAME) ?: "" override fun getAppLauncherName() = intent.getStringExtra(APP_LAUNCHER_NAME) ?: ""
@ -60,11 +75,150 @@ class ManageBlockedKeywordsActivity : BaseSimpleActivity(), RefreshRecyclerViewL
true true
} }
R.id.export_blocked_keywords -> {
tryExportBlockedNumbers()
true
}
R.id.import_blocked_keywords -> {
tryImportBlockedKeywords()
true
}
else -> false else -> false
} }
} }
} }
override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) {
super.onActivityResult(requestCode, resultCode, resultData)
when {
requestCode == PICK_IMPORT_SOURCE_INTENT && resultCode == Activity.RESULT_OK && resultData != null && resultData.data != null -> {
tryImportBlockedKeywordsFromFile(resultData.data!!)
}
requestCode == PICK_EXPORT_FILE_INTENT && resultCode == Activity.RESULT_OK && resultData != null && resultData.data != null -> {
val outputStream = contentResolver.openOutputStream(resultData.data!!)
exportBlockedKeywordsTo(outputStream)
}
}
}
private fun tryImportBlockedKeywords() {
if (isQPlus()) {
Intent(Intent.ACTION_GET_CONTENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "text/plain"
try {
startActivityForResult(this, PICK_IMPORT_SOURCE_INTENT)
} catch (e: ActivityNotFoundException) {
toast(org.fossify.commons.R.string.system_service_disabled, Toast.LENGTH_LONG)
} catch (e: Exception) {
showErrorToast(e)
}
}
} else {
handlePermission(PERMISSION_READ_STORAGE) { isAllowed ->
if (isAllowed) {
pickFileToImportBlockedKeywords()
}
}
}
}
private fun pickFileToImportBlockedKeywords() {
FilePickerDialog(this) {
importBlockedKeywords(it)
}
}
private fun tryImportBlockedKeywordsFromFile(uri: Uri) {
when (uri.scheme) {
"file" -> importBlockedKeywords(uri.path!!)
"content" -> {
val tempFile = getTempFile("blocked", "blocked_keywords.txt")
if (tempFile == null) {
toast(org.fossify.commons.R.string.unknown_error_occurred)
return
}
try {
val inputStream = contentResolver.openInputStream(uri)
val out = FileOutputStream(tempFile)
inputStream!!.copyTo(out)
importBlockedKeywords(tempFile.absolutePath)
} catch (e: Exception) {
showErrorToast(e)
}
}
else -> toast(org.fossify.commons.R.string.invalid_file_format)
}
}
private fun importBlockedKeywords(path: String) {
ensureBackgroundThread {
val result = BlockedKeywordsImporter(this).importBlockedKeywords(path)
toast(
when (result) {
BlockedKeywordsImporter.ImportResult.IMPORT_OK -> org.fossify.commons.R.string.importing_successful
BlockedKeywordsImporter.ImportResult.IMPORT_FAIL -> org.fossify.commons.R.string.no_items_found
}
)
updateBlockedKeywords()
}
}
private fun exportBlockedKeywordsTo(outputStream: OutputStream?) {
ensureBackgroundThread {
val blockedKeywords = config.blockedKeywords.toArrayList()
if (blockedKeywords.isEmpty()) {
toast(org.fossify.commons.R.string.no_entries_for_exporting)
} else {
BlockedKeywordsExporter.exportBlockedKeywords(blockedKeywords, outputStream) {
toast(
when (it) {
ExportResult.EXPORT_OK -> org.fossify.commons.R.string.exporting_successful
else -> org.fossify.commons.R.string.exporting_failed
}
)
}
}
}
}
private fun tryExportBlockedNumbers() {
if (isQPlus()) {
ExportBlockedKeywordsDialog(this, config.lastBlockedKeywordExportPath, true) { file ->
Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
type = "text/plain"
putExtra(Intent.EXTRA_TITLE, file.name)
addCategory(Intent.CATEGORY_OPENABLE)
try {
startActivityForResult(this, PICK_EXPORT_FILE_INTENT)
} catch (e: ActivityNotFoundException) {
toast(org.fossify.commons.R.string.system_service_disabled, Toast.LENGTH_LONG)
} catch (e: Exception) {
showErrorToast(e)
}
}
}
} else {
handlePermission(PERMISSION_WRITE_STORAGE) { isAllowed ->
if (isAllowed) {
ExportBlockedNumbersDialog(this, config.lastBlockedKeywordExportPath, false) { file ->
getFileOutputStream(file.toFileDirItem(this), true) { out ->
exportBlockedKeywordsTo(out)
}
}
}
}
}
}
override fun refreshItems() { override fun refreshItems() {
updateBlockedKeywords() updateBlockedKeywords()
} }
@ -85,7 +239,7 @@ class ManageBlockedKeywordsActivity : BaseSimpleActivity(), RefreshRecyclerViewL
} }
} }
private fun addOrEditBlockedKeyword(keyword: String? = null) { fun addOrEditBlockedKeyword(keyword: String? = null) {
AddBlockedKeywordDialog(this, keyword) { AddBlockedKeywordDialog(this, keyword) {
updateBlockedKeywords() updateBlockedKeywords()
} }

View file

@ -0,0 +1,71 @@
package org.fossify.messages.dialogs
import androidx.appcompat.app.AlertDialog
import org.fossify.commons.activities.BaseSimpleActivity
import org.fossify.commons.dialogs.FilePickerDialog
import org.fossify.commons.extensions.*
import org.fossify.commons.helpers.ensureBackgroundThread
import org.fossify.messages.R
import org.fossify.messages.databinding.DialogExportBlockedKeywordsBinding
import org.fossify.messages.extensions.config
import org.fossify.messages.helpers.BLOCKED_KEYWORDS_EXPORT_EXTENSION
import java.io.File
class ExportBlockedKeywordsDialog(
val activity: BaseSimpleActivity,
val path: String,
val hidePath: Boolean,
callback: (file: File) -> Unit,
) {
private var realPath = path.ifEmpty { activity.internalStoragePath }
private val config = activity.config
init {
val view = DialogExportBlockedKeywordsBinding.inflate(activity.layoutInflater, null, false).apply {
exportBlockedKeywordsFolder.text = activity.humanizePath(realPath)
exportBlockedKeywordsFilename.setText("${activity.getString(R.string.blocked_keywords)}_${activity.getCurrentFormattedDateTime()}")
if (hidePath) {
exportBlockedKeywordsFolderLabel.beGone()
exportBlockedKeywordsFolder.beGone()
} else {
exportBlockedKeywordsFolder.setOnClickListener {
FilePickerDialog(activity, realPath, false, showFAB = true) {
exportBlockedKeywordsFolder.text = activity.humanizePath(it)
realPath = it
}
}
}
}
activity.getAlertDialogBuilder()
.setPositiveButton(org.fossify.commons.R.string.ok, null)
.setNegativeButton(org.fossify.commons.R.string.cancel, null)
.apply {
activity.setupDialogStuff(view.root, this, R.string.export_blocked_keywords) { alertDialog ->
alertDialog.showKeyboard(view.exportBlockedKeywordsFilename)
alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener {
val filename = view.exportBlockedKeywordsFilename.value
when {
filename.isEmpty() -> activity.toast(org.fossify.commons.R.string.empty_name)
filename.isAValidFilename() -> {
val file = File(realPath, "$filename$BLOCKED_KEYWORDS_EXPORT_EXTENSION")
if (!hidePath && file.exists()) {
activity.toast(org.fossify.commons.R.string.name_taken)
return@setOnClickListener
}
ensureBackgroundThread {
config.lastBlockedKeywordExportPath = file.absolutePath.getParentPath()
callback(file)
alertDialog.dismiss()
}
}
else -> activity.toast(org.fossify.commons.R.string.invalid_name)
}
}
}
}
}
}

View file

@ -0,0 +1,29 @@
package org.fossify.messages.helpers
import org.fossify.commons.helpers.ExportResult
import java.io.OutputStream
object BlockedKeywordsExporter {
fun exportBlockedKeywords(
blockedKeywords: ArrayList<String>,
outputStream: OutputStream?,
callback: (result: ExportResult) -> Unit,
) {
if (outputStream == null) {
callback.invoke(ExportResult.EXPORT_FAIL)
return
}
try {
outputStream.bufferedWriter().use { out ->
out.write(blockedKeywords.joinToString(BLOCKED_KEYWORDS_EXPORT_DELIMITER) {
it
})
}
callback.invoke(ExportResult.EXPORT_OK)
} catch (e: Exception) {
callback.invoke(ExportResult.EXPORT_FAIL)
}
}
}

View file

@ -0,0 +1,37 @@
package org.fossify.messages.helpers
import android.app.Activity
import org.fossify.commons.extensions.showErrorToast
import org.fossify.messages.extensions.config
import java.io.File
class BlockedKeywordsImporter(
private val activity: Activity,
) {
enum class ImportResult {
IMPORT_FAIL, IMPORT_OK
}
fun importBlockedKeywords(path: String): ImportResult {
return try {
val inputStream = File(path).inputStream()
val keywords = inputStream.bufferedReader().use {
val content = it.readText().trimEnd().split(BLOCKED_KEYWORDS_EXPORT_DELIMITER)
content
}
if (keywords.isNotEmpty()) {
keywords.forEach { keyword: String ->
activity.config.addBlockedKeyword(keyword)
}
ImportResult.IMPORT_OK
} else {
ImportResult.IMPORT_FAIL
}
} catch (e: Exception) {
activity.showErrorToast(e)
ImportResult.IMPORT_FAIL
}
}
}

View file

@ -2,6 +2,7 @@ package org.fossify.messages.helpers
import android.content.Context import android.content.Context
import org.fossify.commons.helpers.BaseConfig import org.fossify.commons.helpers.BaseConfig
import org.fossify.commons.helpers.LAST_BLOCKED_NUMBERS_EXPORT_PATH
import org.fossify.messages.extensions.getDefaultKeyboardHeight import org.fossify.messages.extensions.getDefaultKeyboardHeight
import org.fossify.messages.models.Conversation import org.fossify.messages.models.Conversation
@ -127,4 +128,8 @@ class Config(context: Context) : BaseConfig(context) {
fun removeCustomNotificationsByThreadId(threadId: Long) { fun removeCustomNotificationsByThreadId(threadId: Long) {
customNotifications = customNotifications.minus(threadId.toString()) customNotifications = customNotifications.minus(threadId.toString())
} }
var lastBlockedKeywordExportPath: String
get() = prefs.getString(LAST_BLOCKED_KEYWORD_EXPORT_PATH, "")!!
set(lastBlockedNumbersExportPath) = prefs.edit().putString(LAST_BLOCKED_KEYWORD_EXPORT_PATH, lastBlockedNumbersExportPath).apply()
} }

View file

@ -26,6 +26,9 @@ const val SEND_GROUP_MESSAGE_MMS = "send_group_message_mms"
const val MMS_FILE_SIZE_LIMIT = "mms_file_size_limit" const val MMS_FILE_SIZE_LIMIT = "mms_file_size_limit"
const val PINNED_CONVERSATIONS = "pinned_conversations" const val PINNED_CONVERSATIONS = "pinned_conversations"
const val BLOCKED_KEYWORDS = "blocked_keywords" const val BLOCKED_KEYWORDS = "blocked_keywords"
const val BLOCKED_KEYWORDS_EXPORT_DELIMITER = ","
const val BLOCKED_KEYWORDS_EXPORT_EXTENSION = ".txt"
const val LAST_BLOCKED_KEYWORD_EXPORT_PATH = "last_blocked_keyword_export_path"
const val EXPORT_SMS = "export_sms" const val EXPORT_SMS = "export_sms"
const val EXPORT_MMS = "export_mms" const val EXPORT_MMS = "export_mms"
const val JSON_FILE_EXTENSION = ".json" const val JSON_FILE_EXTENSION = ".json"

View file

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/export_blocked_keywords_wrapper"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/export_blocked_keywords_holder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingLeft="@dimen/activity_margin"
android:paddingTop="@dimen/activity_margin"
android:paddingRight="@dimen/activity_margin">
<org.fossify.commons.views.MyTextView
android:id="@+id/export_blocked_keywords_folder_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/small_margin"
android:text="@string/folder"
android:textSize="@dimen/smaller_text_size" />
<org.fossify.commons.views.MyTextView
android:id="@+id/export_blocked_keywords_folder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/small_margin"
android:paddingStart="@dimen/small_margin"
android:paddingTop="@dimen/small_margin"
android:paddingBottom="@dimen/activity_margin" />
<org.fossify.commons.views.MyTextInputLayout
android:id="@+id/export_blocked_keywords_hint"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/filename_without_txt">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/export_blocked_keywords_filename"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/activity_margin"
android:singleLine="true"
android:textCursorDrawable="@null"
android:textSize="@dimen/normal_text_size" />
</org.fossify.commons.views.MyTextInputLayout>
</LinearLayout>
</ScrollView>

View file

@ -6,4 +6,12 @@
android:icon="@drawable/ic_plus_vector" android:icon="@drawable/ic_plus_vector"
android:title="@string/add_a_blocked_keyword" android:title="@string/add_a_blocked_keyword"
app:showAsAction="ifRoom" /> app:showAsAction="ifRoom" />
<item
android:id="@+id/import_blocked_keywords"
android:title="@string/import_blocked_keywords"
app:showAsAction="never" />
<item
android:id="@+id/export_blocked_keywords"
android:title="@string/export_blocked_keywords"
app:showAsAction="never" />
</menu> </menu>

View file

@ -106,6 +106,7 @@
<string name="send_long_message_mms">Send long messages as MMS</string> <string name="send_long_message_mms">Send long messages as MMS</string>
<!-- Export / Import --> <!-- Export / Import -->
<string name="messages">Messages</string> <string name="messages">Messages</string>
<string name="keywords">Keywords</string>
<string name="export_messages">Export messages</string> <string name="export_messages">Export messages</string>
<string name="export_sms">Export SMS</string> <string name="export_sms">Export SMS</string>
<string name="export_mms">Export MMS</string> <string name="export_mms">Export MMS</string>
@ -113,6 +114,8 @@
<string name="import_sms">Import SMS</string> <string name="import_sms">Import SMS</string>
<string name="import_mms">Import MMS</string> <string name="import_mms">Import MMS</string>
<string name="no_option_selected">You have to select at least one item</string> <string name="no_option_selected">You have to select at least one item</string>
<string name="export_blocked_keywords">Export blocked keywords</string>
<string name="import_blocked_keywords">Import blocked keywords</string>
<!-- Errors --> <!-- Errors -->
<string name="empty_destination_address">Can\'t send message to an empty number</string> <string name="empty_destination_address">Can\'t send message to an empty number</string>
<string name="unable_to_save_message">Unable to save message to the telephony database</string> <string name="unable_to_save_message">Unable to save message to the telephony database</string>