diff --git a/app/src/main/kotlin/org/fossify/messages/activities/ManageBlockedKeywordsActivity.kt b/app/src/main/kotlin/org/fossify/messages/activities/ManageBlockedKeywordsActivity.kt index a294acc0..f8270e61 100644 --- a/app/src/main/kotlin/org/fossify/messages/activities/ManageBlockedKeywordsActivity.kt +++ b/app/src/main/kotlin/org/fossify/messages/activities/ManageBlockedKeywordsActivity.kt @@ -1,21 +1,36 @@ 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.widget.Toast 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.helpers.APP_ICON_IDS -import org.fossify.commons.helpers.APP_LAUNCHER_NAME -import org.fossify.commons.helpers.NavigationIcon -import org.fossify.commons.helpers.ensureBackgroundThread +import org.fossify.commons.helpers.* import org.fossify.commons.interfaces.RefreshRecyclerViewListener import org.fossify.messages.R import org.fossify.messages.databinding.ActivityManageBlockedKeywordsBinding import org.fossify.messages.dialogs.AddBlockedKeywordDialog +import org.fossify.messages.dialogs.ExportBlockedKeywordsDialog import org.fossify.messages.dialogs.ManageBlockedKeywordsAdapter import org.fossify.messages.extensions.config 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 { + + 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 getAppLauncherName() = intent.getStringExtra(APP_LAUNCHER_NAME) ?: "" @@ -60,11 +75,150 @@ class ManageBlockedKeywordsActivity : BaseSimpleActivity(), RefreshRecyclerViewL true } + R.id.export_blocked_keywords -> { + tryExportBlockedNumbers() + true + } + + R.id.import_blocked_keywords -> { + tryImportBlockedKeywords() + true + } + 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() { updateBlockedKeywords() } @@ -85,7 +239,7 @@ class ManageBlockedKeywordsActivity : BaseSimpleActivity(), RefreshRecyclerViewL } } - private fun addOrEditBlockedKeyword(keyword: String? = null) { + fun addOrEditBlockedKeyword(keyword: String? = null) { AddBlockedKeywordDialog(this, keyword) { updateBlockedKeywords() } diff --git a/app/src/main/kotlin/org/fossify/messages/dialogs/ExportBlockedKeywordsDialog.kt b/app/src/main/kotlin/org/fossify/messages/dialogs/ExportBlockedKeywordsDialog.kt new file mode 100644 index 00000000..a05e2b94 --- /dev/null +++ b/app/src/main/kotlin/org/fossify/messages/dialogs/ExportBlockedKeywordsDialog.kt @@ -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) + } + } + } + } + } +} diff --git a/app/src/main/kotlin/org/fossify/messages/helpers/BlockedKeywordsExporter.kt b/app/src/main/kotlin/org/fossify/messages/helpers/BlockedKeywordsExporter.kt new file mode 100644 index 00000000..880b43b1 --- /dev/null +++ b/app/src/main/kotlin/org/fossify/messages/helpers/BlockedKeywordsExporter.kt @@ -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, + 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) + } + } +} diff --git a/app/src/main/kotlin/org/fossify/messages/helpers/BlockedKeywordsImporter.kt b/app/src/main/kotlin/org/fossify/messages/helpers/BlockedKeywordsImporter.kt new file mode 100644 index 00000000..d3ddc348 --- /dev/null +++ b/app/src/main/kotlin/org/fossify/messages/helpers/BlockedKeywordsImporter.kt @@ -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 + } + } +} diff --git a/app/src/main/kotlin/org/fossify/messages/helpers/Config.kt b/app/src/main/kotlin/org/fossify/messages/helpers/Config.kt index 8f2dcb48..f636525d 100644 --- a/app/src/main/kotlin/org/fossify/messages/helpers/Config.kt +++ b/app/src/main/kotlin/org/fossify/messages/helpers/Config.kt @@ -2,6 +2,7 @@ package org.fossify.messages.helpers import android.content.Context 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.models.Conversation @@ -127,4 +128,8 @@ class Config(context: Context) : BaseConfig(context) { fun removeCustomNotificationsByThreadId(threadId: Long) { 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() } diff --git a/app/src/main/kotlin/org/fossify/messages/helpers/Constants.kt b/app/src/main/kotlin/org/fossify/messages/helpers/Constants.kt index 1c4cb454..01630cbd 100644 --- a/app/src/main/kotlin/org/fossify/messages/helpers/Constants.kt +++ b/app/src/main/kotlin/org/fossify/messages/helpers/Constants.kt @@ -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 PINNED_CONVERSATIONS = "pinned_conversations" 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_MMS = "export_mms" const val JSON_FILE_EXTENSION = ".json" diff --git a/app/src/main/res/layout/dialog_export_blocked_keywords.xml b/app/src/main/res/layout/dialog_export_blocked_keywords.xml new file mode 100644 index 00000000..ed187263 --- /dev/null +++ b/app/src/main/res/layout/dialog_export_blocked_keywords.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/menu/menu_add_blocked_keyword.xml b/app/src/main/res/menu/menu_add_blocked_keyword.xml index 53c85055..555b7b3b 100644 --- a/app/src/main/res/menu/menu_add_blocked_keyword.xml +++ b/app/src/main/res/menu/menu_add_blocked_keyword.xml @@ -6,4 +6,12 @@ android:icon="@drawable/ic_plus_vector" android:title="@string/add_a_blocked_keyword" app:showAsAction="ifRoom" /> + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a2753dff..c7a6e2d5 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -106,6 +106,7 @@ Send long messages as MMS Messages + Keywords Export messages Export SMS Export MMS @@ -113,6 +114,8 @@ Import SMS Import MMS You have to select at least one item + Export blocked keywords + Import blocked keywords Can\'t send message to an empty number Unable to save message to the telephony database