diff --git a/app/src/main/java/be/scri/App.kt b/app/src/main/java/be/scri/App.kt index fad2489b..02298789 100644 --- a/app/src/main/java/be/scri/App.kt +++ b/app/src/main/java/be/scri/App.kt @@ -2,7 +2,6 @@ package be.scri import android.app.Application import androidx.appcompat.app.AppCompatDelegate -import be.scri.extensions.checkUseEnglish import be.scri.extensions.config class App : Application() { @@ -15,6 +14,5 @@ class App : Application() { }, ) super.onCreate() - checkUseEnglish() } } diff --git a/app/src/main/java/be/scri/activities/BaseSimpleActivity.kt b/app/src/main/java/be/scri/activities/BaseSimpleActivity.kt deleted file mode 100644 index 7067397c..00000000 --- a/app/src/main/java/be/scri/activities/BaseSimpleActivity.kt +++ /dev/null @@ -1,609 +0,0 @@ -package be.scri.activities - -import android.annotation.SuppressLint -import android.app.Activity -import android.app.ActivityManager -import android.app.RecoverableSecurityException -import android.app.role.RoleManager -import android.content.ActivityNotFoundException -import android.content.Context -import android.content.Intent -import android.graphics.BitmapFactory -import android.graphics.Color -import android.graphics.drawable.ColorDrawable -import android.net.Uri -import android.os.Build -import android.os.Bundle -import android.provider.DocumentsContract -import android.provider.MediaStore -import android.telecom.TelecomManager -import android.view.Menu -import android.view.MenuItem -import android.view.View -import android.view.WindowManager -import androidx.annotation.RequiresApi -import androidx.appcompat.app.AppCompatActivity -import be.scri.R -import be.scri.extensions.addBit -import be.scri.extensions.baseConfig -import be.scri.extensions.buildDocumentUriSdk30 -import be.scri.extensions.createAndroidDataOrObbPath -import be.scri.extensions.createAndroidDataOrObbUri -import be.scri.extensions.createFirstParentTreeUri -import be.scri.extensions.getAppIconColors -import be.scri.extensions.getColoredDrawableWithColor -import be.scri.extensions.getContrastColor -import be.scri.extensions.getFirstParentLevel -import be.scri.extensions.getFirstParentPath -import be.scri.extensions.getProperStatusBarColor -import be.scri.extensions.getProperTextColor -import be.scri.extensions.getThemeId -import be.scri.extensions.hideKeyboard -import be.scri.extensions.humanizePath -import be.scri.extensions.isPathOnOTG -import be.scri.extensions.isPathOnSD -import be.scri.extensions.removeBit -import be.scri.extensions.showErrorToast -import be.scri.extensions.storeAndroidTreeUri -import be.scri.extensions.toast -import be.scri.extensions.updateActionBarTitle -import be.scri.extensions.updateOTGPathFromPartition -import be.scri.fragments.AboutFragment -import be.scri.helpers.APP_ICON_IDS -import be.scri.helpers.APP_LAUNCHER_NAME -import be.scri.helpers.APP_LICENSES -import be.scri.helpers.APP_NAME -import be.scri.helpers.APP_VERSION_NAME -import be.scri.helpers.CREATE_DOCUMENT_SDK_30 -import be.scri.helpers.EXTERNAL_STORAGE_PROVIDER_AUTHORITY -import be.scri.helpers.INVALID_NAVIGATION_BAR_COLOR -import be.scri.helpers.MyContextWrapper -import be.scri.helpers.OPEN_DOCUMENT_TREE_FOR_ANDROID_DATA_OR_OBB -import be.scri.helpers.OPEN_DOCUMENT_TREE_FOR_SDK_30 -import be.scri.helpers.OPEN_DOCUMENT_TREE_OTG -import be.scri.helpers.OPEN_DOCUMENT_TREE_SD -import be.scri.helpers.REQUEST_CODE_SET_DEFAULT_DIALER -import be.scri.helpers.SD_OTG_SHORT -import be.scri.helpers.SHOW_FAQ_BEFORE_MAIL -import be.scri.helpers.isMarshmallowPlus -import be.scri.helpers.isOreoPlus -import be.scri.helpers.isQPlus -import be.scri.helpers.isRPlus -import java.util.regex.Pattern - -abstract class BaseSimpleActivity : AppCompatActivity() { - var copyMoveCallback: ((destinationPath: String) -> Unit)? = null - var actionOnPermission: ((granted: Boolean) -> Unit)? = null - var isAskingPermissions = false - var useDynamicTheme = true - var showTransparentTop = false - var checkedDocumentPath = "" - var configItemsToExport = LinkedHashMap() - - companion object { - var funAfterSAFPermission: ((success: Boolean) -> Unit)? = null - var funAfterSdk30Action: ((success: Boolean) -> Unit)? = null - var funAfterUpdate30File: ((success: Boolean) -> Unit)? = null - var funRecoverableSecurity: ((success: Boolean) -> Unit)? = null - private const val GENERIC_PERM_HANDLER = 100 - private const val DELETE_FILE_SDK_30_HANDLER = 300 - private const val RECOVERABLE_SECURITY_HANDLER = 301 - private const val UPDATE_FILE_SDK_30_HANDLER = 302 - } - - abstract fun getAppIconIDs(): ArrayList - - abstract fun getAppLauncherName(): String - - override fun onCreate(savedInstanceState: Bundle?) { - if (useDynamicTheme) { - setTheme(getThemeId(showTransparentTop = showTransparentTop)) - } - - super.onCreate(savedInstanceState) - } - - override fun onResume() { - super.onResume() - if (useDynamicTheme) { - setTheme(getThemeId(showTransparentTop = showTransparentTop)) - - val backgroundColor = - if (baseConfig.isUsingSystemTheme) { - resources.getColor(R.color.you_background_color, theme) - } else { - baseConfig.backgroundColor - } - - updateBackgroundColor(backgroundColor) - } - - if (showTransparentTop) { - window.statusBarColor = Color.TRANSPARENT - } else { - val color = - if (baseConfig.isUsingSystemTheme) { - resources.getColor(R.color.you_status_bar_color) - } else { - getProperStatusBarColor() - } - - updateActionbarColor(color) - } - - updateRecentsAppIcon() - updateNavigationBarColor() - } - - override fun onDestroy() { - super.onDestroy() - funAfterSAFPermission = null - actionOnPermission = null - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - when (item.itemId) { - android.R.id.home -> { - hideKeyboard() - finish() - } - else -> return super.onOptionsItemSelected(item) - } - return true - } - - override fun attachBaseContext(newBase: Context) { - if (newBase.baseConfig.useEnglish) { - super.attachBaseContext(MyContextWrapper(newBase).wrap(newBase, "en")) - } else { - super.attachBaseContext(newBase) - } - } - - fun updateBackgroundColor(color: Int = baseConfig.backgroundColor) { - window.decorView.setBackgroundColor(color) - } - - fun updateStatusbarColor(color: Int) { - window.statusBarColor = color - - if (isMarshmallowPlus()) { - if (color.getContrastColor() == 0xFF333333.toInt()) { - window.decorView.systemUiVisibility = window.decorView.systemUiVisibility.addBit(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR) - } else { - window.decorView.systemUiVisibility = window.decorView.systemUiVisibility.removeBit(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR) - } - } - } - - fun updateActionbarColor(color: Int = getProperStatusBarColor()) { - updateActionBarTitle(supportActionBar?.title.toString(), color) - supportActionBar?.setBackgroundDrawable(ColorDrawable(color)) - updateStatusbarColor(color) - setTaskDescription(ActivityManager.TaskDescription(null, null, color)) - } - - fun updateNavigationBarColor(color: Int = baseConfig.navigationBarColor) { - if (baseConfig.navigationBarColor != INVALID_NAVIGATION_BAR_COLOR) { - try { - val colorToUse = if (color == -2) -1 else color - window.navigationBarColor = colorToUse - - if (isOreoPlus()) { - if (color.getContrastColor() == 0xFF333333.toInt()) { - window.decorView.systemUiVisibility = window.decorView.systemUiVisibility.addBit(View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR) - } else { - window.decorView.systemUiVisibility = window.decorView.systemUiVisibility.removeBit(View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR) - } - } - } catch (ignored: Exception) { - } - } - } - - fun updateRecentsAppIcon() { - if (baseConfig.isUsingModifiedAppIcon) { - val appIconIDs = getAppIconIDs() - val currentAppIconColorIndex = getCurrentAppIconColorIndex() - if (appIconIDs.size - 1 < currentAppIconColorIndex) { - return - } - - val recentsIcon = BitmapFactory.decodeResource(resources, appIconIDs[currentAppIconColorIndex]) - val title = getAppLauncherName() - val color = baseConfig.primaryColor - - val description = ActivityManager.TaskDescription(title, recentsIcon, color) - setTaskDescription(description) - } - } - - fun updateMenuItemColors( - menu: Menu?, - useCrossAsBack: Boolean = false, - baseColor: Int = getProperStatusBarColor(), - updateHomeAsUpColor: Boolean = true, - isContextualMenu: Boolean = false, - forceWhiteIcons: Boolean = false, - ) { - if (menu == null) { - return - } - - var color = baseColor.getContrastColor() - if (baseConfig.isUsingSystemTheme && !isContextualMenu) { - color = getProperTextColor() - } - - if (forceWhiteIcons) { - color = Color.WHITE - } - - for (i in 0 until menu.size()) { - try { - menu.getItem(i)?.icon?.setTint(color) - } catch (ignored: Exception) { - } - } - - if (updateHomeAsUpColor && !isContextualMenu) { - val drawableId = if (useCrossAsBack) R.drawable.ic_cross_vector else R.drawable.ic_arrow_left_vector - val icon = resources.getColoredDrawableWithColor(drawableId, color) - supportActionBar?.setHomeAsUpIndicator(icon) - } - } - - private fun getCurrentAppIconColorIndex(): Int { - val appIconColor = baseConfig.appIconColor - getAppIconColors().forEachIndexed { index, color -> - if (color == appIconColor) { - return index - } - } - return 0 - } - - fun setTranslucentNavigation() { - window.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION, WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION) - } - - override fun onActivityResult( - requestCode: Int, - resultCode: Int, - resultData: Intent?, - ) { - super.onActivityResult(requestCode, resultCode, resultData) - val partition: String = - if (checkedDocumentPath.length >= 18) { - checkedDocumentPath.substring(9, 18) - } else { - showErrorToast("An unexpected error occurred while extracting the substring.") - "" - } - - val sdOtgPattern = Pattern.compile(SD_OTG_SHORT) - if (requestCode == CREATE_DOCUMENT_SDK_30) { - if (resultCode == Activity.RESULT_OK && resultData != null && resultData.data != null) { - val treeUri = resultData.data - val checkedUri = buildDocumentUriSdk30(checkedDocumentPath) - - if (treeUri != checkedUri) { - toast(getString(R.string.wrong_folder_selected, checkedDocumentPath)) - return - } - - val takeFlags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION - applicationContext.contentResolver.takePersistableUriPermission(treeUri, takeFlags) - val funAfter = funAfterSdk30Action - funAfterSdk30Action = null - funAfter?.invoke(true) - } else { - funAfterSdk30Action?.invoke(false) - } - } else if (requestCode == OPEN_DOCUMENT_TREE_FOR_SDK_30) { - if (resultCode == Activity.RESULT_OK && resultData != null && resultData.data != null) { - val treeUri = resultData.data - val checkedUri = createFirstParentTreeUri(checkedDocumentPath) - - if (treeUri != checkedUri) { - val level = getFirstParentLevel(checkedDocumentPath) - val firstParentPath = checkedDocumentPath.getFirstParentPath(this, level) - toast(getString(R.string.wrong_folder_selected, humanizePath(firstParentPath))) - return - } - - val takeFlags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION - applicationContext.contentResolver.takePersistableUriPermission(treeUri, takeFlags) - val funAfter = funAfterSdk30Action - funAfterSdk30Action = null - funAfter?.invoke(true) - } else { - funAfterSdk30Action?.invoke(false) - } - } else if (requestCode == OPEN_DOCUMENT_TREE_FOR_ANDROID_DATA_OR_OBB) { - if (resultCode == Activity.RESULT_OK && resultData != null && resultData.data != null) { - if (isProperAndroidRoot(checkedDocumentPath, resultData.data!!)) { - if (resultData.dataString == baseConfig.otgTreeUri || resultData.dataString == baseConfig.sdTreeUri) { - val pathToSelect = createAndroidDataOrObbPath(checkedDocumentPath) - toast(getString(R.string.wrong_folder_selected, pathToSelect)) - return - } - - val treeUri = resultData.data - storeAndroidTreeUri(checkedDocumentPath, treeUri.toString()) - - val takeFlags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION - applicationContext.contentResolver.takePersistableUriPermission(treeUri!!, takeFlags) - funAfterSAFPermission?.invoke(true) - funAfterSAFPermission = null - } else { - toast(getString(R.string.wrong_folder_selected, createAndroidDataOrObbPath(checkedDocumentPath))) - Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).apply { - if (isRPlus()) { - putExtra(DocumentsContract.EXTRA_INITIAL_URI, createAndroidDataOrObbUri(checkedDocumentPath)) - } - - try { - startActivityForResult(this, requestCode) - } catch (e: ActivityNotFoundException) { - showErrorToast("No application found to handle this request.") - } catch (e: SecurityException) { - showErrorToast("Security exception: ${e.message}") - } catch (e: IllegalArgumentException) { - showErrorToast("Invalid argument provided: ${e.message}") - } - } - } - } else { - funAfterSAFPermission?.invoke(false) - } - } else if (requestCode == OPEN_DOCUMENT_TREE_SD) { - if (resultCode == Activity.RESULT_OK && resultData != null && resultData.data != null) { - val isProperPartition = - partition.isEmpty() || - !sdOtgPattern.matcher(partition).matches() || - ( - sdOtgPattern - .matcher(partition) - .matches() && - resultData.dataString!!.contains(partition) - ) - if (isProperSDRootFolder(resultData.data!!) && isProperPartition) { - if (resultData.dataString == baseConfig.otgTreeUri) { - toast(R.string.sd_card_usb_same) - return - } - - saveTreeUri(resultData) - funAfterSAFPermission?.invoke(true) - funAfterSAFPermission = null - } else { - toast(R.string.wrong_root_selected) - val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE) - - try { - startActivityForResult(intent, requestCode) - } catch (e: ActivityNotFoundException) { - showErrorToast("No application found to handle this request.") - } catch (e: SecurityException) { - showErrorToast("Security exception: ${e.message}") - } catch (e: IllegalArgumentException) { - showErrorToast("Invalid argument provided: ${e.message}") - } - } - } else { - funAfterSAFPermission?.invoke(false) - } - } else if (requestCode == OPEN_DOCUMENT_TREE_OTG) { - if (resultCode == Activity.RESULT_OK && resultData != null && resultData.data != null) { - val isProperPartition = - partition.isEmpty() || - !sdOtgPattern.matcher(partition).matches() || - ( - sdOtgPattern - .matcher(partition) - .matches() && - resultData.dataString!!.contains(partition) - ) - if (isProperOTGRootFolder(resultData.data!!) && isProperPartition) { - if (resultData.dataString == baseConfig.sdTreeUri) { - funAfterSAFPermission?.invoke(false) - toast(R.string.sd_card_usb_same) - return - } - baseConfig.otgTreeUri = resultData.dataString!! - baseConfig.otgPartition = - baseConfig.otgTreeUri - .removeSuffix("%3A") - .substringAfterLast('/') - .trimEnd('/') - updateOTGPathFromPartition() - - val takeFlags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION - applicationContext.contentResolver.takePersistableUriPermission(resultData.data!!, takeFlags) - - funAfterSAFPermission?.invoke(true) - funAfterSAFPermission = null - } else { - toast(R.string.wrong_root_selected_usb) - val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE) - - try { - startActivityForResult(intent, requestCode) - } catch (e: ActivityNotFoundException) { - showErrorToast("No application found to handle this request.") - } catch (e: SecurityException) { - showErrorToast("Security exception: ${e.message}") - } - } - } else { - funAfterSAFPermission?.invoke(false) - } - } else if (requestCode == Companion.DELETE_FILE_SDK_30_HANDLER) { - funAfterSdk30Action?.invoke(resultCode == Activity.RESULT_OK) - } else if (requestCode == Companion.RECOVERABLE_SECURITY_HANDLER) { - funRecoverableSecurity?.invoke(resultCode == Activity.RESULT_OK) - funRecoverableSecurity = null - } else if (requestCode == Companion.UPDATE_FILE_SDK_30_HANDLER) { - funAfterUpdate30File?.invoke(resultCode == Activity.RESULT_OK) - } - } - - private fun saveTreeUri(resultData: Intent) { - val treeUri = resultData.data - baseConfig.sdTreeUri = treeUri.toString() - - val takeFlags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION - applicationContext.contentResolver.takePersistableUriPermission(treeUri!!, takeFlags) - } - - private fun isProperSDRootFolder(uri: Uri) = isExternalStorageDocument(uri) && isRootUri(uri) && !isInternalStorage(uri) - - private fun isProperSDFolder(uri: Uri) = isExternalStorageDocument(uri) && !isInternalStorage(uri) - - private fun isProperOTGRootFolder(uri: Uri) = isExternalStorageDocument(uri) && isRootUri(uri) && !isInternalStorage(uri) - - private fun isProperOTGFolder(uri: Uri) = isExternalStorageDocument(uri) && !isInternalStorage(uri) - - private fun isRootUri(uri: Uri) = uri.lastPathSegment?.endsWith(":") ?: false - - private fun isInternalStorage(uri: Uri) = isExternalStorageDocument(uri) && DocumentsContract.getTreeDocumentId(uri).contains("primary") - - private fun isAndroidDir(uri: Uri) = isExternalStorageDocument(uri) && DocumentsContract.getTreeDocumentId(uri).contains(":Android") - - private fun isInternalStorageAndroidDir(uri: Uri) = isInternalStorage(uri) && isAndroidDir(uri) - - private fun isOTGAndroidDir(uri: Uri) = isProperOTGFolder(uri) && isAndroidDir(uri) - - private fun isSDAndroidDir(uri: Uri) = isProperSDFolder(uri) && isAndroidDir(uri) - - private fun isExternalStorageDocument(uri: Uri) = EXTERNAL_STORAGE_PROVIDER_AUTHORITY == uri.authority - - private fun isProperAndroidRoot( - path: String, - uri: Uri, - ): Boolean = - when { - isPathOnOTG(path) -> isOTGAndroidDir(uri) - isPathOnSD(path) -> isSDAndroidDir(uri) - else -> isInternalStorageAndroidDir(uri) - } - - fun startAboutActivity( - appNameId: Int, - licenseMask: Int, - versionName: String, - showFAQBeforeMail: Boolean, - ) { - hideKeyboard() - Intent(applicationContext, AboutFragment::class.java).apply { - putExtra(APP_ICON_IDS, getAppIconIDs()) - putExtra(APP_LAUNCHER_NAME, getAppLauncherName()) - putExtra(APP_NAME, getString(appNameId)) - putExtra(APP_LICENSES, licenseMask) - putExtra(APP_VERSION_NAME, versionName) - putExtra(SHOW_FAQ_BEFORE_MAIL, showFAQBeforeMail) - startActivity(this) - } - } - - @RequiresApi(Build.VERSION_CODES.O) - fun handleOTGPermission(callback: (success: Boolean) -> Unit) { - hideKeyboard() - if (baseConfig.otgTreeUri.isNotEmpty()) { - callback(true) - return - } - - funAfterSAFPermission = callback - } - - @SuppressLint("NewApi") - fun deleteSDK30Uris( - uris: List, - callback: (success: Boolean) -> Unit, - ) { - hideKeyboard() - if (isRPlus()) { - funAfterSdk30Action = callback - try { - val deleteRequest = MediaStore.createDeleteRequest(contentResolver, uris).intentSender - startIntentSenderForResult(deleteRequest, Companion.DELETE_FILE_SDK_30_HANDLER, null, 0, 0, 0) - } catch (e: SecurityException) { - showErrorToast("Security exception: ${e.message}") - } catch (e: IllegalArgumentException) { - showErrorToast("Illegal argument: ${e.message}") - } - } else { - callback(false) - } - } - - @SuppressLint("NewApi") - fun updateSDK30Uris( - uris: List, - callback: (success: Boolean) -> Unit, - ) { - hideKeyboard() - if (isRPlus()) { - funAfterUpdate30File = callback - try { - val writeRequest = MediaStore.createWriteRequest(contentResolver, uris).intentSender - startIntentSenderForResult(writeRequest, Companion.UPDATE_FILE_SDK_30_HANDLER, null, 0, 0, 0) - } catch (e: SecurityException) { - showErrorToast("Security exception: ${e.message}") - } catch (e: IllegalArgumentException) { - showErrorToast("Illegal argument: ${e.message}") - } - } else { - callback(false) - } - } - - @SuppressLint("NewApi") - fun handleRecoverableSecurityException(callback: (success: Boolean) -> Unit) { - try { - callback.invoke(true) - } catch (securityException: SecurityException) { - if (isQPlus()) { - funRecoverableSecurity = callback - val recoverableSecurityException = securityException as? RecoverableSecurityException ?: throw securityException - val intentSender = recoverableSecurityException.userAction.actionIntent.intentSender - startIntentSenderForResult(intentSender, Companion.RECOVERABLE_SECURITY_HANDLER, null, 0, 0, 0) - } else { - callback(false) - } - } - } - - override fun onRequestPermissionsResult( - requestCode: Int, - permissions: Array, - grantResults: IntArray, - ) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults) - isAskingPermissions = false - if (requestCode == Companion.GENERIC_PERM_HANDLER && grantResults.isNotEmpty()) { - actionOnPermission?.invoke(grantResults[0] == 0) - } - } - - @SuppressLint("InlinedApi") - protected fun launchSetDefaultDialerIntent() { - if (isQPlus()) { - val roleManager = getSystemService(RoleManager::class.java) - if (roleManager!!.isRoleAvailable(RoleManager.ROLE_DIALER) && !roleManager.isRoleHeld(RoleManager.ROLE_DIALER)) { - val intent = roleManager.createRequestRoleIntent(RoleManager.ROLE_DIALER) - startActivityForResult(intent, REQUEST_CODE_SET_DEFAULT_DIALER) - } - } else { - Intent(TelecomManager.ACTION_CHANGE_DEFAULT_DIALER).putExtra(TelecomManager.EXTRA_CHANGE_DEFAULT_DIALER_PACKAGE_NAME, packageName).apply { - try { - startActivityForResult(this, REQUEST_CODE_SET_DEFAULT_DIALER) - } catch (e: ActivityNotFoundException) { - toast(R.string.no_app_found) - } - } - } - } -} diff --git a/app/src/main/java/be/scri/activities/BaseSplashActivity.kt b/app/src/main/java/be/scri/activities/BaseSplashActivity.kt deleted file mode 100644 index 6177d6aa..00000000 --- a/app/src/main/java/be/scri/activities/BaseSplashActivity.kt +++ /dev/null @@ -1,49 +0,0 @@ -package be.scri.activities - -import android.graphics.Color -import android.os.Bundle -import androidx.appcompat.app.AppCompatActivity -import be.scri.R -import be.scri.extensions.baseConfig -import be.scri.extensions.getSharedTheme -import be.scri.extensions.isThankYouInstalled -import be.scri.extensions.isUsingSystemDarkTheme - -abstract class BaseSplashActivity : AppCompatActivity() { - abstract fun initActivity() - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - baseConfig.apply { - if (isUsingAutoTheme) { - val isUsingSystemDarkTheme = isUsingSystemDarkTheme() - isUsingSharedTheme = false - textColor = resources.getColor(if (isUsingSystemDarkTheme) R.color.theme_dark_text_color else R.color.theme_light_text_color) - backgroundColor = resources.getColor(if (isUsingSystemDarkTheme) R.color.theme_dark_background_color else R.color.theme_light_background_color) - navigationBarColor = if (isUsingSystemDarkTheme) Color.BLACK else -2 - } - } - - if (!baseConfig.isUsingAutoTheme && !baseConfig.isUsingSystemTheme && isThankYouInstalled()) { - getSharedTheme { - if (it != null) { - baseConfig.apply { - wasSharedThemeForced = true - isUsingSharedTheme = true - wasSharedThemeEverActivated = true - - textColor = it.textColor - backgroundColor = it.backgroundColor - primaryColor = it.primaryColor - navigationBarColor = it.navigationBarColor - accentColor = it.accentColor - } - } - initActivity() - } - } else { - initActivity() - } - } -} diff --git a/app/src/main/java/be/scri/activities/MainActivity.kt b/app/src/main/java/be/scri/activities/MainActivity.kt index 9981a001..94609f13 100644 --- a/app/src/main/java/be/scri/activities/MainActivity.kt +++ b/app/src/main/java/be/scri/activities/MainActivity.kt @@ -8,6 +8,7 @@ import android.view.View import android.view.ViewGroup import android.widget.Button import android.widget.TextView +import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatDelegate import androidx.viewpager2.widget.ViewPager2 import be.scri.R @@ -16,7 +17,7 @@ import be.scri.databinding.ActivityMainBinding import be.scri.services.EnglishKeyboardIME import com.google.android.material.bottomnavigation.BottomNavigationView -class MainActivity : SimpleActivity() { +class MainActivity : AppCompatActivity() { private lateinit var bottomNavigationView: BottomNavigationView private lateinit var viewPager: ViewPager2 private lateinit var adapter: ViewPagerAdapter diff --git a/app/src/main/java/be/scri/activities/SimpleActivity.kt b/app/src/main/java/be/scri/activities/SimpleActivity.kt deleted file mode 100644 index fd794485..00000000 --- a/app/src/main/java/be/scri/activities/SimpleActivity.kt +++ /dev/null @@ -1,12 +0,0 @@ -package be.scri.activities - -import be.scri.R - -open class SimpleActivity : BaseSimpleActivity() { - override fun getAppIconIDs() = - arrayListOf( - R.mipmap.ic_launcher, - ) - - override fun getAppLauncherName() = getString(R.string.app_launcher_name) -} diff --git a/app/src/main/java/be/scri/activities/SplashActivity.kt b/app/src/main/java/be/scri/activities/SplashActivity.kt deleted file mode 100644 index c2ca7af4..00000000 --- a/app/src/main/java/be/scri/activities/SplashActivity.kt +++ /dev/null @@ -1,11 +0,0 @@ -package be.scri.activities - -import android.content.Intent -import be.scri.fragments.MainFragment - -class SplashActivity : BaseSplashActivity() { - override fun initActivity() { - startActivity(Intent(this, MainFragment::class.java)) - finish() - } -} diff --git a/app/src/main/java/be/scri/databases/ClipsDatabase.kt b/app/src/main/java/be/scri/databases/ClipsDatabase.kt deleted file mode 100644 index a742d267..00000000 --- a/app/src/main/java/be/scri/databases/ClipsDatabase.kt +++ /dev/null @@ -1,33 +0,0 @@ -package be.scri.databases - -import android.content.Context -import androidx.room.Database -import androidx.room.Room -import androidx.room.RoomDatabase -import be.scri.interfaces.ClipsDao -import be.scri.models.Clip - -@Database(entities = [Clip::class], version = 1) -abstract class ClipsDatabase : RoomDatabase() { - abstract fun ClipsDao(): ClipsDao - - companion object { - private var db: ClipsDatabase? = null - - fun getInstance(context: Context): ClipsDatabase { - if (db == null) { - synchronized(ClipsDatabase::class) { - if (db == null) { - db = Room.databaseBuilder(context.applicationContext, ClipsDatabase::class.java, "clips.db").build() - db!!.openHelper.setWriteAheadLoggingEnabled(true) - } - } - } - return db!! - } - - fun destroyInstance() { - db = null - } - } -} diff --git a/app/src/main/java/be/scri/extensions/Activity.kt b/app/src/main/java/be/scri/extensions/Activity.kt deleted file mode 100644 index 0d0105d3..00000000 --- a/app/src/main/java/be/scri/extensions/Activity.kt +++ /dev/null @@ -1,161 +0,0 @@ -package be.scri.extensions - -import android.app.Activity -import android.content.Context -import android.os.Handler -import android.os.Looper -import android.text.Html -import android.view.View -import android.view.WindowManager -import android.view.inputmethod.InputMethodManager -import androidx.appcompat.app.AppCompatActivity -import androidx.documentfile.provider.DocumentFile -import be.scri.R -import be.scri.activities.BaseSimpleActivity -import be.scri.helpers.isOnMainThread -import java.io.File -import java.io.FileNotFoundException -import java.io.FileOutputStream -import java.io.IOException -import java.io.OutputStream - -fun AppCompatActivity.updateActionBarTitle( - text: String, - color: Int = getProperStatusBarColor(), -) { - val colorToUse = - if (baseConfig.isUsingSystemTheme) { - getProperTextColor() - } else { - color.getContrastColor() - } - - supportActionBar?.title = Html.fromHtml("$text") -} - -fun Activity.hideKeyboard() { - if (isOnMainThread()) { - hideKeyboardSync() - } else { - Handler(Looper.getMainLooper()).post { - hideKeyboardSync() - } - } -} - -fun Activity.hideKeyboardSync() { - val inputMethodManager = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager - inputMethodManager.hideSoftInputFromWindow((currentFocus ?: View(this)).windowToken, 0) - window!!.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN) - currentFocus?.clearFocus() -} - -fun BaseSimpleActivity.showFileCreateError(path: String) { - val error = String.format(getString(R.string.could_not_create_file), path) - baseConfig.sdTreeUri = "" - showErrorToast(error) -} - -fun BaseSimpleActivity.getFileOutputStreamSync( - path: String, - mimeType: String, - parentDocumentFile: DocumentFile? = null, -): OutputStream? { - val targetFile = File(path) - - return when { - isRestrictedSAFOnlyRoot(path) -> { - val uri = getAndroidSAFUri(path) - if (!getDoesFilePathExist(path)) { - createAndroidSAFFile(path) - } - applicationContext.contentResolver.openOutputStream(uri) - } - needsStupidWritePermissions(path) -> { - var documentFile = - parentDocumentFile ?: run { - if (getDoesFilePathExist(targetFile.parentFile.absolutePath)) { - getDocumentFile(targetFile.parent) - } else { - getDocumentFile(targetFile.parentFile.parent) - ?.createDirectory(targetFile.parentFile.name) - ?: getDocumentFile(targetFile.parentFile.absolutePath) - } - } - - if (documentFile == null) { - val casualOutputStream = createCasualFileOutputStream(this, targetFile) - return casualOutputStream ?: run { - showFileCreateError(targetFile.parent) - null - } - } - - try { - val newDocument = getDocumentFile(path) ?: documentFile.createFile(mimeType, path.getFilenameFromPath()) - applicationContext.contentResolver.openOutputStream(newDocument!!.uri) - } catch (e: FileNotFoundException) { - showErrorToast("File not found: ${e.message}") - null - } catch (e: IOException) { - showErrorToast("I/O error: ${e.message}") - null - } - } - isAccessibleWithSAFSdk30(path) -> { - try { - val uri = createDocumentUriUsingFirstParentTreeUri(path) - if (!getDoesFilePathExist(path)) { - createSAFFileSdk30(path) - } - applicationContext.contentResolver.openOutputStream(uri) - } catch (e: FileNotFoundException) { - showErrorToast("File not found: ${e.message}") - null - } catch (e: IOException) { - showErrorToast("I/O error: ${e.message}") - null - } ?: createCasualFileOutputStream(this, targetFile) - } - else -> createCasualFileOutputStream(this, targetFile) - } -} - -private fun createCasualFileOutputStream( - activity: BaseSimpleActivity, - targetFile: File, -): OutputStream? { - targetFile.parentFile?.takeIf { !it.exists() }?.mkdirs() - - return try { - FileOutputStream(targetFile) - } catch (e: FileNotFoundException) { - activity.showErrorToast("File not found: ${e.message}") - null - } catch (e: IOException) { - activity.showErrorToast("I/O error: ${e.message}") - null - } -} - -fun BaseSimpleActivity.createDirectorySync(directory: String): Boolean { - if (getDoesFilePathExist(directory)) { - return true - } - - if (needsStupidWritePermissions(directory)) { - val documentFile = getDocumentFile(directory.getParentPath()) ?: return false - val newDir = documentFile.createDirectory(directory.getFilenameFromPath()) ?: getDocumentFile(directory) - return newDir != null - } - - if (isRestrictedSAFOnlyRoot(directory)) { - return createAndroidSAFDirectory(directory) - } - - if (isAccessibleWithSAFSdk30(directory)) { - return createSAFDirectorySdk30(directory) - } - - return File(directory).mkdirs() -} diff --git a/app/src/main/java/be/scri/extensions/ActivitySdk30.kt b/app/src/main/java/be/scri/extensions/ActivitySdk30.kt deleted file mode 100644 index 157c2c02..00000000 --- a/app/src/main/java/be/scri/extensions/ActivitySdk30.kt +++ /dev/null @@ -1,80 +0,0 @@ -package be.scri.extensions - -import android.content.ContentValues -import android.provider.MediaStore -import be.scri.R -import be.scri.activities.BaseSimpleActivity -import be.scri.models.FileDirItem -import java.io.File -import java.io.InputStream -import java.io.OutputStream - -fun BaseSimpleActivity.copySingleFileSdk30( - source: FileDirItem, - destination: FileDirItem, -): Boolean { - val directory = destination.getParentPath() - if (!createDirectorySync(directory)) { - val error = String.format(getString(R.string.could_not_create_folder), directory) - showErrorToast(error) - return false - } - - var inputStream: InputStream? = null - var out: OutputStream? = null - try { - out = getFileOutputStreamSync(destination.path, source.path.getMimeType()) - inputStream = getFileInputStreamSync(source.path)!! - - var copiedSize = 0L - val buffer = ByteArray(DEFAULT_BUFFER_SIZE) - var bytes = inputStream.read(buffer) - while (bytes >= 0) { - out!!.write(buffer, 0, bytes) - copiedSize += bytes - bytes = inputStream.read(buffer) - } - - out?.flush() - - return if (source.size == copiedSize && getDoesFilePathExist(destination.path)) { - if (baseConfig.keepLastModified) { - copyOldLastModified(source.path, destination.path) - File(destination.path).setLastModified(File(source.path).lastModified()) - } - true - } else { - false - } - } finally { - inputStream?.close() - out?.close() - } -} - -fun BaseSimpleActivity.copyOldLastModified( - sourcePath: String, - destinationPath: String, -) { - val projection = arrayOf(MediaStore.Images.Media.DATE_TAKEN, MediaStore.Images.Media.DATE_MODIFIED) - val uri = MediaStore.Files.getContentUri("external") - val selection = "${MediaStore.MediaColumns.DATA} = ?" - var selectionArgs = arrayOf(sourcePath) - val cursor = applicationContext.contentResolver.query(uri, projection, selection, selectionArgs, null) - - cursor?.use { - if (cursor.moveToFirst()) { - val dateTaken = cursor.getLongValue(MediaStore.Images.Media.DATE_TAKEN) - val dateModified = cursor.getIntValue(MediaStore.Images.Media.DATE_MODIFIED) - - val values = - ContentValues().apply { - put(MediaStore.Images.Media.DATE_TAKEN, dateTaken) - put(MediaStore.Images.Media.DATE_MODIFIED, dateModified) - } - - selectionArgs = arrayOf(destinationPath) - applicationContext.contentResolver.update(uri, values, selection, selectionArgs) - } - } -} diff --git a/app/src/main/java/be/scri/extensions/ActivityThemes.kt b/app/src/main/java/be/scri/extensions/ActivityThemes.kt deleted file mode 100644 index 3c38c57c..00000000 --- a/app/src/main/java/be/scri/extensions/ActivityThemes.kt +++ /dev/null @@ -1,415 +0,0 @@ -package be.scri.extensions - -import android.app.Activity -import android.graphics.Color -import be.scri.R -import be.scri.helpers.DARK_GREY - -fun Activity.getThemeId( - color: Int = baseConfig.primaryColor, - showTransparentTop: Boolean = false, -) = when { - baseConfig.isUsingSystemTheme -> if (isUsingSystemDarkTheme()) R.style.AppTheme_Base_System else R.style.AppTheme_Base_System_Light - isBlackAndWhiteTheme() -> - when { - showTransparentTop -> R.style.AppTheme_BlackAndWhite_NoActionBar - baseConfig.primaryColor.getContrastColor() == DARK_GREY -> R.style.AppTheme_BlackAndWhite_DarkTextColor - else -> R.style.AppTheme_BlackAndWhite - } - isWhiteTheme() -> - when { - showTransparentTop -> R.style.AppTheme_White_NoActionBar - baseConfig.primaryColor.getContrastColor() == Color.WHITE -> R.style.AppTheme_White_LightTextColor - else -> R.style.AppTheme_White - } - showTransparentTop -> { - when (color) { - -12846 -> R.style.AppTheme_Red_100_core - -1074534 -> R.style.AppTheme_Red_200_core - -1739917 -> R.style.AppTheme_Red_300_core - -1092784 -> R.style.AppTheme_Red_400_core - -769226 -> R.style.AppTheme_Red_500_core - -1754827 -> R.style.AppTheme_Red_600_core - -2937041 -> R.style.AppTheme_Red_700_core - -3790808 -> R.style.AppTheme_Red_800_core - -4776932 -> R.style.AppTheme_Red_900_core - - -476208 -> R.style.AppTheme_Pink_100_core - -749647 -> R.style.AppTheme_Pink_200_core - -1023342 -> R.style.AppTheme_Pink_300_core - -1294214 -> R.style.AppTheme_Pink_400_core - -1499549 -> R.style.AppTheme_Pink_500_core - -2614432 -> R.style.AppTheme_Pink_600_core - -4056997 -> R.style.AppTheme_Pink_700_core - -5434281 -> R.style.AppTheme_Pink_800_core - -7860657 -> R.style.AppTheme_Pink_900_core - - -1982745 -> R.style.AppTheme_Purple_100_core - -3238952 -> R.style.AppTheme_Purple_200_core - -4560696 -> R.style.AppTheme_Purple_300_core - -5552196 -> R.style.AppTheme_Purple_400_core - -6543440 -> R.style.AppTheme_Purple_500_core - -7461718 -> R.style.AppTheme_Purple_600_core - -8708190 -> R.style.AppTheme_Purple_700_core - -9823334 -> R.style.AppTheme_Purple_800_core - -11922292 -> R.style.AppTheme_Purple_900_core - - -3029783 -> R.style.AppTheme_Deep_Purple_100_core - -5005861 -> R.style.AppTheme_Deep_Purple_200_core - -6982195 -> R.style.AppTheme_Deep_Purple_300_core - -8497214 -> R.style.AppTheme_Deep_Purple_400_core - -10011977 -> R.style.AppTheme_Deep_Purple_500_core - -10603087 -> R.style.AppTheme_Deep_Purple_600_core - -11457112 -> R.style.AppTheme_Deep_Purple_700_core - -12245088 -> R.style.AppTheme_Deep_Purple_800_core - -13558894 -> R.style.AppTheme_Deep_Purple_900_core - - -3814679 -> R.style.AppTheme_Indigo_100_core - -6313766 -> R.style.AppTheme_Indigo_200_core - -8812853 -> R.style.AppTheme_Indigo_300_core - -10720320 -> R.style.AppTheme_Indigo_400_core - -12627531 -> R.style.AppTheme_Indigo_500_core - -13022805 -> R.style.AppTheme_Indigo_600_core - -13615201 -> R.style.AppTheme_Indigo_700_core - -14142061 -> R.style.AppTheme_Indigo_800_core - -15064194 -> R.style.AppTheme_Indigo_900_core - - -4464901 -> R.style.AppTheme_Blue_100_core - -7288071 -> R.style.AppTheme_Blue_200_core - -10177034 -> R.style.AppTheme_Blue_300_core - -12409355 -> R.style.AppTheme_Blue_400_core - -14575885 -> R.style.AppTheme_Blue_500_core - -14776091 -> R.style.AppTheme_Blue_600_core - -15108398 -> R.style.AppTheme_Blue_700_core - -15374912 -> R.style.AppTheme_Blue_800_core - -15906911 -> R.style.AppTheme_Blue_900_core - - -4987396 -> R.style.AppTheme_Light_Blue_100_core - -8268550 -> R.style.AppTheme_Light_Blue_200_core - -11549705 -> R.style.AppTheme_Light_Blue_300_core - -14043396 -> R.style.AppTheme_Light_Blue_400_core - -16537100 -> R.style.AppTheme_Light_Blue_500_core - -16540699 -> R.style.AppTheme_Light_Blue_600_core - -16611119 -> R.style.AppTheme_Light_Blue_700_core - -16615491 -> R.style.AppTheme_Light_Blue_800_core - -16689253 -> R.style.AppTheme_Light_Blue_900_core - - -5051406 -> R.style.AppTheme_Cyan_100_core - -8331542 -> R.style.AppTheme_Cyan_200_core - -11677471 -> R.style.AppTheme_Cyan_300_core - -14235942 -> R.style.AppTheme_Cyan_400_core - -16728876 -> R.style.AppTheme_Cyan_500_core - -16732991 -> R.style.AppTheme_Cyan_600_core - -16738393 -> R.style.AppTheme_Cyan_700_core - -16743537 -> R.style.AppTheme_Cyan_800_core - -16752540 -> R.style.AppTheme_Cyan_900_core - - -5054501 -> R.style.AppTheme_Teal_100_core - -8336444 -> R.style.AppTheme_Teal_200_core - -11684180 -> R.style.AppTheme_Teal_300_core - -14244198 -> R.style.AppTheme_Teal_400_core - -16738680 -> R.style.AppTheme_Teal_500_core - -16742021 -> R.style.AppTheme_Teal_600_core - -16746133 -> R.style.AppTheme_Teal_700_core - -16750244 -> R.style.AppTheme_Teal_800_core - -16757440 -> R.style.AppTheme_Teal_900_core - - -3610935 -> R.style.AppTheme_Green_100_core - -5908825 -> R.style.AppTheme_Green_200_core - -8271996 -> R.style.AppTheme_Green_300_core - -10044566 -> R.style.AppTheme_Green_400_core - -11751600 -> R.style.AppTheme_Green_500_core - -12345273 -> R.style.AppTheme_Green_600_core - -13070788 -> R.style.AppTheme_Green_700_core - -13730510 -> R.style.AppTheme_Green_800_core - -14983648 -> R.style.AppTheme_Green_900_core - - -2298424 -> R.style.AppTheme_Light_Green_100_core - -3808859 -> R.style.AppTheme_Light_Green_200_core - -5319295 -> R.style.AppTheme_Light_Green_300_core - -6501275 -> R.style.AppTheme_Light_Green_400_core - -7617718 -> R.style.AppTheme_Light_Green_500_core - -8604862 -> R.style.AppTheme_Light_Green_600_core - -9920712 -> R.style.AppTheme_Light_Green_700_core - -11171025 -> R.style.AppTheme_Light_Green_800_core - -13407970 -> R.style.AppTheme_Light_Green_900_core - - -985917 -> R.style.AppTheme_Lime_100_core - -1642852 -> R.style.AppTheme_Lime_200_core - -2300043 -> R.style.AppTheme_Lime_300_core - -2825897 -> R.style.AppTheme_Lime_400_core - -3285959 -> R.style.AppTheme_Lime_500_core - -4142541 -> R.style.AppTheme_Lime_600_core - -5983189 -> R.style.AppTheme_Lime_700_core - -6382300 -> R.style.AppTheme_Lime_800_core - -8227049 -> R.style.AppTheme_Lime_900_core - - -1596 -> R.style.AppTheme_Yellow_100_core - -2672 -> R.style.AppTheme_Yellow_200_core - -3722 -> R.style.AppTheme_Yellow_300_core - -4520 -> R.style.AppTheme_Yellow_400_core - -5317 -> R.style.AppTheme_Yellow_500_core - -141259 -> R.style.AppTheme_Yellow_600_core - -278483 -> R.style.AppTheme_Yellow_700_core - -415707 -> R.style.AppTheme_Yellow_800_core - -688361 -> R.style.AppTheme_Yellow_900_core - - -4941 -> R.style.AppTheme_Amber_100_core - -8062 -> R.style.AppTheme_Amber_200_core - -10929 -> R.style.AppTheme_Amber_300_core - -13784 -> R.style.AppTheme_Amber_400_core - -16121 -> R.style.AppTheme_Amber_500_core - -19712 -> R.style.AppTheme_Amber_600_core - -24576 -> R.style.AppTheme_Amber_700_core - -28928 -> R.style.AppTheme_Amber_800_core - -37120 -> R.style.AppTheme_Amber_900_core - - -8014 -> R.style.AppTheme_Orange_100_core - -13184 -> R.style.AppTheme_Orange_200_core - -18611 -> R.style.AppTheme_Orange_300_core - -22746 -> R.style.AppTheme_Orange_400_core - -26624 -> R.style.AppTheme_Orange_500_core - -291840 -> R.style.AppTheme_Orange_600_core - -689152 -> R.style.AppTheme_Orange_700_core - -1086464 -> R.style.AppTheme_Orange_800_core - -1683200 -> R.style.AppTheme_Orange_900_core - - -13124 -> R.style.AppTheme_Deep_Orange_100_core - -21615 -> R.style.AppTheme_Deep_Orange_200_core - -30107 -> R.style.AppTheme_Deep_Orange_300_core - -36797 -> R.style.AppTheme_Deep_Orange_400_core - -43230 -> R.style.AppTheme_Deep_Orange_500_core - -765666 -> R.style.AppTheme_Deep_Orange_600_core - -1684967 -> R.style.AppTheme_Deep_Orange_700_core - -2604267 -> R.style.AppTheme_Deep_Orange_800_core - -4246004 -> R.style.AppTheme_Deep_Orange_900_core - - -2634552 -> R.style.AppTheme_Brown_100_core - -4412764 -> R.style.AppTheme_Brown_200_core - -6190977 -> R.style.AppTheme_Brown_300_core - -7508381 -> R.style.AppTheme_Brown_400_core - -8825528 -> R.style.AppTheme_Brown_500_core - -9614271 -> R.style.AppTheme_Brown_600_core - -10665929 -> R.style.AppTheme_Brown_700_core - -11652050 -> R.style.AppTheme_Brown_800_core - -12703965 -> R.style.AppTheme_Brown_900_core - - -3155748 -> R.style.AppTheme_Blue_Grey_100_core - -5194811 -> R.style.AppTheme_Blue_Grey_200_core - -7297874 -> R.style.AppTheme_Blue_Grey_300_core - -8875876 -> R.style.AppTheme_Blue_Grey_400_core - -10453621 -> R.style.AppTheme_Blue_Grey_500_core - -11243910 -> R.style.AppTheme_Blue_Grey_600_core - -12232092 -> R.style.AppTheme_Blue_Grey_700_core - -13154481 -> R.style.AppTheme_Blue_Grey_800_core - -14273992 -> R.style.AppTheme_Blue_Grey_900_core - - -1 -> R.style.AppTheme_Grey_100_core - -1118482 -> R.style.AppTheme_Grey_200_core - -2039584 -> R.style.AppTheme_Grey_300_core - -4342339 -> R.style.AppTheme_Grey_400_core - -6381922 -> R.style.AppTheme_Grey_500_core - -9079435 -> R.style.AppTheme_Grey_600_core - -10395295 -> R.style.AppTheme_Grey_700_core - -12434878 -> R.style.AppTheme_Grey_800_core - -16777216 -> R.style.AppTheme_Grey_900_core - - else -> R.style.AppTheme_Orange_700_core - } - } - else -> { - when (color) { - -12846 -> R.style.AppTheme_Red_100 - -1074534 -> R.style.AppTheme_Red_200 - -1739917 -> R.style.AppTheme_Red_300 - -1092784 -> R.style.AppTheme_Red_400 - -769226 -> R.style.AppTheme_Red_500 - -1754827 -> R.style.AppTheme_Red_600 - -2937041 -> R.style.AppTheme_Red_700 - -3790808 -> R.style.AppTheme_Red_800 - -4776932 -> R.style.AppTheme_Red_900 - - -476208 -> R.style.AppTheme_Pink_100 - -749647 -> R.style.AppTheme_Pink_200 - -1023342 -> R.style.AppTheme_Pink_300 - -1294214 -> R.style.AppTheme_Pink_400 - -1499549 -> R.style.AppTheme_Pink_500 - -2614432 -> R.style.AppTheme_Pink_600 - -4056997 -> R.style.AppTheme_Pink_700 - -5434281 -> R.style.AppTheme_Pink_800 - -7860657 -> R.style.AppTheme_Pink_900 - - -1982745 -> R.style.AppTheme_Purple_100 - -3238952 -> R.style.AppTheme_Purple_200 - -4560696 -> R.style.AppTheme_Purple_300 - -5552196 -> R.style.AppTheme_Purple_400 - -6543440 -> R.style.AppTheme_Purple_500 - -7461718 -> R.style.AppTheme_Purple_600 - -8708190 -> R.style.AppTheme_Purple_700 - -9823334 -> R.style.AppTheme_Purple_800 - -11922292 -> R.style.AppTheme_Purple_900 - - -3029783 -> R.style.AppTheme_Deep_Purple_100 - -5005861 -> R.style.AppTheme_Deep_Purple_200 - -6982195 -> R.style.AppTheme_Deep_Purple_300 - -8497214 -> R.style.AppTheme_Deep_Purple_400 - -10011977 -> R.style.AppTheme_Deep_Purple_500 - -10603087 -> R.style.AppTheme_Deep_Purple_600 - -11457112 -> R.style.AppTheme_Deep_Purple_700 - -12245088 -> R.style.AppTheme_Deep_Purple_800 - -13558894 -> R.style.AppTheme_Deep_Purple_900 - - -3814679 -> R.style.AppTheme_Indigo_100 - -6313766 -> R.style.AppTheme_Indigo_200 - -8812853 -> R.style.AppTheme_Indigo_300 - -10720320 -> R.style.AppTheme_Indigo_400 - -12627531 -> R.style.AppTheme_Indigo_500 - -13022805 -> R.style.AppTheme_Indigo_600 - -13615201 -> R.style.AppTheme_Indigo_700 - -14142061 -> R.style.AppTheme_Indigo_800 - -15064194 -> R.style.AppTheme_Indigo_900 - - -4464901 -> R.style.AppTheme_Blue_100 - -7288071 -> R.style.AppTheme_Blue_200 - -10177034 -> R.style.AppTheme_Blue_300 - -12409355 -> R.style.AppTheme_Blue_400 - -14575885 -> R.style.AppTheme_Blue_500 - -14776091 -> R.style.AppTheme_Blue_600 - -15108398 -> R.style.AppTheme_Blue_700 - -15374912 -> R.style.AppTheme_Blue_800 - -15906911 -> R.style.AppTheme_Blue_900 - - -4987396 -> R.style.AppTheme_Light_Blue_100 - -8268550 -> R.style.AppTheme_Light_Blue_200 - -11549705 -> R.style.AppTheme_Light_Blue_300 - -14043396 -> R.style.AppTheme_Light_Blue_400 - -16537100 -> R.style.AppTheme_Light_Blue_500 - -16540699 -> R.style.AppTheme_Light_Blue_600 - -16611119 -> R.style.AppTheme_Light_Blue_700 - -16615491 -> R.style.AppTheme_Light_Blue_800 - -16689253 -> R.style.AppTheme_Light_Blue_900 - - -5051406 -> R.style.AppTheme_Cyan_100 - -8331542 -> R.style.AppTheme_Cyan_200 - -11677471 -> R.style.AppTheme_Cyan_300 - -14235942 -> R.style.AppTheme_Cyan_400 - -16728876 -> R.style.AppTheme_Cyan_500 - -16732991 -> R.style.AppTheme_Cyan_600 - -16738393 -> R.style.AppTheme_Cyan_700 - -16743537 -> R.style.AppTheme_Cyan_800 - -16752540 -> R.style.AppTheme_Cyan_900 - - -5054501 -> R.style.AppTheme_Teal_100 - -8336444 -> R.style.AppTheme_Teal_200 - -11684180 -> R.style.AppTheme_Teal_300 - -14244198 -> R.style.AppTheme_Teal_400 - -16738680 -> R.style.AppTheme_Teal_500 - -16742021 -> R.style.AppTheme_Teal_600 - -16746133 -> R.style.AppTheme_Teal_700 - -16750244 -> R.style.AppTheme_Teal_800 - -16757440 -> R.style.AppTheme_Teal_900 - - -3610935 -> R.style.AppTheme_Green_100 - -5908825 -> R.style.AppTheme_Green_200 - -8271996 -> R.style.AppTheme_Green_300 - -10044566 -> R.style.AppTheme_Green_400 - -11751600 -> R.style.AppTheme_Green_500 - -12345273 -> R.style.AppTheme_Green_600 - -13070788 -> R.style.AppTheme_Green_700 - -13730510 -> R.style.AppTheme_Green_800 - -14983648 -> R.style.AppTheme_Green_900 - - -2298424 -> R.style.AppTheme_Light_Green_100 - -3808859 -> R.style.AppTheme_Light_Green_200 - -5319295 -> R.style.AppTheme_Light_Green_300 - -6501275 -> R.style.AppTheme_Light_Green_400 - -7617718 -> R.style.AppTheme_Light_Green_500 - -8604862 -> R.style.AppTheme_Light_Green_600 - -9920712 -> R.style.AppTheme_Light_Green_700 - -11171025 -> R.style.AppTheme_Light_Green_800 - -13407970 -> R.style.AppTheme_Light_Green_900 - - -985917 -> R.style.AppTheme_Lime_100 - -1642852 -> R.style.AppTheme_Lime_200 - -2300043 -> R.style.AppTheme_Lime_300 - -2825897 -> R.style.AppTheme_Lime_400 - -3285959 -> R.style.AppTheme_Lime_500 - -4142541 -> R.style.AppTheme_Lime_600 - -5983189 -> R.style.AppTheme_Lime_700 - -6382300 -> R.style.AppTheme_Lime_800 - -8227049 -> R.style.AppTheme_Lime_900 - - -1596 -> R.style.AppTheme_Yellow_100 - -2672 -> R.style.AppTheme_Yellow_200 - -3722 -> R.style.AppTheme_Yellow_300 - -4520 -> R.style.AppTheme_Yellow_400 - -5317 -> R.style.AppTheme_Yellow_500 - -141259 -> R.style.AppTheme_Yellow_600 - -278483 -> R.style.AppTheme_Yellow_700 - -415707 -> R.style.AppTheme_Yellow_800 - -688361 -> R.style.AppTheme_Yellow_900 - - -4941 -> R.style.AppTheme_Amber_100 - -8062 -> R.style.AppTheme_Amber_200 - -10929 -> R.style.AppTheme_Amber_300 - -13784 -> R.style.AppTheme_Amber_400 - -16121 -> R.style.AppTheme_Amber_500 - -19712 -> R.style.AppTheme_Amber_600 - -24576 -> R.style.AppTheme_Amber_700 - -28928 -> R.style.AppTheme_Amber_800 - -37120 -> R.style.AppTheme_Amber_900 - - -8014 -> R.style.AppTheme_Orange_100 - -13184 -> R.style.AppTheme_Orange_200 - -18611 -> R.style.AppTheme_Orange_300 - -22746 -> R.style.AppTheme_Orange_400 - -26624 -> R.style.AppTheme_Orange_500 - -291840 -> R.style.AppTheme_Orange_600 - -689152 -> R.style.AppTheme_Orange_700 - -1086464 -> R.style.AppTheme_Orange_800 - -1683200 -> R.style.AppTheme_Orange_900 - - -13124 -> R.style.AppTheme_Deep_Orange_100 - -21615 -> R.style.AppTheme_Deep_Orange_200 - -30107 -> R.style.AppTheme_Deep_Orange_300 - -36797 -> R.style.AppTheme_Deep_Orange_400 - -43230 -> R.style.AppTheme_Deep_Orange_500 - -765666 -> R.style.AppTheme_Deep_Orange_600 - -1684967 -> R.style.AppTheme_Deep_Orange_700 - -2604267 -> R.style.AppTheme_Deep_Orange_800 - -4246004 -> R.style.AppTheme_Deep_Orange_900 - - -2634552 -> R.style.AppTheme_Brown_100 - -4412764 -> R.style.AppTheme_Brown_200 - -6190977 -> R.style.AppTheme_Brown_300 - -7508381 -> R.style.AppTheme_Brown_400 - -8825528 -> R.style.AppTheme_Brown_500 - -9614271 -> R.style.AppTheme_Brown_600 - -10665929 -> R.style.AppTheme_Brown_700 - -11652050 -> R.style.AppTheme_Brown_800 - -12703965 -> R.style.AppTheme_Brown_900 - - -3155748 -> R.style.AppTheme_Blue_Grey_100 - -5194811 -> R.style.AppTheme_Blue_Grey_200 - -7297874 -> R.style.AppTheme_Blue_Grey_300 - -8875876 -> R.style.AppTheme_Blue_Grey_400 - -10453621 -> R.style.AppTheme_Blue_Grey_500 - -11243910 -> R.style.AppTheme_Blue_Grey_600 - -12232092 -> R.style.AppTheme_Blue_Grey_700 - -13154481 -> R.style.AppTheme_Blue_Grey_800 - -14273992 -> R.style.AppTheme_Blue_Grey_900 - - -1 -> R.style.AppTheme_Grey_100 - -1118482 -> R.style.AppTheme_Grey_200 - -2039584 -> R.style.AppTheme_Grey_300 - -4342339 -> R.style.AppTheme_Grey_400 - -6381922 -> R.style.AppTheme_Grey_500 - -9079435 -> R.style.AppTheme_Grey_600 - -10395295 -> R.style.AppTheme_Grey_700 - -12434878 -> R.style.AppTheme_Grey_800 - -16777216 -> R.style.AppTheme_Grey_900 - - else -> R.style.AppTheme_Orange_700 - } - } -} diff --git a/app/src/main/java/be/scri/extensions/AlertDialog.kt b/app/src/main/java/be/scri/extensions/AlertDialog.kt deleted file mode 100644 index c1941202..00000000 --- a/app/src/main/java/be/scri/extensions/AlertDialog.kt +++ /dev/null @@ -1,19 +0,0 @@ -package be.scri.extensions - -import android.view.WindowManager -import android.widget.EditText -import androidx.appcompat.app.AlertDialog - -fun AlertDialog.showKeyboard(editText: EditText) { - window!!.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE) - editText.apply { - requestFocus() - onGlobalLayout { - setSelection(text.toString().length) - } - } -} - -fun AlertDialog.hideKeyboard() { - window!!.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN) -} diff --git a/app/src/main/java/be/scri/extensions/Any.kt b/app/src/main/java/be/scri/extensions/Any.kt deleted file mode 100644 index 5fad19de..00000000 --- a/app/src/main/java/be/scri/extensions/Any.kt +++ /dev/null @@ -1,8 +0,0 @@ -package be.scri.extensions - -// extensions used mostly at importing app settings for now -fun Any.toBoolean() = toString() == "true" - -fun Any.toInt() = Integer.parseInt(toString()) - -fun Any.toStringSet() = toString().split(",".toRegex()).toSet() diff --git a/app/src/main/java/be/scri/extensions/App.kt b/app/src/main/java/be/scri/extensions/App.kt deleted file mode 100644 index 803e64fe..00000000 --- a/app/src/main/java/be/scri/extensions/App.kt +++ /dev/null @@ -1,13 +0,0 @@ -package be.scri.extensions - -import android.app.Application -import be.scri.helpers.isNougatPlus -import java.util.Locale - -fun Application.checkUseEnglish() { - if (baseConfig.useEnglish && !isNougatPlus()) { - val conf = resources.configuration - conf.locale = Locale.ENGLISH - resources.updateConfiguration(conf, resources.displayMetrics) - } -} diff --git a/app/src/main/java/be/scri/extensions/ArrayList.kt b/app/src/main/java/be/scri/extensions/ArrayList.kt deleted file mode 100644 index 139f190c..00000000 --- a/app/src/main/java/be/scri/extensions/ArrayList.kt +++ /dev/null @@ -1,6 +0,0 @@ -package be.scri.extensions - -fun ArrayList.moveLastItemToFront() { - val last = removeAt(size - 1) - add(0, last) -} diff --git a/app/src/main/java/be/scri/extensions/BufferedWriter.kt b/app/src/main/java/be/scri/extensions/BufferedWriter.kt deleted file mode 100644 index 9154278e..00000000 --- a/app/src/main/java/be/scri/extensions/BufferedWriter.kt +++ /dev/null @@ -1,8 +0,0 @@ -package be.scri.extensions - -import java.io.BufferedWriter - -fun BufferedWriter.writeLn(line: String) { - write(line) - newLine() -} diff --git a/app/src/main/java/be/scri/extensions/CommonsContext.kt b/app/src/main/java/be/scri/extensions/CommonsContext.kt index 85b0fbc5..ddb663d9 100644 --- a/app/src/main/java/be/scri/extensions/CommonsContext.kt +++ b/app/src/main/java/be/scri/extensions/CommonsContext.kt @@ -1,874 +1,9 @@ package be.scri.extensions -import android.annotation.TargetApi -import android.app.Activity -import android.app.NotificationManager -import android.app.role.RoleManager -import android.content.ClipData -import android.content.ClipboardManager -import android.content.ContentValues import android.content.Context -import android.content.Intent -import android.content.pm.PackageManager -import android.content.pm.ShortcutManager -import android.content.res.Configuration -import android.database.Cursor -import android.database.sqlite.SQLiteException -import android.graphics.BitmapFactory -import android.graphics.Point -import android.media.MediaMetadataRetriever -import android.media.RingtoneManager -import android.net.Uri -import android.os.Build -import android.os.Handler -import android.os.Looper -import android.provider.BaseColumns -import android.provider.BlockedNumberContract.BlockedNumbers -import android.provider.ContactsContract.CommonDataKinds.BaseTypes -import android.provider.ContactsContract.CommonDataKinds.Phone -import android.provider.MediaStore.Audio -import android.provider.MediaStore.MediaColumns -import android.provider.OpenableColumns -import android.provider.Settings -import android.telecom.TelecomManager -import android.telephony.PhoneNumberUtils -import android.util.Log -import android.view.View -import android.view.WindowManager -import android.widget.Toast -import androidx.annotation.RequiresApi -import androidx.exifinterface.media.ExifInterface -import androidx.loader.content.CursorLoader -import be.scri.R import be.scri.helpers.BaseConfig -import be.scri.helpers.DAY_SECONDS -import be.scri.helpers.FONT_SIZE_LARGE -import be.scri.helpers.FONT_SIZE_MEDIUM -import be.scri.helpers.FONT_SIZE_SMALL -import be.scri.helpers.FRIDAY_BIT -import be.scri.helpers.HOUR_SECONDS -import be.scri.helpers.MINUTE_SECONDS -import be.scri.helpers.MONDAY_BIT -import be.scri.helpers.MONTH_SECONDS -import be.scri.helpers.MyContentProvider import be.scri.helpers.PREFS_KEY -import be.scri.helpers.SATURDAY_BIT -import be.scri.helpers.SUNDAY_BIT -import be.scri.helpers.THURSDAY_BIT -import be.scri.helpers.TIME_FORMAT_12 -import be.scri.helpers.TIME_FORMAT_24 -import be.scri.helpers.TUESDAY_BIT -import be.scri.helpers.WEDNESDAY_BIT -import be.scri.helpers.WEEK_SECONDS -import be.scri.helpers.YEAR_SECONDS -import be.scri.helpers.YOUR_ALARM_SOUNDS_MIN_ID -import be.scri.helpers.ensureBackgroundThread -import be.scri.helpers.isMarshmallowPlus -import be.scri.helpers.isNougatPlus -import be.scri.helpers.isOnMainThread -import be.scri.helpers.isQPlus -import be.scri.helpers.proPackages -import be.scri.models.AlarmSound -import be.scri.models.BlockedNumber -import com.google.gson.Gson -import com.google.gson.reflect.TypeToken -import java.io.File -import java.text.SimpleDateFormat -import java.util.Date -import java.util.Locale fun Context.getSharedPrefs() = getSharedPreferences(PREFS_KEY, Context.MODE_PRIVATE) -val Context.isRTLLayout: Boolean get() = resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL - -val Context.areSystemAnimationsEnabled: Boolean get() = Settings.Global.getFloat(contentResolver, Settings.Global.ANIMATOR_DURATION_SCALE, 0f) > 0f - -fun Context.toast( - id: Int, - length: Int = Toast.LENGTH_SHORT, -) { - toast(getString(id), length) -} - -fun Context.toast( - msg: String, - length: Int = Toast.LENGTH_SHORT, -) { - try { - val showToast = { doToast(this, msg, length) } - if (isOnMainThread()) { - showToast() - } else { - Handler(Looper.getMainLooper()).post(showToast) - } - } catch (e: IllegalArgumentException) { - Log.e("ToastError", "Invalid argument while showing toast: ${e.message}", e) - } -} - -private fun doToast( - context: Context, - message: String, - length: Int, -) { - if (context is Activity) { - if (!context.isFinishing && !context.isDestroyed) { - Toast.makeText(context, message, length).show() - } - } else { - Toast.makeText(context, message, length).show() - } -} - -fun Context.showErrorToast( - msg: String, - length: Int = Toast.LENGTH_LONG, -) { - toast(String.format(getString(R.string.an_error_occurred), msg), length) -} - -fun Context.showErrorToast( - exception: Exception, - length: Int = Toast.LENGTH_LONG, -) { - showErrorToast(exception.toString(), length) -} - val Context.baseConfig: BaseConfig get() = BaseConfig.newInstance(this) -val Context.sdCardPath: String get() = baseConfig.sdCardPath -val Context.internalStoragePath: String get() = baseConfig.internalStoragePath -val Context.otgPath: String get() = baseConfig.otgPath - -fun Context.queryCursor( - uri: Uri, - projection: Array, - selection: String? = null, - selectionArgs: Array? = null, - sortOrder: String? = null, - showErrors: Boolean = false, - callback: (cursor: Cursor) -> Unit, -) { - try { - val cursor: Cursor? = contentResolver.query(uri, projection, selection, selectionArgs, sortOrder) - cursor?.use { - if (it.moveToFirst()) { - do { - callback(it) - } while (it.moveToNext()) - } else { - Log.w("QueryCursor", "Cursor is empty for URI: $uri") - } - } ?: run { - Log.w("QueryCursor", "Cursor is null for URI: $uri") - } - } catch (e: SecurityException) { - Log.e("QueryCursor", "SecurityException while querying URI: $uri", e) - if (showErrors) { - showErrorToast(e) - } - } catch (e: IllegalArgumentException) { - Log.e("QueryCursor", "IllegalArgumentException: ${e.message}", e) - if (showErrors) { - showErrorToast(e) - } - } -} - -fun Context.getFilenameFromUri(uri: Uri): String = - if (uri.scheme == "file") { - File(uri.toString()).name - } else { - getFilenameFromContentUri(uri) ?: uri.lastPathSegment ?: "" - } - -fun Context.getMimeTypeFromUri(uri: Uri): String { - var mimetype = uri.path?.getMimeType() ?: "" - if (mimetype.isEmpty()) { - try { - mimetype = contentResolver.getType(uri) ?: "" - } catch (e: IllegalStateException) { - } - } - return mimetype -} - -fun Context.getFilenameFromContentUri(uri: Uri): String? { - val projection = arrayOf(OpenableColumns.DISPLAY_NAME) - - return try { - val cursor: Cursor? = contentResolver.query(uri, projection, null, null, null) - cursor?.use { - if (it.moveToFirst()) { - it.getString(it.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME)) - } else { - Log.w("ContentUriFilename", "Cursor is empty for URI: $uri") - null - } - } ?: run { - Log.w("ContentUriFilename", "Cursor is null for URI: $uri") - null - } - } catch (e: SecurityException) { - Log.e("ContentUriFilename", "SecurityException while accessing URI: $uri", e) - null - } catch (e: IllegalArgumentException) { - Log.e("ContentUriFilename", "IllegalArgumentException: ${e.message}", e) - null - } -} - -fun Context.getSizeFromContentUri(uri: Uri): Long { - val projection = arrayOf(OpenableColumns.SIZE) - return try { - val cursor: Cursor? = contentResolver.query(uri, projection, null, null, null) - cursor?.use { - if (cursor.moveToFirst()) { - cursor.getLong(cursor.getColumnIndexOrThrow(OpenableColumns.SIZE)) - } else { - Log.w("ContentUriSize", "Cursor is empty for URI: $uri") - 0L - } - } ?: run { - Log.w("ContentUriSize", "Cursor is null for URI: $uri") - 0L - } - } catch (e: SecurityException) { - Log.e("ContentUriSize", "SecurityException while accessing URI: $uri", e) - 0L - } catch (e: IllegalArgumentException) { - Log.e("ContentUriSize", "IllegalArgumentException: ${e.message}", e) - 0L - } -} - -fun Context.getMyContentProviderCursorLoader() = CursorLoader(this, MyContentProvider.MY_CONTENT_URI, null, null, null, null) - -fun Context.getCurrentFormattedDateTime(): String { - val simpleDateFormat = SimpleDateFormat("yyyy_MM_dd_HH_mm_ss", Locale.getDefault()) - return simpleDateFormat.format(Date(System.currentTimeMillis())) -} - -fun Context.updateSDCardPath() { - ensureBackgroundThread { - val oldPath = baseConfig.sdCardPath - baseConfig.sdCardPath = getSDCardPath() - if (oldPath != baseConfig.sdCardPath) { - baseConfig.sdTreeUri = "" - } - } -} - -fun Context.getUriMimeType( - path: String, - newUri: Uri, -): String { - var mimeType = path.getMimeType() - if (mimeType.isEmpty()) { - mimeType = getMimeTypeFromUri(newUri) - } - return mimeType -} - -fun Context.isThankYouInstalled() = isPackageInstalled("com.simplemobiletools.thankyou") - -fun Context.isOrWasThankYouInstalled(): Boolean = - when { - baseConfig.hadThankYouInstalled -> true - isThankYouInstalled() -> { - baseConfig.hadThankYouInstalled = true - true - } - else -> false - } - -fun Context.isAProApp() = packageName.startsWith("be.scri.") && packageName.removeSuffix(".debug").endsWith(".pro") - -fun Context.getCustomizeColorsString(): String { - val textId = - if (isOrWasThankYouInstalled()) { - R.string.customize_colors - } else { - R.string.customize_colors_locked - } - - return getString(textId) -} - -fun Context.isPackageInstalled(pkgName: String): Boolean = - try { - packageManager.getPackageInfo(pkgName, 0) - true - } catch (e: PackageManager.NameNotFoundException) { - Log.e("PackageCheck", "Package not found: $pkgName", e) - false - } - -// format day bits to strings like "Mon, Tue, Wed" -fun Context.getSelectedDaysString(bitMask: Int): String { - val dayBits = arrayListOf(MONDAY_BIT, TUESDAY_BIT, WEDNESDAY_BIT, THURSDAY_BIT, FRIDAY_BIT, SATURDAY_BIT, SUNDAY_BIT) - val weekDays = resources.getStringArray(R.array.week_days_short).toList() as ArrayList - - if (baseConfig.isSundayFirst) { - dayBits.moveLastItemToFront() - weekDays.moveLastItemToFront() - } - - var days = "" - dayBits.forEachIndexed { index, bit -> - if (bitMask and bit != 0) { - days += "${weekDays[index]}, " - } - } - return days.trim().trimEnd(',') -} - -fun Context.formatMinutesToTimeString(totalMinutes: Int) = formatSecondsToTimeString(totalMinutes * 60) - -fun Context.formatSecondsToTimeString(totalSeconds: Int): String { - val days = totalSeconds / DAY_SECONDS - val hours = (totalSeconds % DAY_SECONDS) / HOUR_SECONDS - val minutes = (totalSeconds % HOUR_SECONDS) / MINUTE_SECONDS - val seconds = totalSeconds % MINUTE_SECONDS - val timesString = StringBuilder() - if (days > 0) { - val daysString = String.format(resources.getQuantityString(R.plurals.days, days, days)) - timesString.append("$daysString, ") - } - - if (hours > 0) { - val hoursString = String.format(resources.getQuantityString(R.plurals.hours, hours, hours)) - timesString.append("$hoursString, ") - } - - if (minutes > 0) { - val minutesString = String.format(resources.getQuantityString(R.plurals.minutes, minutes, minutes)) - timesString.append("$minutesString, ") - } - - if (seconds > 0) { - val secondsString = String.format(resources.getQuantityString(R.plurals.seconds, seconds, seconds)) - timesString.append(secondsString) - } - - var result = timesString.toString().trim().trimEnd(',') - if (result.isEmpty()) { - result = String.format(resources.getQuantityString(R.plurals.minutes, 0, 0)) - } - return result -} - -fun Context.getFormattedMinutes( - minutes: Int, - showBefore: Boolean = true, -) = getFormattedSeconds(if (minutes == -1) minutes else minutes * 60, showBefore) - -fun Context.getFormattedSeconds( - seconds: Int, - showBefore: Boolean = true, -) = when (seconds) { - -1 -> getString(R.string.no_reminder) - 0 -> getString(R.string.at_start) - else -> { - when { - seconds < 0 && seconds > -60 * 60 * 24 -> { - val minutes = -seconds / 60 - getString(R.string.during_day_at).format(minutes / 60, minutes % 60) - } - seconds % YEAR_SECONDS == 0 -> { - val base = if (showBefore) R.plurals.years_before else R.plurals.by_years - resources.getQuantityString(base, seconds / YEAR_SECONDS, seconds / YEAR_SECONDS) - } - seconds % MONTH_SECONDS == 0 -> { - val base = if (showBefore) R.plurals.months_before else R.plurals.by_months - resources.getQuantityString(base, seconds / MONTH_SECONDS, seconds / MONTH_SECONDS) - } - seconds % WEEK_SECONDS == 0 -> { - val base = if (showBefore) R.plurals.weeks_before else R.plurals.by_weeks - resources.getQuantityString(base, seconds / WEEK_SECONDS, seconds / WEEK_SECONDS) - } - seconds % DAY_SECONDS == 0 -> { - val base = if (showBefore) R.plurals.days_before else R.plurals.by_days - resources.getQuantityString(base, seconds / DAY_SECONDS, seconds / DAY_SECONDS) - } - seconds % HOUR_SECONDS == 0 -> { - val base = if (showBefore) R.plurals.hours_before else R.plurals.by_hours - resources.getQuantityString(base, seconds / HOUR_SECONDS, seconds / HOUR_SECONDS) - } - seconds % MINUTE_SECONDS == 0 -> { - val base = if (showBefore) R.plurals.minutes_before else R.plurals.by_minutes - resources.getQuantityString(base, seconds / MINUTE_SECONDS, seconds / MINUTE_SECONDS) - } - else -> { - val base = if (showBefore) R.plurals.seconds_before else R.plurals.by_seconds - resources.getQuantityString(base, seconds, seconds) - } - } - } -} - -fun Context.getDefaultAlarmTitle(type: Int): String { - val alarmString = getString(R.string.alarm) - return try { - RingtoneManager.getRingtone(this, RingtoneManager.getDefaultUri(type))?.getTitle(this) ?: alarmString - } catch (e: SecurityException) { - Log.e("RingtoneError", "SecurityException: ${e.message}", e) - alarmString - } catch (e: IllegalArgumentException) { - Log.e("RingtoneError", "IllegalArgumentException: ${e.message}", e) - alarmString - } -} - -fun Context.getDefaultAlarmSound(type: Int) = AlarmSound(0, getDefaultAlarmTitle(type), RingtoneManager.getDefaultUri(type).toString()) - -fun Context.grantReadUriPermission(uriString: String) { - try { - // ensure custom reminder sounds play well - grantUriPermission("com.android.systemui", Uri.parse(uriString), Intent.FLAG_GRANT_READ_URI_PERMISSION) - } catch (ignored: Exception) { - } -} - -fun Context.storeNewYourAlarmSound(resultData: Intent): AlarmSound { - val uri = resultData.data - var filename = getFilenameFromUri(uri!!) - if (filename.isEmpty()) { - filename = getString(R.string.alarm) - } - - val token = object : TypeToken>() {}.type - val yourAlarmSounds = - Gson().fromJson>(baseConfig.yourAlarmSounds, token) - ?: ArrayList() - val newAlarmSoundId = (yourAlarmSounds.maxByOrNull { it.id }?.id ?: YOUR_ALARM_SOUNDS_MIN_ID) + 1 - val newAlarmSound = AlarmSound(newAlarmSoundId, filename, uri.toString()) - if (yourAlarmSounds.firstOrNull { it.uri == uri.toString() } == null) { - yourAlarmSounds.add(newAlarmSound) - } - - baseConfig.yourAlarmSounds = Gson().toJson(yourAlarmSounds) - - val takeFlags = Intent.FLAG_GRANT_READ_URI_PERMISSION - contentResolver.takePersistableUriPermission(uri, takeFlags) - - return newAlarmSound -} - -@RequiresApi(Build.VERSION_CODES.N) -fun Context.saveImageRotation( - path: String, - degrees: Int, -): Boolean { - if (!needsStupidWritePermissions(path)) { - saveExifRotation(ExifInterface(path), degrees) - return true - } else if (isNougatPlus()) { - val documentFile = getSomeDocumentFile(path) - if (documentFile != null) { - val parcelFileDescriptor = contentResolver.openFileDescriptor(documentFile.uri, "rw") - val fileDescriptor = parcelFileDescriptor!!.fileDescriptor - saveExifRotation(ExifInterface(fileDescriptor), degrees) - return true - } - } - return false -} - -fun Context.saveExifRotation( - exif: ExifInterface, - degrees: Int, -) { - val orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL) - val orientationDegrees = (orientation.degreesFromOrientation() + degrees) % 360 - exif.setAttribute(ExifInterface.TAG_ORIENTATION, orientationDegrees.orientationFromDegrees()) - exif.saveAttributes() -} - -fun Context.getLaunchIntent() = packageManager.getLaunchIntentForPackage(baseConfig.appId) - -fun Context.getCanAppBeUpgraded() = proPackages.contains(baseConfig.appId.removeSuffix(".debug").removePrefix("com.simplemobiletools.")) - -fun Context.getProUrl() = "https://play.google.com/store/apps/details?id=${baseConfig.appId.removeSuffix(".debug")}.pro" - -fun Context.getTimeFormat() = if (baseConfig.use24HourFormat) TIME_FORMAT_24 else TIME_FORMAT_12 - -fun Context.getResolution(path: String): Point? = - if (path.isImageFast() || path.isImageSlow()) { - getImageResolution(path) - } else if (path.isVideoFast() || path.isVideoSlow()) { - getVideoResolution(path) - } else { - null - } - -fun Context.getImageResolution(path: String): Point? { - val options = BitmapFactory.Options() - options.inJustDecodeBounds = true - if (isRestrictedSAFOnlyRoot(path)) { - BitmapFactory.decodeStream(contentResolver.openInputStream(getAndroidSAFUri(path)), null, options) - } else { - BitmapFactory.decodeFile(path, options) - } - - val width = options.outWidth - val height = options.outHeight - return if (width > 0 && height > 0) { - Point(options.outWidth, options.outHeight) - } else { - null - } -} - -fun Context.getVideoResolution(path: String): Point? { - var point = - try { - val retriever = MediaMetadataRetriever() - if (isRestrictedSAFOnlyRoot(path)) { - retriever.setDataSource(this, getAndroidSAFUri(path)) - } else { - retriever.setDataSource(path) - } - - val width = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH)!!.toInt() - val height = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT)!!.toInt() - Point(width, height) - } catch (ignored: Exception) { - null - } - - if (point == null && path.startsWith("content://", true)) { - try { - val fd = contentResolver.openFileDescriptor(Uri.parse(path), "r")?.fileDescriptor - val retriever = MediaMetadataRetriever() - retriever.setDataSource(fd) - val width = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH)!!.toInt() - val height = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT)!!.toInt() - point = Point(width, height) - } catch (ignored: Exception) { - } - } - - return point -} - -fun Context.getDuration(path: String): Int? { - val projection = - arrayOf( - MediaColumns.DURATION, - ) - - val uri = getFileUri(path) - val selection = if (path.startsWith("content://")) "${BaseColumns._ID} = ?" else "${MediaColumns.DATA} = ?" - val selectionArgs = if (path.startsWith("content://")) arrayOf(path.substringAfterLast("/")) else arrayOf(path) - - try { - val cursor = contentResolver.query(uri, projection, selection, selectionArgs, null) - cursor?.use { - if (cursor.moveToFirst()) { - return Math.round(cursor.getIntValue(MediaColumns.DURATION) / 1000.toDouble()).toInt() - } - } - } catch (ignored: Exception) { - } - - return try { - val retriever = MediaMetadataRetriever() - retriever.setDataSource(path) - Math.round(retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)!!.toInt() / 1000f) - } catch (ignored: Exception) { - null - } -} - -fun Context.getTitle(path: String): String? { - val projection = - arrayOf( - MediaColumns.TITLE, - ) - - val uri = getFileUri(path) - val selection = if (path.startsWith("content://")) "${BaseColumns._ID} = ?" else "${MediaColumns.DATA} = ?" - val selectionArgs = if (path.startsWith("content://")) arrayOf(path.substringAfterLast("/")) else arrayOf(path) - - try { - val cursor = contentResolver.query(uri, projection, selection, selectionArgs, null) - cursor?.use { - if (cursor.moveToFirst()) { - return cursor.getStringValue(MediaColumns.TITLE) - } - } - } catch (ignored: Exception) { - } - - return try { - val retriever = MediaMetadataRetriever() - retriever.setDataSource(path) - retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE) - } catch (ignored: Exception) { - null - } -} - -fun Context.getArtist(path: String): String? { - val projection = - arrayOf( - Audio.Media.ARTIST, - ) - - val uri = getFileUri(path) - val selection = if (path.startsWith("content://")) "${BaseColumns._ID} = ?" else "${MediaColumns.DATA} = ?" - val selectionArgs = if (path.startsWith("content://")) arrayOf(path.substringAfterLast("/")) else arrayOf(path) - - try { - val cursor = contentResolver.query(uri, projection, selection, selectionArgs, null) - cursor?.use { - if (cursor.moveToFirst()) { - return cursor.getStringValue(Audio.Media.ARTIST) - } - } - } catch (ignored: Exception) { - } - - return try { - val retriever = MediaMetadataRetriever() - retriever.setDataSource(path) - retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ARTIST) - } catch (ignored: Exception) { - null - } -} - -fun Context.getAlbum(path: String): String? { - val projection = - arrayOf( - Audio.Media.ALBUM, - ) - - val uri = getFileUri(path) - val selection = if (path.startsWith("content://")) "${BaseColumns._ID} = ?" else "${MediaColumns.DATA} = ?" - val selectionArgs = if (path.startsWith("content://")) arrayOf(path.substringAfterLast("/")) else arrayOf(path) - - try { - val cursor = contentResolver.query(uri, projection, selection, selectionArgs, null) - cursor?.use { - if (cursor.moveToFirst()) { - return cursor.getStringValue(Audio.Media.ALBUM) - } - } - } catch (ignored: Exception) { - } - - return try { - val retriever = MediaMetadataRetriever() - retriever.setDataSource(path) - retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUM) - } catch (ignored: Exception) { - null - } -} - -fun Context.getMediaStoreLastModified(path: String): Long { - val projection = - arrayOf( - MediaColumns.DATE_MODIFIED, - ) - - val uri = getFileUri(path) - val selection = "${BaseColumns._ID} = ?" - val selectionArgs = arrayOf(path.substringAfterLast("/")) - - try { - val cursor = contentResolver.query(uri, projection, selection, selectionArgs, null) - cursor?.use { - if (cursor.moveToFirst()) { - return cursor.getLongValue(MediaColumns.DATE_MODIFIED) * 1000 - } - } - } catch (ignored: Exception) { - } - return 0 -} - -fun Context.getStringsPackageName() = getString(R.string.package_name) - -fun Context.getFontSizeText() = - getString( - when (baseConfig.fontSize) { - FONT_SIZE_SMALL -> R.string.small - FONT_SIZE_MEDIUM -> R.string.medium - FONT_SIZE_LARGE -> R.string.large - else -> R.string.extra_large - }, - ) - -fun Context.getTextSize() = - when (baseConfig.fontSize) { - FONT_SIZE_SMALL -> resources.getDimension(R.dimen.smaller_text_size) - FONT_SIZE_MEDIUM -> resources.getDimension(R.dimen.bigger_text_size) - FONT_SIZE_LARGE -> resources.getDimension(R.dimen.big_text_size) - else -> resources.getDimension(R.dimen.extra_big_text_size) - } - -val Context.telecomManager: TelecomManager get() = getSystemService(Context.TELECOM_SERVICE) as TelecomManager -val Context.windowManager: WindowManager get() = getSystemService(Context.WINDOW_SERVICE) as WindowManager -val Context.notificationManager: NotificationManager get() = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager -val Context.shortcutManager: ShortcutManager get() = getSystemService(ShortcutManager::class.java) as ShortcutManager - -val Context.portrait get() = resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT -val Context.navigationBarRight: Boolean get() = usableScreenSize.x < realScreenSize.x && usableScreenSize.x > usableScreenSize.y -val Context.navigationBarBottom: Boolean get() = usableScreenSize.y < realScreenSize.y -val Context.navigationBarHeight: Int get() = if (navigationBarBottom && navigationBarSize.y != usableScreenSize.y) navigationBarSize.y else 0 -val Context.navigationBarWidth: Int get() = if (navigationBarRight) navigationBarSize.x else 0 - -val Context.navigationBarSize: Point - get() = - when { - navigationBarRight -> Point(newNavigationBarHeight, usableScreenSize.y) - navigationBarBottom -> Point(usableScreenSize.x, newNavigationBarHeight) - else -> Point() - } - -val Context.newNavigationBarHeight: Int - get() { - var navigationBarHeight = 0 - val resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android") - if (resourceId > 0) { - navigationBarHeight = resources.getDimensionPixelSize(resourceId) - } - return navigationBarHeight - } - -val Context.statusBarHeight: Int - get() { - var statusBarHeight = 0 - val resourceId = resources.getIdentifier("status_bar_height", "dimen", "android") - if (resourceId > 0) { - statusBarHeight = resources.getDimensionPixelSize(resourceId) - } - return statusBarHeight - } - -val Context.actionBarHeight: Int - get() { - val styledAttributes = theme.obtainStyledAttributes(intArrayOf(android.R.attr.actionBarSize)) - val actionBarHeight = styledAttributes.getDimension(0, 0f) - styledAttributes.recycle() - return actionBarHeight.toInt() - } - -val Context.usableScreenSize: Point - get() { - val size = Point() - windowManager.defaultDisplay.getSize(size) - return size - } - -val Context.realScreenSize: Point - get() { - val size = Point() - windowManager.defaultDisplay.getRealSize(size) - return size - } - -fun Context.getCornerRadius() = resources.getDimension(R.dimen.rounded_corner_radius_small) - -// we need the Default Dialer functionality only in Simple Dialer and in Simple Contacts for now -@TargetApi(Build.VERSION_CODES.M) -fun Context.isDefaultDialer(): Boolean = - if (!packageName.startsWith("com.simplemobiletools.contacts") && !packageName.startsWith("com.simplemobiletools.dialer")) { - true - } else if ((packageName.startsWith("com.simplemobiletools.contacts") || packageName.startsWith("com.simplemobiletools.dialer")) && isQPlus()) { - val roleManager = getSystemService(RoleManager::class.java) - roleManager!!.isRoleAvailable(RoleManager.ROLE_DIALER) && roleManager.isRoleHeld(RoleManager.ROLE_DIALER) - } else { - isMarshmallowPlus() && telecomManager.defaultDialerPackage == packageName - } - -@TargetApi(Build.VERSION_CODES.N) -fun Context.getBlockedNumbers(): ArrayList { - val blockedNumbers = ArrayList() - if (!isNougatPlus() || !isDefaultDialer()) { - return blockedNumbers - } - - val uri = BlockedNumbers.CONTENT_URI - val projection = - arrayOf( - BlockedNumbers.COLUMN_ID, - BlockedNumbers.COLUMN_ORIGINAL_NUMBER, - BlockedNumbers.COLUMN_E164_NUMBER, - ) - - queryCursor(uri, projection) { cursor -> - val id = cursor.getLongValue(BlockedNumbers.COLUMN_ID) - val number = cursor.getStringValue(BlockedNumbers.COLUMN_ORIGINAL_NUMBER) ?: "" - val normalizedNumber = cursor.getStringValue(BlockedNumbers.COLUMN_E164_NUMBER) ?: number - val comparableNumber = normalizedNumber.trimToComparableNumber() - val blockedNumber = BlockedNumber(id, number, normalizedNumber, comparableNumber) - blockedNumbers.add(blockedNumber) - } - - return blockedNumbers -} - -@TargetApi(Build.VERSION_CODES.N) -fun Context.addBlockedNumber(number: String) { - ContentValues().apply { - put(BlockedNumbers.COLUMN_ORIGINAL_NUMBER, number) - put(BlockedNumbers.COLUMN_E164_NUMBER, PhoneNumberUtils.normalizeNumber(number)) - try { - contentResolver.insert(BlockedNumbers.CONTENT_URI, this) - } catch (e: SecurityException) { - showErrorToast("Permission denied: ${e.message}") - } catch (e: SQLiteException) { - showErrorToast("Database error: ${e.message}") - } - } -} - -@TargetApi(Build.VERSION_CODES.N) -fun Context.deleteBlockedNumber(number: String) { - val selection = "${BlockedNumbers.COLUMN_ORIGINAL_NUMBER} = ?" - val selectionArgs = arrayOf(number) - contentResolver.delete(BlockedNumbers.CONTENT_URI, selection, selectionArgs) -} - -fun Context.isNumberBlocked( - number: String, - blockedNumbers: ArrayList = getBlockedNumbers(), -): Boolean { - if (!isNougatPlus()) { - return false - } - - val numberToCompare = number.trimToComparableNumber() - return blockedNumbers.map { it.numberToCompare }.contains(numberToCompare) || blockedNumbers.map { it.number }.contains(numberToCompare) -} - -fun Context.copyToClipboard(text: String) { - val clip = ClipData.newPlainText(getString(R.string.simple_commons), text) - (getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager).setPrimaryClip(clip) - val toastText = String.format(getString(R.string.value_copied_to_clipboard_show), text) - toast(toastText) -} - -fun Context.getPhoneNumberTypeText( - type: Int, - label: String, -): String = - if (type == BaseTypes.TYPE_CUSTOM) { - label - } else { - getString( - when (type) { - Phone.TYPE_MOBILE -> R.string.mobile - Phone.TYPE_HOME -> R.string.home - Phone.TYPE_WORK -> R.string.work - Phone.TYPE_MAIN -> R.string.main_number - Phone.TYPE_FAX_WORK -> R.string.work_fax - Phone.TYPE_FAX_HOME -> R.string.home_fax - Phone.TYPE_PAGER -> R.string.pager - else -> R.string.other - }, - ) - } diff --git a/app/src/main/java/be/scri/extensions/Context.kt b/app/src/main/java/be/scri/extensions/Context.kt index 5f9194ae..47925449 100644 --- a/app/src/main/java/be/scri/extensions/Context.kt +++ b/app/src/main/java/be/scri/extensions/Context.kt @@ -1,26 +1,12 @@ package be.scri.extensions -import android.content.ClipboardManager import android.content.Context import android.graphics.Color import be.scri.R -import be.scri.databases.ClipsDatabase import be.scri.helpers.Config -import be.scri.interfaces.ClipsDao val Context.config: Config get() = Config.newInstance(applicationContext) -val Context.clipsDB: ClipsDao get() = ClipsDatabase.getInstance(applicationContext).ClipsDao() - -fun Context.getCurrentClip(): String? { - val clipboardManager = (getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager) - return clipboardManager.primaryClip - ?.getItemAt(0) - ?.text - ?.trim() - ?.toString() -} - fun Context.getStrokeColor(): Int = if (config.isUsingSystemTheme) { if (isUsingSystemDarkTheme()) { diff --git a/app/src/main/java/be/scri/extensions/ContextStorage.kt b/app/src/main/java/be/scri/extensions/ContextStorage.kt deleted file mode 100644 index 21b4d854..00000000 --- a/app/src/main/java/be/scri/extensions/ContextStorage.kt +++ /dev/null @@ -1,786 +0,0 @@ -package be.scri.extensions - -import android.content.ContentUris -import android.content.Context -import android.content.Intent -import android.database.CursorIndexOutOfBoundsException -import android.media.MediaScannerConnection -import android.net.Uri -import android.os.Environment -import android.provider.DocumentsContract -import android.provider.DocumentsContract.Document -import android.provider.MediaStore -import android.provider.MediaStore.Audio -import android.provider.MediaStore.Files -import android.provider.MediaStore.Images -import android.provider.MediaStore.MediaColumns -import android.provider.MediaStore.Video -import android.text.TextUtils -import android.util.Log -import androidx.core.net.toUri -import androidx.documentfile.provider.DocumentFile -import be.scri.R -import be.scri.helpers.EXTERNAL_STORAGE_PROVIDER_AUTHORITY -import be.scri.helpers.ExternalStorageProviderHack -import be.scri.helpers.SD_OTG_PATTERN -import be.scri.helpers.SD_OTG_SHORT -import be.scri.helpers.ensureBackgroundThread -import be.scri.helpers.isMarshmallowPlus -import be.scri.helpers.isRPlus -import be.scri.models.FileDirItem -import java.io.File -import java.io.FileInputStream -import java.io.FileNotFoundException -import java.io.IOException -import java.io.InputStream -import java.util.Collections -import java.util.regex.Pattern - -private const val ANDROID_DATA_DIR = "/Android/data/" -private const val ANDROID_OBB_DIR = "/Android/obb/" -val DIRS_ACCESSIBLE_ONLY_WITH_SAF = listOf(ANDROID_DATA_DIR, ANDROID_OBB_DIR) -val Context.recycleBinPath: String get() = filesDir.absolutePath - -// http://stackoverflow.com/a/40582634/1967672 -fun Context.getSDCardPath(): String { - val directories = - getStorageDirectories().filter { - !it.equals(getInternalStoragePath()) && - !it.equals( - "/storage/emulated/0", - true, - ) && - (baseConfig.otgPartition.isEmpty() || !it.endsWith(baseConfig.otgPartition)) - } - - val fullSDpattern = Pattern.compile(SD_OTG_PATTERN) - var sdCardPath = - directories.firstOrNull { fullSDpattern.matcher(it).matches() } - ?: directories.firstOrNull { !physicalPaths.contains(it.toLowerCase()) } ?: "" - - // on some devices no method retrieved any SD card path, so test if its not sdcard1 by any chance. It happened on an Android 5.1 - if (sdCardPath.trimEnd('/').isEmpty()) { - val file = File("/storage/sdcard1") - if (file.exists()) { - return file.absolutePath - } - - sdCardPath = directories.firstOrNull() ?: "" - } - - if (sdCardPath.isEmpty()) { - val sdPattern = Pattern.compile(SD_OTG_SHORT) - try { - File("/storage").listFiles()?.forEach { - if (sdPattern.matcher(it.name).matches()) { - sdCardPath = "/storage/${it.name}" - } - } - } catch (e: SecurityException) { - Log.e("StorageAccess", "Permission denied: ${e.message}") - } - } - - val finalPath = sdCardPath.trimEnd('/') - baseConfig.sdCardPath = finalPath - return finalPath -} - -fun Context.getStorageDirectories(): Array { - val paths = HashSet() - val rawExternalStorage = System.getenv("EXTERNAL_STORAGE") - val rawSecondaryStoragesStr = System.getenv("SECONDARY_STORAGE") - val rawEmulatedStorageTarget = System.getenv("EMULATED_STORAGE_TARGET") - if (TextUtils.isEmpty(rawEmulatedStorageTarget)) { - if (isMarshmallowPlus()) { - getExternalFilesDirs(null) - .filterNotNull() - .map { it.absolutePath } - .mapTo(paths) { it.substring(0, it.indexOf("Android/data")) } - } else { - if (TextUtils.isEmpty(rawExternalStorage)) { - paths.addAll(physicalPaths) - } else { - paths.add(rawExternalStorage!!) - } - } - } else { - val path = Environment.getExternalStorageDirectory().absolutePath - val folders = Pattern.compile("/").split(path) - val lastFolder = folders[folders.size - 1] - var isDigit = false - try { - Integer.valueOf(lastFolder) - isDigit = true - } catch (ignored: NumberFormatException) { - } - - val rawUserId = if (isDigit) lastFolder else "" - if (TextUtils.isEmpty(rawUserId)) { - paths.add(rawEmulatedStorageTarget!!) - } else { - paths.add(rawEmulatedStorageTarget + File.separator + rawUserId) - } - } - - if (!TextUtils.isEmpty(rawSecondaryStoragesStr)) { - val rawSecondaryStorages = rawSecondaryStoragesStr!!.split(File.pathSeparator.toRegex()).dropLastWhile(String::isEmpty).toTypedArray() - Collections.addAll(paths, *rawSecondaryStorages) - } - return paths.map { it.trimEnd('/') }.toTypedArray() -} - -fun Context.getHumanReadablePath(path: String): String = - getString( - when (path) { - "/" -> R.string.root - internalStoragePath -> R.string.internal - otgPath -> R.string.usb - else -> R.string.sd_card - }, - ) - -fun Context.humanizePath(path: String): String { - val trimmedPath = path.trimEnd('/') - val basePath = path.getBasePath(this) - return when (basePath) { - "/" -> "${getHumanReadablePath(basePath)}$trimmedPath" - else -> trimmedPath.replaceFirst(basePath, getHumanReadablePath(basePath)) - } -} - -fun Context.getInternalStoragePath() = - if (File("/storage/emulated/0").exists()) "/storage/emulated/0" else Environment.getExternalStorageDirectory().absolutePath.trimEnd('/') - -fun Context.isPathOnSD(path: String) = sdCardPath.isNotEmpty() && path.startsWith(sdCardPath) - -fun Context.isPathOnOTG(path: String) = otgPath.isNotEmpty() && path.startsWith(otgPath) - -fun Context.isPathOnInternalStorage(path: String) = internalStoragePath.isNotEmpty() && path.startsWith(internalStoragePath) - -fun Context.getSAFOnlyDirs(): List = DIRS_ACCESSIBLE_ONLY_WITH_SAF.map { "$internalStoragePath$it" } - -fun Context.isSAFOnlyRoot(path: String): Boolean = getSAFOnlyDirs().any { "${path.trimEnd('/')}/".startsWith(it) } - -fun Context.isRestrictedSAFOnlyRoot(path: String): Boolean = isRPlus() && isSAFOnlyRoot(path) - -// no need to use DocumentFile if an SD card is set as the default storage -fun Context.needsStupidWritePermissions(path: String) = !isRPlus() && (isPathOnSD(path) || isPathOnOTG(path)) && !isSDCardSetAsDefaultStorage() - -fun Context.isSDCardSetAsDefaultStorage() = sdCardPath.isNotEmpty() && Environment.getExternalStorageDirectory().absolutePath.equals(sdCardPath, true) - -fun Context.hasProperStoredAndroidTreeUri(path: String): Boolean { - val uri = getAndroidTreeUri(path) - val hasProperUri = contentResolver.persistedUriPermissions.any { it.uri.toString() == uri } - if (!hasProperUri) { - storeAndroidTreeUri(path, "") - } - return hasProperUri -} - -fun Context.getAndroidTreeUri(path: String): String = - when { - isPathOnOTG(path) -> if (isAndroidDataDir(path)) baseConfig.otgAndroidDataTreeUri else baseConfig.otgAndroidObbTreeUri - isPathOnSD(path) -> if (isAndroidDataDir(path)) baseConfig.sdAndroidDataTreeUri else baseConfig.sdAndroidObbTreeUri - else -> if (isAndroidDataDir(path)) baseConfig.primaryAndroidDataTreeUri else baseConfig.primaryAndroidObbTreeUri - } - -fun isAndroidDataDir(path: String): Boolean { - val resolvedPath = "${path.trimEnd('/')}/" - return resolvedPath.contains(ANDROID_DATA_DIR) -} - -fun Context.storeAndroidTreeUri( - path: String, - treeUri: String, -) = when { - isPathOnOTG(path) -> if (isAndroidDataDir(path)) baseConfig.otgAndroidDataTreeUri = treeUri else baseConfig.otgAndroidObbTreeUri = treeUri - isPathOnSD(path) -> if (isAndroidDataDir(path)) baseConfig.sdAndroidDataTreeUri = treeUri else baseConfig.otgAndroidObbTreeUri = treeUri - else -> if (isAndroidDataDir(path)) baseConfig.primaryAndroidDataTreeUri = treeUri else baseConfig.primaryAndroidObbTreeUri = treeUri -} - -fun Context.getSAFStorageId(fullPath: String): String = - if (fullPath.startsWith('/')) { - when { - fullPath.startsWith(internalStoragePath) -> "primary" - else -> fullPath.substringAfter("/storage/", "").substringBefore('/') - } - } else { - fullPath.substringBefore(':', "").substringAfterLast('/') - } - -fun Context.createDocumentUriFromRootTree(fullPath: String): Uri { - val storageId = getSAFStorageId(fullPath) - - val relativePath = - when { - fullPath.startsWith(internalStoragePath) -> fullPath.substring(internalStoragePath.length).trim('/') - else -> fullPath.substringAfter(storageId).trim('/') - } - - val treeUri = DocumentsContract.buildTreeDocumentUri(EXTERNAL_STORAGE_PROVIDER_AUTHORITY, "$storageId:") - val documentId = "$storageId:$relativePath" - return DocumentsContract.buildDocumentUriUsingTree(treeUri, documentId) -} - -fun Context.createAndroidDataOrObbPath(fullPath: String): String = - if (isAndroidDataDir(fullPath)) { - fullPath.getBasePath(this).trimEnd('/').plus(ANDROID_DATA_DIR) - } else { - fullPath.getBasePath(this).trimEnd('/').plus(ANDROID_OBB_DIR) - } - -fun Context.createAndroidDataOrObbUri(fullPath: String): Uri { - val path = createAndroidDataOrObbPath(fullPath) - return createDocumentUriFromRootTree(path) -} - -fun Context.getStorageRootIdForAndroidDir(path: String) = - getAndroidTreeUri(path).removeSuffix(if (isAndroidDataDir(path)) "%3AAndroid%2Fdata" else "%3AAndroid%2Fobb").substringAfterLast('/').trimEnd('/') - -fun Context.tryFastDocumentDelete( - path: String, - allowDeleteFolder: Boolean, -): Boolean { - val document = getFastDocumentFile(path) - return if (document?.isFile == true || allowDeleteFolder) { - try { - DocumentsContract.deleteDocument(contentResolver, document?.uri!!) - } catch (e: SecurityException) { - Log.e("DocumentDelete", "Permission denied: ${e.message}") - false - } catch (e: IllegalArgumentException) { - Log.e("DocumentDelete", "Invalid document URI: ${e.message}") - false - } - } else { - false - } -} - -fun Context.getFastDocumentFile(path: String): DocumentFile? { - if (isPathOnOTG(path)) { - return getOTGFastDocumentFile(path) - } - - if (baseConfig.sdCardPath.isEmpty()) { - return null - } - - val relativePath = Uri.encode(path.substring(baseConfig.sdCardPath.length).trim('/')) - val externalPathPart = - baseConfig.sdCardPath - .split("/") - .lastOrNull(String::isNotEmpty) - ?.trim('/') ?: return null - val fullUri = "${baseConfig.sdTreeUri}/document/$externalPathPart%3A$relativePath" - return DocumentFile.fromSingleUri(this, Uri.parse(fullUri)) -} - -fun Context.getOTGFastDocumentFile( - path: String, - otgPathToUse: String? = null, -): DocumentFile? { - if (baseConfig.otgTreeUri.isEmpty()) { - return null - } - - val otgPath = otgPathToUse ?: baseConfig.otgPath - if (baseConfig.otgPartition.isEmpty()) { - baseConfig.otgPartition = - baseConfig.otgTreeUri - .removeSuffix("%3A") - .substringAfterLast('/') - .trimEnd('/') - updateOTGPathFromPartition() - } - - val relativePath = Uri.encode(path.substring(otgPath.length).trim('/')) - val fullUri = "${baseConfig.otgTreeUri}/document/${baseConfig.otgPartition}%3A$relativePath" - return DocumentFile.fromSingleUri(this, Uri.parse(fullUri)) -} - -fun Context.getDocumentFile(path: String): DocumentFile? { - val isOTG = isPathOnOTG(path) - var relativePath = path.substring(if (isOTG) otgPath.length else sdCardPath.length) - if (relativePath.startsWith(File.separator)) { - relativePath = relativePath.substring(1) - } - - return try { - val treeUri = Uri.parse(if (isOTG) baseConfig.otgTreeUri else baseConfig.sdTreeUri) - var document = DocumentFile.fromTreeUri(applicationContext, treeUri) - val parts = relativePath.split("/").filter { it.isNotEmpty() } - for (part in parts) { - document = document?.findFile(part) - } - document - } catch (ignored: Exception) { - null - } -} - -fun Context.getSomeDocumentFile(path: String) = getFastDocumentFile(path) ?: getDocumentFile(path) - -fun Context.scanFileRecursively( - file: File, - callback: (() -> Unit)? = null, -) { - scanFilesRecursively(arrayListOf(file), callback) -} - -fun Context.scanPathRecursively( - path: String, - callback: (() -> Unit)? = null, -) { - scanPathsRecursively(arrayListOf(path), callback) -} - -fun Context.scanFilesRecursively( - files: List, - callback: (() -> Unit)? = null, -) { - val allPaths = ArrayList() - for (file in files) { - allPaths.addAll(getPaths(file)) - } - rescanPaths(allPaths, callback) -} - -fun Context.scanPathsRecursively( - paths: List, - callback: (() -> Unit)? = null, -) { - val allPaths = ArrayList() - for (path in paths) { - allPaths.addAll(getPaths(File(path))) - } - rescanPaths(allPaths, callback) -} - -fun Context.rescanPath( - path: String, - callback: (() -> Unit)? = null, -) { - rescanPaths(arrayListOf(path), callback) -} - -// avoid calling this multiple times in row, it can delete whole folder contents -fun Context.rescanPaths( - paths: List, - callback: (() -> Unit)? = null, -) { - if (paths.isEmpty()) { - callback?.invoke() - return - } - - for (path in paths) { - Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE).apply { - data = Uri.fromFile(File(path)) - sendBroadcast(this) - } - } - - var cnt = paths.size - MediaScannerConnection.scanFile(applicationContext, paths.toTypedArray(), null) { s, uri -> - if (--cnt == 0) { - callback?.invoke() - } - } -} - -fun getPaths(file: File): ArrayList { - val paths = arrayListOf(file.absolutePath) - if (file.isDirectory) { - val files = file.listFiles() ?: return paths - for (curFile in files) { - paths.addAll(getPaths(curFile)) - } - } - return paths -} - -fun Context.getFileUri(path: String) = - when { - path.isImageSlow() -> MediaStore.Images.Media.EXTERNAL_CONTENT_URI - path.isVideoSlow() -> Video.Media.EXTERNAL_CONTENT_URI - path.isAudioSlow() -> Audio.Media.EXTERNAL_CONTENT_URI - else -> Files.getContentUri("external") - } - -// these functions update the mediastore instantly, MediaScannerConnection.scanFileRecursively takes some time to really get applied -fun Context.deleteFromMediaStore( - path: String, - callback: ((needsRescan: Boolean) -> Unit)? = null, -) { - if (getIsPathDirectory(path)) { - callback?.invoke(false) - return - } - - ensureBackgroundThread { - try { - val where = "${MediaColumns.DATA} = ?" - val args = arrayOf(path) - val success = contentResolver.delete(getFileUri(path), where, args) != 1 - callback?.invoke(success) - } catch (ignored: Exception) { - } - callback?.invoke(true) - } -} - -fun Context.getDirectChildrenCount( - rootDocId: String, - treeUri: Uri, - documentId: String, - shouldShowHidden: Boolean, -): Int { - return try { - val projection = arrayOf(Document.COLUMN_DOCUMENT_ID) - val childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(treeUri, documentId) - val rawCursor = contentResolver.query(childrenUri, projection, null, null, null) ?: return 0 - val cursor = ExternalStorageProviderHack.transformQueryResult(rootDocId, childrenUri, rawCursor) - - if (shouldShowHidden) { - cursor.count - } else { - var count = 0 - cursor.use { - while (cursor.moveToNext()) { - val docId = cursor.getStringValue(Document.COLUMN_DOCUMENT_ID) - if (!docId.getFilenameFromPath().startsWith('.') || shouldShowHidden) { - count++ - } - } - } - count - } - } catch (e: SecurityException) { - Log.e("GetChildrenCount", "Permission denied: ${e.message}") - 0 - } catch (e: IllegalArgumentException) { - Log.e("GetChildrenCount", "Invalid argument: ${e.message}") - 0 - } -} - -fun Context.getProperChildrenCount( - rootDocId: String, - treeUri: Uri, - documentId: String, - shouldShowHidden: Boolean, -): Int { - val projection = arrayOf(Document.COLUMN_DOCUMENT_ID, Document.COLUMN_MIME_TYPE) - val childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(treeUri, documentId) - val rawCursor = contentResolver.query(childrenUri, projection, null, null, null)!! - val cursor = ExternalStorageProviderHack.transformQueryResult(rootDocId, childrenUri, rawCursor) - return if (cursor.count > 0) { - var count = 0 - cursor.use { - while (cursor.moveToNext()) { - val docId = cursor.getStringValue(Document.COLUMN_DOCUMENT_ID) - val mimeType = cursor.getStringValue(Document.COLUMN_MIME_TYPE) - if (mimeType == Document.MIME_TYPE_DIR) { - count++ - count += getProperChildrenCount(rootDocId, treeUri, docId, shouldShowHidden) - } else if (!docId.getFilenameFromPath().startsWith('.') || shouldShowHidden) { - count++ - } - } - } - count - } else { - 1 - } -} - -fun Context.getFileSize( - treeUri: Uri, - documentId: String, -): Long { - val projection = arrayOf(Document.COLUMN_SIZE) - val documentUri = DocumentsContract.buildDocumentUriUsingTree(treeUri, documentId) - return contentResolver.query(documentUri, projection, null, null, null)?.use { cursor -> - if (cursor.moveToFirst()) { - cursor.getLongValue(Document.COLUMN_SIZE) - } else { - 0L - } - } ?: 0L -} - -fun Context.createAndroidSAFDocumentId(path: String): String { - val basePath = path.getBasePath(this) - val relativePath = path.substring(basePath.length).trim('/') - val storageId = getStorageRootIdForAndroidDir(path) - return "$storageId:$relativePath" -} - -fun Context.getAndroidSAFUri(path: String): Uri { - val treeUri = getAndroidTreeUri(path).toUri() - val documentId = createAndroidSAFDocumentId(path) - return DocumentsContract.buildDocumentUriUsingTree(treeUri, documentId) -} - -fun Context.getFastAndroidSAFDocument(path: String): DocumentFile? { - val treeUri = getAndroidTreeUri(path) - if (treeUri.isEmpty()) { - return null - } - - val uri = getAndroidSAFUri(path) - return DocumentFile.fromSingleUri(this, uri) -} - -fun Context.createAndroidSAFDirectory(path: String): Boolean = - try { - val treeUri = getAndroidTreeUri(path).toUri() - val parentPath = path.getParentPath() - if (!getDoesFilePathExist(parentPath)) { - createAndroidSAFDirectory(parentPath) - } - val documentId = createAndroidSAFDocumentId(parentPath) - val parentUri = DocumentsContract.buildDocumentUriUsingTree(treeUri, documentId) - DocumentsContract.createDocument(contentResolver, parentUri, Document.MIME_TYPE_DIR, path.getFilenameFromPath()) != null - } catch (e: IllegalStateException) { - showErrorToast(e) - false - } - -fun Context.createAndroidSAFFile(path: String): Boolean = - try { - val treeUri = getAndroidTreeUri(path).toUri() - val parentPath = path.getParentPath() - if (!getDoesFilePathExist(parentPath)) { - createAndroidSAFDirectory(parentPath) - } - - val documentId = createAndroidSAFDocumentId(path.getParentPath()) - val parentUri = DocumentsContract.buildDocumentUriUsingTree(treeUri, documentId) - DocumentsContract.createDocument(contentResolver, parentUri, path.getMimeType(), path.getFilenameFromPath()) != null - } catch (e: IllegalStateException) { - showErrorToast(e) - false - } - -fun Context.getAndroidSAFFileSize(path: String): Long { - val treeUri = getAndroidTreeUri(path).toUri() - val documentId = createAndroidSAFDocumentId(path) - return getFileSize(treeUri, documentId) -} - -fun Context.getAndroidSAFFileCount( - path: String, - countHidden: Boolean, -): Int { - val treeUri = getAndroidTreeUri(path).toUri() - if (treeUri == Uri.EMPTY) { - return 0 - } - - val documentId = createAndroidSAFDocumentId(path) - val rootDocId = getStorageRootIdForAndroidDir(path) - return getProperChildrenCount(rootDocId, treeUri, documentId, countHidden) -} - -fun Context.getAndroidSAFDirectChildrenCount( - path: String, - countHidden: Boolean, -): Int { - val treeUri = getAndroidTreeUri(path).toUri() - if (treeUri == Uri.EMPTY) { - return 0 - } - - val documentId = createAndroidSAFDocumentId(path) - val rootDocId = getStorageRootIdForAndroidDir(path) - return getDirectChildrenCount(rootDocId, treeUri, documentId, countHidden) -} - -fun Context.getAndroidSAFLastModified(path: String): Long { - val treeUri = getAndroidTreeUri(path).toUri() - if (treeUri == Uri.EMPTY) { - return 0L - } - - val documentId = createAndroidSAFDocumentId(path) - val projection = arrayOf(Document.COLUMN_LAST_MODIFIED) - val documentUri = DocumentsContract.buildDocumentUriUsingTree(treeUri, documentId) - return contentResolver.query(documentUri, projection, null, null, null)?.use { cursor -> - if (cursor.moveToFirst()) { - cursor.getLongValue(Document.COLUMN_LAST_MODIFIED) - } else { - 0L - } - } ?: 0L -} - -fun Context.getFileInputStreamSync(path: String): InputStream? = - when { - isRestrictedSAFOnlyRoot(path) -> { - val uri = getAndroidSAFUri(path) - try { - applicationContext.contentResolver.openInputStream(uri) - } catch (e: FileNotFoundException) { - Log.e("GetFileInputStreamSync", "File not found: ${e.message}") - null - } catch (e: SecurityException) { - Log.e("GetFileInputStreamSync", "Permission denied: ${e.message}") - null - } - } - isAccessibleWithSAFSdk30(path) -> { - try { - FileInputStream(File(path)) - } catch (e: SecurityException) { - Log.e("GetFileInputStreamSync", "Permission denied: ${e.message}") - null - } catch (e: IOException) { - val uri = createDocumentUriUsingFirstParentTreeUri(path) - try { - applicationContext.contentResolver.openInputStream(uri) - } catch (e: FileNotFoundException) { - Log.e("GetFileInputStreamSync", "File not found: ${e.message}") - null - } catch (e: SecurityException) { - Log.e("GetFileInputStreamSync", "Permission denied: ${e.message}") - null - } - } - } - isPathOnOTG(path) -> { - val fileDocument = getSomeDocumentFile(path) - try { - applicationContext.contentResolver.openInputStream(fileDocument?.uri!!) - } catch (e: FileNotFoundException) { - Log.e("GetFileInputStreamSync", "File not found: ${e.message}") - null - } catch (e: SecurityException) { - Log.e("GetFileInputStreamSync", "Permission denied: ${e.message}") - null - } - } - else -> { - try { - FileInputStream(File(path)) - } catch (e: FileNotFoundException) { - Log.e("GetFileInputStreamSync", "File not found: ${e.message}") - null - } catch (e: SecurityException) { - Log.e("GetFileInputStreamSync", "Permission denied: ${e.message}") - null - } - } - } - -fun Context.updateOTGPathFromPartition() { - val otgPath = "/storage/${baseConfig.otgPartition}" - baseConfig.otgPath = - if (getOTGFastDocumentFile(otgPath, otgPath)?.exists() == true) { - "/storage/${baseConfig.otgPartition}" - } else { - "/mnt/media_rw/${baseConfig.otgPartition}" - } -} - -fun Context.getDoesFilePathExist( - path: String, - otgPathToUse: String? = null, -): Boolean { - val otgPath = otgPathToUse ?: baseConfig.otgPath - return when { - isRestrictedSAFOnlyRoot(path) -> getFastAndroidSAFDocument(path)?.exists() ?: false - otgPath.isNotEmpty() && path.startsWith(otgPath) -> getOTGFastDocumentFile(path)?.exists() ?: false - else -> File(path).exists() - } -} - -fun Context.getIsPathDirectory(path: String): Boolean = - when { - isRestrictedSAFOnlyRoot(path) -> getFastAndroidSAFDocument(path)?.isDirectory ?: false - isPathOnOTG(path) -> getOTGFastDocumentFile(path)?.isDirectory ?: false - else -> File(path).isDirectory - } - -// avoid these being set as SD card paths -private val physicalPaths = - arrayListOf( - "/storage/sdcard1", // Motorola Xoom - "/storage/extsdcard", // Samsung SGS3 - "/storage/sdcard0/external_sdcard", // User request - "/mnt/extsdcard", - "/mnt/sdcard/external_sd", // Samsung galaxy family - "/mnt/external_sd", - "/mnt/media_rw/sdcard1", // 4.4.2 on CyanogenMod S3 - "/removable/microsd", // Asus transformer prime - "/mnt/emmc", - "/storage/external_SD", // LG - "/storage/ext_sd", // HTC One Max - "/storage/removable/sdcard1", // Sony Xperia Z1 - "/data/sdext", - "/data/sdext2", - "/data/sdext3", - "/data/sdext4", - "/sdcard1", // Sony Xperia Z - "/sdcard2", // HTC One M8s - "/storage/usbdisk0", - "/storage/usbdisk1", - "/storage/usbdisk2", - ) - -// Convert paths like /storage/emulated/0/Pictures/Screenshots/first.jpg to content://media/external/images/media/131799 -// so that we can refer to the file in the MediaStore. -// If we found no mediastore uri for a given file, do not return its path either to avoid some mismatching -fun Context.getFileUrisFromFileDirItems(fileDirItems: List): Pair, ArrayList> { - val fileUris = ArrayList() - val successfulFilePaths = ArrayList() - val allIds = getMediaStoreIds(this) - val filePaths = fileDirItems.map { it.path } - filePaths.forEach { path -> - for ((filePath, mediaStoreId) in allIds) { - if (filePath.lowercase() == path.lowercase()) { - val baseUri = getFileUri(filePath) - val uri = ContentUris.withAppendedId(baseUri, mediaStoreId) - fileUris.add(uri) - successfulFilePaths.add(path) - } - } - } - - return Pair(successfulFilePaths, fileUris) -} - -fun getMediaStoreIds(context: Context): HashMap { - val ids = HashMap() - val projection = - arrayOf( - Images.Media.DATA, - Images.Media._ID, - ) - - val uri = Files.getContentUri("external") - - try { - context.queryCursor(uri, projection) { cursor -> - try { - val id = cursor.getLongValue(Images.Media._ID) - if (id != 0L) { - val path = cursor.getStringValue(Images.Media.DATA) - ids[path] = id - } - } catch (e: CursorIndexOutOfBoundsException) { - Log.e("GetMediaStoreIds", "Cursor index error: ${e.message}") - } catch (e: SecurityException) { - Log.e("GetMediaStoreIds", "Permission denied: ${e.message}") - } - } - } catch (e: SecurityException) { - Log.e("GetMediaStoreIds", "Permission denied while querying: ${e.message}") - } catch (e: IllegalArgumentException) { - Log.e("GetMediaStoreIds", "Invalid URI or projection: ${e.message}") - } - - return ids -} diff --git a/app/src/main/java/be/scri/extensions/ContextStorageSdk30.kt b/app/src/main/java/be/scri/extensions/ContextStorageSdk30.kt deleted file mode 100644 index f15d01db..00000000 --- a/app/src/main/java/be/scri/extensions/ContextStorageSdk30.kt +++ /dev/null @@ -1,249 +0,0 @@ -package be.scri.extensions - -import android.content.Context -import android.net.Uri -import android.os.Environment -import android.provider.DocumentsContract -import androidx.documentfile.provider.DocumentFile -import be.scri.helpers.EXTERNAL_STORAGE_PROVIDER_AUTHORITY -import be.scri.helpers.isRPlus -import be.scri.helpers.isSPlus -import be.scri.models.FileDirItem -import java.io.File -import java.io.IOException - -private const val DOWNLOAD_DIR = "Download" -private const val ANDROID_DIR = "Android" -private val DIRS_INACCESSIBLE_WITH_SAF_SDK_30 = - if (isSPlus()) { - listOf(DOWNLOAD_DIR, ANDROID_DIR) - } else { - listOf(DOWNLOAD_DIR) - } - -fun Context.hasProperStoredFirstParentUri(path: String): Boolean { - val firstParentUri = createFirstParentTreeUri(path) - return contentResolver.persistedUriPermissions.any { it.uri.toString() == firstParentUri.toString() } -} - -fun Context.isAccessibleWithSAFSdk30(path: String): Boolean { - if (path.startsWith(recycleBinPath) || isExternalStorageManager()) { - return false - } - - val level = getFirstParentLevel(path) - val firstParentDir = path.getFirstParentDirName(this, level) - val firstParentPath = path.getFirstParentPath(this, level) - - val isValidName = firstParentDir != null - val isDirectory = File(firstParentPath).isDirectory - val isAnAccessibleDirectory = DIRS_INACCESSIBLE_WITH_SAF_SDK_30.all { !firstParentDir.equals(it, true) } - return isValidName && isDirectory && isAnAccessibleDirectory -} - -fun Context.getFirstParentLevel(path: String): Int = - when { - isSPlus() && (isInAndroidDir(path) || isInSubFolderInDownloadDir(path)) -> 1 - isRPlus() && isInSubFolderInDownloadDir(path) -> 1 - else -> 0 - } - -fun Context.isRestrictedWithSAFSdk30(path: String): Boolean { - if (path.startsWith(recycleBinPath) || isExternalStorageManager()) { - return false - } - - val level = getFirstParentLevel(path) - val firstParentDir = path.getFirstParentDirName(this, level) - val firstParentPath = path.getFirstParentPath(this, level) - - val isInvalidName = firstParentDir == null - val isDirectory = File(firstParentPath).isDirectory - val isARestrictedDirectory = DIRS_INACCESSIBLE_WITH_SAF_SDK_30.any { firstParentDir.equals(it, true) } - return isInvalidName || (isDirectory && isARestrictedDirectory) -} - -fun Context.isInSubFolderInDownloadDir(path: String): Boolean { - if (path.startsWith(recycleBinPath)) { - return false - } - val firstParentDir = path.getFirstParentDirName(this, 1) - return if (firstParentDir == null) { - false - } else { - val startsWithDownloadDir = firstParentDir.startsWith(DOWNLOAD_DIR, true) - val hasAtLeast1PathSegment = firstParentDir.split("/").filter { it.isNotEmpty() }.size > 1 - val firstParentPath = path.getFirstParentPath(this, 1) - startsWithDownloadDir && hasAtLeast1PathSegment && File(firstParentPath).isDirectory - } -} - -fun Context.isInAndroidDir(path: String): Boolean { - if (path.startsWith(recycleBinPath)) { - return false - } - val firstParentDir = path.getFirstParentDirName(this, 0) - return firstParentDir.equals(ANDROID_DIR, true) -} - -fun isExternalStorageManager(): Boolean = isRPlus() && Environment.isExternalStorageManager() - -fun Context.createFirstParentTreeUriUsingRootTree(fullPath: String): Uri { - val storageId = getSAFStorageId(fullPath) - val level = getFirstParentLevel(fullPath) - val rootParentDirName = fullPath.getFirstParentDirName(this, level) - val treeUri = DocumentsContract.buildTreeDocumentUri(EXTERNAL_STORAGE_PROVIDER_AUTHORITY, "$storageId:") - val documentId = "$storageId:$rootParentDirName" - return DocumentsContract.buildDocumentUriUsingTree(treeUri, documentId) -} - -fun Context.createFirstParentTreeUri(fullPath: String): Uri { - val storageId = getSAFStorageId(fullPath) - val level = getFirstParentLevel(fullPath) - val rootParentDirName = fullPath.getFirstParentDirName(this, level) - val firstParentId = "$storageId:$rootParentDirName" - return DocumentsContract.buildTreeDocumentUri(EXTERNAL_STORAGE_PROVIDER_AUTHORITY, firstParentId) -} - -fun Context.createDocumentUriUsingFirstParentTreeUri(fullPath: String): Uri { - val storageId = getSAFStorageId(fullPath) - val relativePath = - when { - fullPath.startsWith(internalStoragePath) -> fullPath.substring(internalStoragePath.length).trim('/') - else -> fullPath.substringAfter(storageId).trim('/') - } - val treeUri = createFirstParentTreeUri(fullPath) - val documentId = "$storageId:$relativePath" - return DocumentsContract.buildDocumentUriUsingTree(treeUri, documentId) -} - -fun Context.getSAFDocumentId(path: String): String { - val basePath = path.getBasePath(this) - val relativePath = path.substring(basePath.length).trim('/') - val storageId = getSAFStorageId(path) - return "$storageId:$relativePath" -} - -fun Context.createSAFDirectorySdk30(path: String): Boolean = - try { - val treeUri = createFirstParentTreeUri(path) - val parentPath = path.getParentPath() - if (!getDoesFilePathExistSdk30(parentPath)) { - createSAFDirectorySdk30(parentPath) - } - - val documentId = getSAFDocumentId(parentPath) - val parentUri = DocumentsContract.buildDocumentUriUsingTree(treeUri, documentId) - DocumentsContract.createDocument(contentResolver, parentUri, DocumentsContract.Document.MIME_TYPE_DIR, path.getFilenameFromPath()) != null - } catch (e: IllegalStateException) { - showErrorToast(e) - false - } - -fun Context.createSAFFileSdk30(path: String): Boolean = - try { - val treeUri = createFirstParentTreeUri(path) - val parentPath = path.getParentPath() - if (!getDoesFilePathExistSdk30(parentPath)) { - createSAFDirectorySdk30(parentPath) - } - - val documentId = getSAFDocumentId(parentPath) - val parentUri = DocumentsContract.buildDocumentUriUsingTree(treeUri, documentId) - DocumentsContract.createDocument(contentResolver, parentUri, path.getMimeType(), path.getFilenameFromPath()) != null - } catch (e: IllegalStateException) { - showErrorToast(e) - false - } - -fun Context.getDoesFilePathExistSdk30(path: String): Boolean = - when { - isAccessibleWithSAFSdk30(path) -> getFastDocumentSdk30(path)?.exists() ?: false - else -> File(path).exists() - } - -fun Context.getFastDocumentSdk30(path: String): DocumentFile? { - val uri = createDocumentUriUsingFirstParentTreeUri(path) - return DocumentFile.fromSingleUri(this, uri) -} - -fun Context.getDocumentSdk30(path: String): DocumentFile? { - val level = getFirstParentLevel(path) - val firstParentPath = path.getFirstParentPath(this, level) - var relativePath = path.substring(firstParentPath.length) - if (relativePath.startsWith(File.separator)) { - relativePath = relativePath.substring(1) - } - - return try { - val treeUri = createFirstParentTreeUri(path) - var document = DocumentFile.fromTreeUri(applicationContext, treeUri) - val parts = relativePath.split("/").filter { it.isNotEmpty() } - for (part in parts) { - document = document?.findFile(part) - } - document - } catch (ignored: Exception) { - null - } -} - -fun Context.deleteDocumentWithSAFSdk30( - fileDirItem: FileDirItem, - allowDeleteFolder: Boolean, - callback: ((wasSuccess: Boolean) -> Unit)?, -) { - try { - var fileDeleted = false - if (fileDirItem.isDirectory.not() || allowDeleteFolder) { - val fileUri = createDocumentUriUsingFirstParentTreeUri(fileDirItem.path) - fileDeleted = DocumentsContract.deleteDocument(contentResolver, fileUri) - } - - if (fileDeleted) { - deleteFromMediaStore(fileDirItem.path) - callback?.invoke(true) - } - } catch (e: SecurityException) { - callback?.invoke(false) - showErrorToast("Permission denied: ${e.message}") - } catch (e: IllegalArgumentException) { - callback?.invoke(false) - showErrorToast("Invalid arguments: ${e.message}") - } catch (e: IOException) { - callback?.invoke(false) - showErrorToast("I/O error: ${e.message}") - } -} - -fun Context.renameDocumentSdk30( - oldPath: String, - newPath: String, -): Boolean = - try { - val treeUri = createFirstParentTreeUri(oldPath) - val documentId = getSAFDocumentId(oldPath) - val parentUri = DocumentsContract.buildDocumentUriUsingTree(treeUri, documentId) - DocumentsContract.renameDocument(contentResolver, parentUri, newPath.getFilenameFromPath()) != null - } catch (e: IllegalStateException) { - showErrorToast(e) - false - } - -fun Context.hasProperStoredDocumentUriSdk30(path: String): Boolean { - val documentUri = buildDocumentUriSdk30(path) - return contentResolver.persistedUriPermissions.any { it.uri.toString() == documentUri.toString() } -} - -fun Context.buildDocumentUriSdk30(fullPath: String): Uri { - val storageId = getSAFStorageId(fullPath) - - val relativePath = - when { - fullPath.startsWith(internalStoragePath) -> fullPath.substring(internalStoragePath.length).trim('/') - else -> fullPath.substringAfter(storageId).trim('/') - } - - val documentId = "$storageId:$relativePath" - return DocumentsContract.buildDocumentUri(EXTERNAL_STORAGE_PROVIDER_AUTHORITY, documentId) -} diff --git a/app/src/main/java/be/scri/extensions/ContextStyling.kt b/app/src/main/java/be/scri/extensions/ContextStyling.kt index 74aaa839..4c2005b0 100644 --- a/app/src/main/java/be/scri/extensions/ContextStyling.kt +++ b/app/src/main/java/be/scri/extensions/ContextStyling.kt @@ -2,21 +2,9 @@ package be.scri.extensions import android.content.Context import android.content.res.Configuration -import android.database.CursorIndexOutOfBoundsException import android.graphics.Color -import android.view.ViewGroup -import android.widget.Toast -import androidx.loader.content.CursorLoader import be.scri.R import be.scri.helpers.DARK_GREY -import be.scri.helpers.INVALID_NAVIGATION_BAR_COLOR -import be.scri.helpers.MyContentProvider -import be.scri.helpers.ensureBackgroundThread -import be.scri.models.SharedTheme -import be.scri.views.MyAppCompatCheckbox -import be.scri.views.MyEditText -import be.scri.views.MyFloatingActionButton -import be.scri.views.MyTextView // handle system default theme (Material You) specially as the color is taken from the system, not hardcoded by us fun Context.getProperTextColor() = @@ -47,101 +35,8 @@ fun Context.getProperPrimaryColor() = else -> baseConfig.primaryColor } -fun Context.getProperStatusBarColor() = - when { - baseConfig.isUsingSystemTheme -> resources.getColor(R.color.you_primary_color, theme) - else -> baseConfig.primaryColor - } - -fun Context.updateTextColors(viewGroup: ViewGroup) { - val textColor = - when { - baseConfig.isUsingSystemTheme -> getProperTextColor() - else -> baseConfig.textColor - } - - val accentColor = - when { - isWhiteTheme() || isBlackAndWhiteTheme() -> baseConfig.accentColor - else -> getProperPrimaryColor() - } - - for (i in 0 until viewGroup.childCount) { - when (val view = viewGroup.getChildAt(i)) { - is MyTextView -> view.setColors(textColor, accentColor) - is MyAppCompatCheckbox -> view.setColors(textColor, accentColor) - is MyEditText -> view.setColors(textColor, accentColor) - is MyFloatingActionButton -> view.setColors(textColor) - is ViewGroup -> updateTextColors(view) - } - } -} - -fun Context.getLinkTextColor(): Int = - if (baseConfig.primaryColor == resources.getColor(R.color.color_primary)) { - baseConfig.primaryColor - } else { - baseConfig.textColor - } - fun Context.isBlackAndWhiteTheme() = baseConfig.textColor == Color.WHITE && baseConfig.primaryColor == Color.BLACK && baseConfig.backgroundColor == Color.BLACK fun Context.isWhiteTheme() = baseConfig.textColor == DARK_GREY && baseConfig.primaryColor == Color.WHITE && baseConfig.backgroundColor == Color.WHITE fun Context.isUsingSystemDarkTheme() = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_YES != 0 - -fun Context.getTimePickerDialogTheme() = - when { - baseConfig.isUsingSystemTheme -> - if (isUsingSystemDarkTheme()) { - R.style.MyTimePickerMaterialTheme_Dark - } else { - R.style.MyDateTimePickerMaterialTheme - } - baseConfig.backgroundColor.getContrastColor() == Color.WHITE -> R.style.MyDialogTheme_Dark - else -> R.style.MyDialogTheme - } - -fun Context.getDatePickerDialogTheme() = - when { - baseConfig.isUsingSystemTheme -> R.style.MyDateTimePickerMaterialTheme - baseConfig.backgroundColor.getContrastColor() == Color.WHITE -> R.style.MyDialogTheme_Dark - else -> R.style.MyDialogTheme - } - -fun Context.getSharedTheme(callback: (sharedTheme: SharedTheme?) -> Unit) { - if (!isThankYouInstalled()) { - callback(null) - } else { - val cursorLoader = getMyContentProviderCursorLoader() - ensureBackgroundThread { - callback(getSharedThemeSync(cursorLoader)) - } - } -} - -fun Context.getSharedThemeSync(cursorLoader: CursorLoader): SharedTheme? { - val cursor = cursorLoader.loadInBackground() - cursor?.use { - if (cursor.moveToFirst()) { - try { - val textColor = cursor.getIntValue(MyContentProvider.COL_TEXT_COLOR) - val backgroundColor = cursor.getIntValue(MyContentProvider.COL_BACKGROUND_COLOR) - val primaryColor = cursor.getIntValue(MyContentProvider.COL_PRIMARY_COLOR) - val accentColor = cursor.getIntValue(MyContentProvider.COL_ACCENT_COLOR) - val appIconColor = cursor.getIntValue(MyContentProvider.COL_APP_ICON_COLOR) - val navigationBarColor = cursor.getIntValueOrNull(MyContentProvider.COL_NAVIGATION_BAR_COLOR) ?: INVALID_NAVIGATION_BAR_COLOR - val lastUpdatedTS = cursor.getIntValue(MyContentProvider.COL_LAST_UPDATED_TS) - - return SharedTheme(textColor, backgroundColor, primaryColor, appIconColor, navigationBarColor, lastUpdatedTS, accentColor) - } catch (e: IllegalArgumentException) { - Toast.makeText(this, "Invalid column index", Toast.LENGTH_SHORT).show() - } catch (e: CursorIndexOutOfBoundsException) { - Toast.makeText(this, "Cursor is not in a valid state", Toast.LENGTH_SHORT).show() - } - } - } - return null -} - -fun Context.getAppIconColors() = resources.getIntArray(R.array.md_app_icon_colors).toCollection(ArrayList()) diff --git a/app/src/main/java/be/scri/extensions/Cursor.kt b/app/src/main/java/be/scri/extensions/Cursor.kt deleted file mode 100644 index 5d487da7..00000000 --- a/app/src/main/java/be/scri/extensions/Cursor.kt +++ /dev/null @@ -1,17 +0,0 @@ -package be.scri.extensions - -import android.database.Cursor - -fun Cursor.getStringValue(key: String) = getString(getColumnIndex(key)) - -fun Cursor.getStringValueOrNull(key: String) = if (isNull(getColumnIndex(key))) null else getString(getColumnIndex(key)) - -fun Cursor.getIntValue(key: String) = getInt(getColumnIndex(key)) - -fun Cursor.getIntValueOrNull(key: String) = if (isNull(getColumnIndex(key))) null else getInt(getColumnIndex(key)) - -fun Cursor.getLongValue(key: String) = getLong(getColumnIndex(key)) - -fun Cursor.getLongValueOrNull(key: String) = if (isNull(getColumnIndex(key))) null else getLong(getColumnIndex(key)) - -fun Cursor.getBlobValue(key: String) = getBlob(getColumnIndex(key)) diff --git a/app/src/main/java/be/scri/extensions/DocumentFile.kt b/app/src/main/java/be/scri/extensions/DocumentFile.kt deleted file mode 100644 index 24287dab..00000000 --- a/app/src/main/java/be/scri/extensions/DocumentFile.kt +++ /dev/null @@ -1,56 +0,0 @@ -package be.scri.extensions - -import androidx.documentfile.provider.DocumentFile - -fun DocumentFile.getItemSize(countHiddenItems: Boolean): Long = - if (isDirectory) { - getDirectorySize(this, countHiddenItems) - } else { - length() - } - -private fun getDirectorySize( - dir: DocumentFile, - countHiddenItems: Boolean, -): Long { - var size = 0L - if (dir.exists()) { - val files = dir.listFiles() - for (i in files.indices) { - val file = files[i] - if (file.isDirectory) { - size += getDirectorySize(file, countHiddenItems) - } else if (!file.name!!.startsWith(".") || countHiddenItems) { - size += file.length() - } - } - } - return size -} - -fun DocumentFile.getFileCount(countHiddenItems: Boolean): Int = - if (isDirectory) { - getDirectoryFileCount(this, countHiddenItems) - } else { - 1 - } - -private fun getDirectoryFileCount( - dir: DocumentFile, - countHiddenItems: Boolean, -): Int { - var count = 0 - if (dir.exists()) { - val files = dir.listFiles() - for (i in files.indices) { - val file = files[i] - if (file.isDirectory) { - count++ - count += getDirectoryFileCount(file, countHiddenItems) - } else if (!file.name!!.startsWith(".") || countHiddenItems) { - count++ - } - } - } - return count -} diff --git a/app/src/main/java/be/scri/extensions/Drawable.kt b/app/src/main/java/be/scri/extensions/Drawable.kt index 4b96d7a2..9a9786af 100644 --- a/app/src/main/java/be/scri/extensions/Drawable.kt +++ b/app/src/main/java/be/scri/extensions/Drawable.kt @@ -1,29 +1,6 @@ package be.scri.extensions -import android.graphics.Bitmap -import android.graphics.Canvas import android.graphics.PorterDuff -import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.Drawable fun Drawable.applyColorFilter(color: Int) = mutate().setColorFilter(color, PorterDuff.Mode.SRC_IN) - -fun Drawable.convertToBitmap(): Bitmap { - val bitmap = - if (intrinsicWidth <= 0 || intrinsicHeight <= 0) { - Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888) - } else { - Bitmap.createBitmap(intrinsicWidth, intrinsicHeight, Bitmap.Config.ARGB_8888) - } - - if (this is BitmapDrawable) { - if (this.bitmap != null) { - return this.bitmap - } - } - - val canvas = Canvas(bitmap!!) - setBounds(0, 0, canvas.width, canvas.height) - draw(canvas) - return bitmap -} diff --git a/app/src/main/java/be/scri/extensions/EditText.kt b/app/src/main/java/be/scri/extensions/EditText.kt deleted file mode 100644 index f4e76d49..00000000 --- a/app/src/main/java/be/scri/extensions/EditText.kt +++ /dev/null @@ -1,60 +0,0 @@ -package be.scri.extensions - -import android.text.Editable -import android.text.Spannable -import android.text.SpannableString -import android.text.TextWatcher -import android.text.style.BackgroundColorSpan -import android.widget.EditText -import android.widget.TextView -import androidx.core.graphics.ColorUtils - -val EditText.value: String get() = text.toString().trim() - -fun EditText.onTextChangeListener(onTextChangedAction: (newText: String) -> Unit) = - addTextChangedListener( - object : TextWatcher { - override fun afterTextChanged(s: Editable?) { - onTextChangedAction(s.toString()) - } - - override fun beforeTextChanged( - s: CharSequence, - start: Int, - count: Int, - after: Int, - ) {} - - override fun onTextChanged( - s: CharSequence, - start: Int, - before: Int, - count: Int, - ) {} - }, - ) - -fun EditText.highlightText( - highlightText: String, - color: Int, -) { - val content = text.toString() - var indexOf = content.indexOf(highlightText, 0, true) - val wordToSpan = SpannableString(text) - var offset = 0 - - while (offset < content.length && indexOf != -1) { - indexOf = content.indexOf(highlightText, offset, true) - - if (indexOf == -1) { - break - } else { - val spanBgColor = BackgroundColorSpan(ColorUtils.setAlphaComponent(color, 128)) - val spanFlag = Spannable.SPAN_EXCLUSIVE_EXCLUSIVE - wordToSpan.setSpan(spanBgColor, indexOf, indexOf + highlightText.length, spanFlag) - setText(wordToSpan, TextView.BufferType.SPANNABLE) - } - - offset = indexOf + 1 - } -} diff --git a/app/src/main/java/be/scri/extensions/Editable.kt b/app/src/main/java/be/scri/extensions/Editable.kt deleted file mode 100644 index 4341d98b..00000000 --- a/app/src/main/java/be/scri/extensions/Editable.kt +++ /dev/null @@ -1,13 +0,0 @@ -package be.scri.extensions - -import android.text.Editable -import android.text.style.BackgroundColorSpan - -fun Editable.clearBackgroundSpans() { - val spans = getSpans(0, length, Any::class.java) - for (span in spans) { - if (span is BackgroundColorSpan) { - removeSpan(span) - } - } -} diff --git a/app/src/main/java/be/scri/extensions/ExifInterface.kt b/app/src/main/java/be/scri/extensions/ExifInterface.kt deleted file mode 100644 index 8bab37fd..00000000 --- a/app/src/main/java/be/scri/extensions/ExifInterface.kt +++ /dev/null @@ -1,158 +0,0 @@ -package be.scri.extensions - -import android.annotation.TargetApi -import android.content.Context -import android.os.Build -import androidx.exifinterface.media.ExifInterface -import java.text.SimpleDateFormat -import java.util.Locale - -fun ExifInterface.copyTo( - destination: ExifInterface, - copyOrientation: Boolean = true, -) { - val attributes = - arrayListOf( - ExifInterface.TAG_APERTURE_VALUE, - ExifInterface.TAG_DATETIME, - ExifInterface.TAG_DATETIME_DIGITIZED, - ExifInterface.TAG_DATETIME_ORIGINAL, - ExifInterface.TAG_EXPOSURE_TIME, - ExifInterface.TAG_FLASH, - ExifInterface.TAG_FOCAL_LENGTH, - ExifInterface.TAG_GPS_ALTITUDE, - ExifInterface.TAG_GPS_ALTITUDE_REF, - ExifInterface.TAG_GPS_DATESTAMP, - ExifInterface.TAG_GPS_LATITUDE, - ExifInterface.TAG_GPS_LATITUDE_REF, - ExifInterface.TAG_GPS_LONGITUDE, - ExifInterface.TAG_GPS_LONGITUDE_REF, - ExifInterface.TAG_GPS_PROCESSING_METHOD, - ExifInterface.TAG_GPS_TIMESTAMP, - ExifInterface.TAG_IMAGE_LENGTH, - ExifInterface.TAG_IMAGE_WIDTH, - ExifInterface.TAG_ISO_SPEED_RATINGS, - ExifInterface.TAG_MAKE, - ExifInterface.TAG_MODEL, - ExifInterface.TAG_WHITE_BALANCE, - ) - - if (copyOrientation) { - attributes.add(ExifInterface.TAG_ORIENTATION) - } - - attributes.forEach { - val value = getAttribute(it) - if (value != null) { - destination.setAttribute(it, value) - } - } - - try { - destination.saveAttributes() - } catch (ignored: Exception) { - } -} - -fun ExifInterface.removeValues() { - val attributes = - arrayListOf( - // ExifInterface.TAG_ORIENTATION, // do not remove the orientation, it could lead to unexpected behaviour at displaying the file - ExifInterface.TAG_APERTURE_VALUE, - ExifInterface.TAG_DATETIME, - ExifInterface.TAG_DATETIME_DIGITIZED, - ExifInterface.TAG_DATETIME_ORIGINAL, - ExifInterface.TAG_EXPOSURE_TIME, - ExifInterface.TAG_FLASH, - ExifInterface.TAG_F_NUMBER, - ExifInterface.TAG_FOCAL_LENGTH, - ExifInterface.TAG_GPS_ALTITUDE, - ExifInterface.TAG_GPS_ALTITUDE_REF, - ExifInterface.TAG_GPS_DATESTAMP, - ExifInterface.TAG_GPS_LATITUDE, - ExifInterface.TAG_GPS_LATITUDE_REF, - ExifInterface.TAG_GPS_LONGITUDE, - ExifInterface.TAG_GPS_LONGITUDE_REF, - ExifInterface.TAG_GPS_PROCESSING_METHOD, - ExifInterface.TAG_GPS_TIMESTAMP, - ExifInterface.TAG_IMAGE_LENGTH, - ExifInterface.TAG_IMAGE_WIDTH, - ExifInterface.TAG_ISO_SPEED_RATINGS, - ExifInterface.TAG_MAKE, - ExifInterface.TAG_MODEL, - ExifInterface.TAG_WHITE_BALANCE, - ) - - attributes.forEach { - setAttribute(it, null) - } - - saveAttributes() -} - -fun ExifInterface.getExifProperties(): String { - var exifString = "" - getAttribute(ExifInterface.TAG_F_NUMBER).let { - if (it?.isNotEmpty() == true) { - val number = it.trimEnd('0').trimEnd('.') - exifString += "F/$number " - } - } - - getAttribute(ExifInterface.TAG_FOCAL_LENGTH).let { - if (it?.isNotEmpty() == true) { - val values = it.split('/') - val focalLength = "${values[0].toDouble() / values[1].toDouble()}mm" - exifString += "$focalLength " - } - } - - getAttribute(ExifInterface.TAG_EXPOSURE_TIME).let { - if (it?.isNotEmpty() == true) { - val exposureValue = it.toFloat() - exifString += - if (exposureValue > 1f) { - "${exposureValue}s " - } else { - "1/${Math.round(1 / exposureValue)}s " - } - } - } - - getAttribute(ExifInterface.TAG_ISO_SPEED_RATINGS).let { - if (it?.isNotEmpty() == true) { - exifString += "ISO-$it" - } - } - - return exifString.trim() -} - -@TargetApi(Build.VERSION_CODES.N) -fun ExifInterface.getExifDateTaken(context: Context): String { - val dateTime = getAttribute(ExifInterface.TAG_DATETIME_ORIGINAL) ?: getAttribute(ExifInterface.TAG_DATETIME) - dateTime.let { - if (it?.isNotEmpty() == true) { - try { - val simpleDateFormat = SimpleDateFormat("yyyy:MM:dd kk:mm:ss", Locale.ENGLISH) - return simpleDateFormat - .parse(it) - .time - .formatDate(context) - .trim() - } catch (ignored: Exception) { - } - } - } - return "" -} - -fun ExifInterface.getExifCameraModel(): String { - getAttribute(ExifInterface.TAG_MAKE).let { - if (it?.isNotEmpty() == true) { - val model = getAttribute(ExifInterface.TAG_MODEL) - return "$it $model".trim() - } - } - return "" -} diff --git a/app/src/main/java/be/scri/extensions/File.kt b/app/src/main/java/be/scri/extensions/File.kt deleted file mode 100644 index 4e58461e..00000000 --- a/app/src/main/java/be/scri/extensions/File.kt +++ /dev/null @@ -1,182 +0,0 @@ -package be.scri.extensions - -import android.content.Context -import be.scri.helpers.MD5 -import be.scri.helpers.NOMEDIA -import be.scri.helpers.audioExtensions -import be.scri.helpers.photoExtensions -import be.scri.helpers.rawExtensions -import be.scri.helpers.videoExtensions -import be.scri.models.FileDirItem -import java.io.File -import java.io.IOException -import java.security.NoSuchAlgorithmException - -fun File.isMediaFile() = absolutePath.isMediaFile() - -fun File.isGif() = absolutePath.endsWith(".gif", true) - -fun File.isApng() = absolutePath.endsWith(".apng", true) - -fun File.isVideoFast() = videoExtensions.any { absolutePath.endsWith(it, true) } - -fun File.isImageFast() = photoExtensions.any { absolutePath.endsWith(it, true) } - -fun File.isAudioFast() = audioExtensions.any { absolutePath.endsWith(it, true) } - -fun File.isRawFast() = rawExtensions.any { absolutePath.endsWith(it, true) } - -fun File.isSvg() = absolutePath.isSvg() - -fun File.isPortrait() = absolutePath.isPortrait() - -fun File.isImageSlow() = absolutePath.isImageFast() || getMimeType().startsWith("image") - -fun File.isVideoSlow() = absolutePath.isVideoFast() || getMimeType().startsWith("video") - -fun File.isAudioSlow() = absolutePath.isAudioFast() || getMimeType().startsWith("audio") - -fun File.getMimeType() = absolutePath.getMimeType() - -fun File.getProperSize(countHiddenItems: Boolean): Long = - if (isDirectory) { - getDirectorySize(this, countHiddenItems) - } else { - length() - } - -private fun getDirectorySize( - dir: File, - countHiddenItems: Boolean, -): Long { - var size = 0L - if (dir.exists()) { - val files = dir.listFiles() - if (files != null) { - for (i in files.indices) { - if (files[i].isDirectory) { - size += getDirectorySize(files[i], countHiddenItems) - } else if (!files[i].name.startsWith('.') && !dir.name.startsWith('.') || countHiddenItems) { - size += files[i].length() - } - } - } - } - return size -} - -fun File.getFileCount(countHiddenItems: Boolean): Int = - if (isDirectory) { - getDirectoryFileCount(this, countHiddenItems) - } else { - 1 - } - -private fun getDirectoryFileCount( - dir: File, - countHiddenItems: Boolean, -): Int { - var count = -1 - if (dir.exists()) { - val files = dir.listFiles() - if (files != null) { - count++ - for (i in files.indices) { - val file = files[i] - if (file.isDirectory) { - count++ - count += getDirectoryFileCount(file, countHiddenItems) - } else if (!file.name.startsWith('.') || countHiddenItems) { - count++ - } - } - } - } - return count -} - -fun File.getDirectChildrenCount( - context: Context, - countHiddenItems: Boolean, -): Int { - val fileCount = - if (context.isRestrictedSAFOnlyRoot(path)) { - context.getAndroidSAFDirectChildrenCount( - path, - countHiddenItems, - ) - } else { - listFiles() - ?.filter { - if (countHiddenItems) { - true - } else { - !it.name.startsWith('.') - } - }?.size ?: 0 - } - - return fileCount -} - -fun File.toFileDirItem(context: Context) = FileDirItem(absolutePath, name, context.getIsPathDirectory(absolutePath), 0, length(), lastModified()) - -fun File.containsNoMedia(): Boolean = - if (!isDirectory) { - false - } else { - File(this, NOMEDIA).exists() - } - -fun File.doesThisOrParentHaveNoMedia( - folderNoMediaStatuses: HashMap, - callback: ((path: String, hasNoMedia: Boolean) -> Unit)?, -): Boolean { - var curFile = this - while (true) { - val noMediaPath = "${curFile.absolutePath}/$NOMEDIA" - val hasNoMedia = - if (folderNoMediaStatuses.keys.contains(noMediaPath)) { - folderNoMediaStatuses[noMediaPath]!! - } else { - val contains = curFile.containsNoMedia() - callback?.invoke(curFile.absolutePath, contains) - contains - } - - if (hasNoMedia) { - return true - } - - curFile = curFile.parentFile ?: break - if (curFile.absolutePath == "/") { - break - } - } - return false -} - -fun File.doesParentHaveNoMedia(): Boolean { - var curFile = parentFile - while (true) { - if (curFile?.containsNoMedia() == true) { - return true - } - curFile = curFile?.parentFile ?: break - if (curFile.absolutePath == "/") { - break - } - } - return false -} - -fun File.getDigest(algorithm: String): String? = - try { - inputStream().getDigest(algorithm) - } catch (e: IOException) { - null - } catch (e: NoSuchAlgorithmException) { - null - } - -fun File.md5() = this.getDigest(MD5) diff --git a/app/src/main/java/be/scri/extensions/ImageView.kt b/app/src/main/java/be/scri/extensions/ImageView.kt deleted file mode 100644 index f4714859..00000000 --- a/app/src/main/java/be/scri/extensions/ImageView.kt +++ /dev/null @@ -1,24 +0,0 @@ -package be.scri.extensions - -import android.graphics.PorterDuff -import android.graphics.drawable.GradientDrawable -import android.widget.ImageView - -fun ImageView.setFillWithStroke( - fillColor: Int, - backgroundColor: Int, - drawRectangle: Boolean = false, -) { - GradientDrawable().apply { - shape = if (drawRectangle) GradientDrawable.RECTANGLE else GradientDrawable.OVAL - setColor(fillColor) - background = this - - if (backgroundColor == fillColor || fillColor == -2 && backgroundColor == -1) { - val strokeColor = backgroundColor.getContrastColor().adjustAlpha(0.5f) - setStroke(2, strokeColor) - } - } -} - -fun ImageView.applyColorFilter(color: Int) = setColorFilter(color, PorterDuff.Mode.SRC_IN) diff --git a/app/src/main/java/be/scri/extensions/InputStream.kt b/app/src/main/java/be/scri/extensions/InputStream.kt deleted file mode 100644 index 5a81b98a..00000000 --- a/app/src/main/java/be/scri/extensions/InputStream.kt +++ /dev/null @@ -1,20 +0,0 @@ -package be.scri.extensions - -import be.scri.helpers.MD5 -import java.io.InputStream -import java.security.MessageDigest - -fun InputStream.getDigest(algorithm: String): String = - use { fis -> - val md = MessageDigest.getInstance(algorithm) - val buffer = ByteArray(8192) - generateSequence { - when (val bytesRead = fis.read(buffer)) { - -1 -> null - else -> bytesRead - } - }.forEach { bytesRead -> md.update(buffer, 0, bytesRead) } - md.digest().joinToString("") { "%02x".format(it) } - } - -fun InputStream.md5(): String = this.getDigest(MD5) diff --git a/app/src/main/java/be/scri/extensions/Int.kt b/app/src/main/java/be/scri/extensions/Int.kt index d35a006d..1b0564a6 100644 --- a/app/src/main/java/be/scri/extensions/Int.kt +++ b/app/src/main/java/be/scri/extensions/Int.kt @@ -1,16 +1,7 @@ package be.scri.extensions -import android.content.Context -import android.content.res.ColorStateList import android.graphics.Color -import android.media.ExifInterface -import android.text.format.DateFormat -import android.text.format.DateUtils -import android.text.format.Time import be.scri.helpers.DARK_GREY -import java.text.DecimalFormat -import java.util.Calendar -import java.util.Locale import java.util.Random fun Int.getContrastColor(): Int { @@ -18,8 +9,6 @@ fun Int.getContrastColor(): Int { return if (y >= 149 && this != Color.BLACK) DARK_GREY else Color.WHITE } -fun Int.toHex() = "#%06X".format(0xFFFFFF and this).uppercase() - fun Int.adjustAlpha(factor: Float): Int { val alpha = Math.round(Color.alpha(this) * factor) val red = Color.red(this) @@ -28,101 +17,6 @@ fun Int.adjustAlpha(factor: Float): Int { return Color.argb(alpha, red, green, blue) } -fun Int.getFormattedDuration(forceShowHours: Boolean = false): String { - val sb = StringBuilder(8) - val hours = this / 3600 - val minutes = this % 3600 / 60 - val seconds = this % 60 - - if (this >= 3600) { - sb.append(String.format(Locale.getDefault(), "%02d", hours)).append(":") - } else if (forceShowHours) { - sb.append("0:") - } - - sb.append(String.format(Locale.getDefault(), "%02d", minutes)) - sb.append(":").append(String.format(Locale.getDefault(), "%02d", seconds)) - return sb.toString() -} - -fun Int.formatSize(): String { - if (this <= 0) { - return "0 B" - } - - val units = arrayOf("B", "kB", "MB", "GB", "TB") - val digitGroups = (Math.log10(toDouble()) / Math.log10(1024.0)).toInt() - return "${DecimalFormat("#,##0.#").format(this / Math.pow(1024.0, digitGroups.toDouble()))} ${units[digitGroups]}" -} - -fun Int.formatDate( - context: Context, - dateFormat: String? = null, - timeFormat: String? = null, -): String { - val useDateFormat = dateFormat ?: context.baseConfig.dateFormat - val useTimeFormat = timeFormat ?: context.getTimeFormat() - val cal = Calendar.getInstance(Locale.ENGLISH) - cal.timeInMillis = this * 1000L - return DateFormat.format("$useDateFormat, $useTimeFormat", cal).toString() -} - -// if the given date is today, we show only the time. Else we show the date and optionally the time too -fun Int.formatDateOrTime( - context: Context, - hideTimeAtOtherDays: Boolean, - showYearEvenIfCurrent: Boolean, -): String { - val cal = Calendar.getInstance(Locale.ENGLISH) - cal.timeInMillis = this * 1000L - - return if (DateUtils.isToday(this * 1000L)) { - DateFormat.format(context.getTimeFormat(), cal).toString() - } else { - var format = context.baseConfig.dateFormat - if (!showYearEvenIfCurrent && isThisYear()) { - format = - format - .replace("y", "") - .trim() - .trim('-') - .trim('.') - .trim('/') - } - - if (!hideTimeAtOtherDays) { - format += ", ${context.getTimeFormat()}" - } - - DateFormat.format(format, cal).toString() - } -} - -fun Int.isThisYear(): Boolean { - val time = Time() - time.set(this * 1000L) - - val thenYear = time.year - time.set(System.currentTimeMillis()) - - return (thenYear == time.year) -} - -fun Int.addBitIf( - add: Boolean, - bit: Int, -) = if (add) { - addBit(bit) -} else { - removeBit(bit) -} - -fun Int.removeBit(bit: Int) = addBit(bit) - bit - -fun Int.addBit(bit: Int) = this or bit - -fun Int.flipBit(bit: Int) = if (this and bit == 0) addBit(bit) else removeBit(bit) - fun ClosedRange.random() = Random().nextInt(endInclusive - start) + start // taken from https://stackoverflow.com/a/40964456/1967672 @@ -181,38 +75,3 @@ private fun hsv2hsl(hsv: FloatArray): FloatArray { return floatArrayOf(hue, newSat, newHue / 2f) } - -fun Int.orientationFromDegrees() = - when (this) { - 270 -> ExifInterface.ORIENTATION_ROTATE_270 - 180 -> ExifInterface.ORIENTATION_ROTATE_180 - 90 -> ExifInterface.ORIENTATION_ROTATE_90 - else -> ExifInterface.ORIENTATION_NORMAL - }.toString() - -fun Int.degreesFromOrientation() = - when (this) { - ExifInterface.ORIENTATION_ROTATE_270 -> 270 - ExifInterface.ORIENTATION_ROTATE_180 -> 180 - ExifInterface.ORIENTATION_ROTATE_90 -> 90 - else -> 0 - } - -fun Int.ensureTwoDigits(): String = - if (toString().length == 1) { - "0$this" - } else { - toString() - } - -fun Int.getColorStateList(): ColorStateList { - val states = - arrayOf( - intArrayOf(android.R.attr.state_enabled), - intArrayOf(-android.R.attr.state_enabled), - intArrayOf(-android.R.attr.state_checked), - intArrayOf(android.R.attr.state_pressed), - ) - val colors = intArrayOf(this, this, this, this) - return ColorStateList(states, colors) -} diff --git a/app/src/main/java/be/scri/extensions/List.kt b/app/src/main/java/be/scri/extensions/List.kt deleted file mode 100644 index d91e9c02..00000000 --- a/app/src/main/java/be/scri/extensions/List.kt +++ /dev/null @@ -1,21 +0,0 @@ -package be.scri.extensions - -fun List.getMimeType(): String { - val mimeGroups = HashSet(size) - val subtypes = HashSet(size) - forEach { - val parts = it.getMimeType().split("/") - if (parts.size == 2) { - mimeGroups.add(parts.getOrElse(0) { "" }) - subtypes.add(parts.getOrElse(1) { "" }) - } else { - return "*/*" - } - } - - return when { - subtypes.size == 1 -> "${mimeGroups.first()}/${subtypes.first()}" - mimeGroups.size == 1 -> "${mimeGroups.first()}/*" - else -> "*/*" - } -} diff --git a/app/src/main/java/be/scri/extensions/Long.kt b/app/src/main/java/be/scri/extensions/Long.kt deleted file mode 100644 index 12c196f0..00000000 --- a/app/src/main/java/be/scri/extensions/Long.kt +++ /dev/null @@ -1,29 +0,0 @@ -package be.scri.extensions - -import android.content.Context -import android.text.format.DateFormat -import java.text.DecimalFormat -import java.util.Calendar -import java.util.Locale - -fun Long.formatSize(): String { - if (this <= 0) { - return "0 B" - } - - val units = arrayOf("B", "kB", "MB", "GB", "TB") - val digitGroups = (Math.log10(toDouble()) / Math.log10(1024.0)).toInt() - return "${DecimalFormat("#,##0.#").format(this / Math.pow(1024.0, digitGroups.toDouble()))} ${units[digitGroups]}" -} - -fun Long.formatDate( - context: Context, - dateFormat: String? = null, - timeFormat: String? = null, -): String { - val useDateFormat = dateFormat ?: context.baseConfig.dateFormat - val useTimeFormat = timeFormat ?: context.getTimeFormat() - val cal = Calendar.getInstance(Locale.ENGLISH) - cal.timeInMillis = this - return DateFormat.format("$useDateFormat, $useTimeFormat", cal).toString() -} diff --git a/app/src/main/java/be/scri/extensions/Point.kt b/app/src/main/java/be/scri/extensions/Point.kt deleted file mode 100644 index c9f4f42e..00000000 --- a/app/src/main/java/be/scri/extensions/Point.kt +++ /dev/null @@ -1,11 +0,0 @@ -package be.scri.extensions - -import android.graphics.Point - -fun Point.formatAsResolution() = "$x x $y ${getMPx()}" - -fun Point.getMPx(): String { - val px = x * y / 1000000.toFloat() - val rounded = Math.round(px * 10) / 10.toFloat() - return "(${rounded}MP)" -} diff --git a/app/src/main/java/be/scri/extensions/RemoteViews.kt b/app/src/main/java/be/scri/extensions/RemoteViews.kt deleted file mode 100644 index 28f93f16..00000000 --- a/app/src/main/java/be/scri/extensions/RemoteViews.kt +++ /dev/null @@ -1,42 +0,0 @@ -package be.scri.extensions - -import android.graphics.Color -import android.view.View -import android.widget.RemoteViews - -fun RemoteViews.setBackgroundColor( - id: Int, - color: Int, -) { - setInt(id, "setBackgroundColor", color) -} - -fun RemoteViews.setTextSize( - id: Int, - size: Float, -) { - setFloat(id, "setTextSize", size) -} - -fun RemoteViews.setText( - id: Int, - text: String, -) { - setTextViewText(id, text) -} - -fun RemoteViews.setVisibleIf( - id: Int, - beVisible: Boolean, -) { - val visibility = if (beVisible) View.VISIBLE else View.GONE - setViewVisibility(id, visibility) -} - -fun RemoteViews.applyColorFilter( - id: Int, - color: Int, -) { - setInt(id, "setColorFilter", color) - setInt(id, "setImageAlpha", Color.alpha(color)) -} diff --git a/app/src/main/java/be/scri/extensions/Resources.kt b/app/src/main/java/be/scri/extensions/Resources.kt deleted file mode 100644 index e8a4e733..00000000 --- a/app/src/main/java/be/scri/extensions/Resources.kt +++ /dev/null @@ -1,52 +0,0 @@ -package be.scri.extensions - -import android.content.res.Resources -import android.graphics.Bitmap -import android.graphics.Canvas -import android.graphics.PorterDuff -import android.graphics.PorterDuffColorFilter -import android.graphics.drawable.Drawable - -fun Resources.getColoredBitmap( - resourceId: Int, - newColor: Int, -): Bitmap { - val drawable = getDrawable(resourceId) - val bitmap = Bitmap.createBitmap(drawable.intrinsicWidth, drawable.intrinsicHeight, Bitmap.Config.ARGB_8888) - val canvas = Canvas(bitmap) - drawable.setBounds(0, 0, canvas.width, canvas.height) - drawable.colorFilter = PorterDuffColorFilter(newColor, PorterDuff.Mode.SRC_IN) - drawable.draw(canvas) - return bitmap -} - -fun Resources.getColoredDrawable( - drawableId: Int, - colorId: Int, - alpha: Int = 255, -) = getColoredDrawableWithColor(drawableId, getColor(colorId), alpha) - -fun Resources.getColoredDrawableWithColor( - drawableId: Int, - color: Int, - alpha: Int = 255, -): Drawable { - val drawable = getDrawable(drawableId) - drawable.mutate().applyColorFilter(color) - drawable.mutate().alpha = alpha - return drawable -} - -fun Resources.hasNavBar(): Boolean { - val id = getIdentifier("config_showNavigationBar", "bool", "android") - return id > 0 && getBoolean(id) -} - -fun Resources.getNavBarHeight(): Int { - val id = getIdentifier("navigation_bar_height", "dimen", "android") - return if (id > 0 && hasNavBar()) { - getDimensionPixelSize(id) - } else { - 0 - } -} diff --git a/app/src/main/java/be/scri/extensions/SeekBar.kt b/app/src/main/java/be/scri/extensions/SeekBar.kt deleted file mode 100644 index 3002ac42..00000000 --- a/app/src/main/java/be/scri/extensions/SeekBar.kt +++ /dev/null @@ -1,20 +0,0 @@ -package be.scri.extensions - -import android.widget.SeekBar - -fun SeekBar.onSeekBarChangeListener(seekBarChangeListener: (progress: Int) -> Unit) = - setOnSeekBarChangeListener( - object : SeekBar.OnSeekBarChangeListener { - override fun onProgressChanged( - seekBar: SeekBar, - progress: Int, - fromUser: Boolean, - ) { - seekBarChangeListener(progress) - } - - override fun onStartTrackingTouch(seekBar: SeekBar) {} - - override fun onStopTrackingTouch(seekBar: SeekBar) {} - }, - ) diff --git a/app/src/main/java/be/scri/extensions/String.kt b/app/src/main/java/be/scri/extensions/String.kt deleted file mode 100644 index be838e20..00000000 --- a/app/src/main/java/be/scri/extensions/String.kt +++ /dev/null @@ -1,738 +0,0 @@ -package be.scri.extensions - -import android.content.Context -import android.provider.MediaStore -import be.scri.helpers.audioExtensions -import be.scri.helpers.normalizeRegex -import be.scri.helpers.photoExtensions -import be.scri.helpers.rawExtensions -import be.scri.helpers.videoExtensions -import java.io.File -import java.text.Normalizer - -fun String.getFilenameFromPath() = substring(lastIndexOf("/") + 1) - -fun String.getFilenameExtension() = substring(lastIndexOf(".") + 1) - -fun String.getBasePath(context: Context): String = - when { - startsWith(context.internalStoragePath) -> context.internalStoragePath - context.isPathOnSD(this) -> context.sdCardPath - context.isPathOnOTG(this) -> context.otgPath - else -> "/" - } - -fun String.getFirstParentDirName( - context: Context, - level: Int, -): String? { - val basePath = getBasePath(context) - val startIndex = basePath.length + 1 - return if (length > startIndex) { - val pathWithoutBasePath = substring(startIndex) - val pathSegments = pathWithoutBasePath.split("/") - if (level < pathSegments.size) { - pathSegments.slice(0..level).joinToString("/") - } else { - null - } - } else { - null - } -} - -fun String.getFirstParentPath( - context: Context, - level: Int, -): String { - val basePath = getBasePath(context) - val startIndex = basePath.length + 1 - return if (length > startIndex) { - val pathWithoutBasePath = substring(basePath.length + 1) - val pathSegments = pathWithoutBasePath.split("/") - val firstParentPath = - if (level < pathSegments.size) { - pathSegments.slice(0..level).joinToString("/") - } else { - pathWithoutBasePath - } - "$basePath/$firstParentPath" - } else { - basePath - } -} - -fun String.isMediaFile() = isImageFast() || isVideoFast() || isGif() || isRawFast() || isSvg() || isPortrait() - -fun String.isGif() = endsWith(".gif", true) - -fun String.isSvg() = endsWith(".svg", true) - -fun String.isPortrait() = getFilenameFromPath().contains("portrait", true) && File(this).parentFile?.name?.startsWith("img_", true) == true - -// fast extension checks, not guaranteed to be accurate -fun String.isVideoFast() = videoExtensions.any { endsWith(it, true) } - -fun String.isImageFast() = photoExtensions.any { endsWith(it, true) } - -fun String.isAudioFast() = audioExtensions.any { endsWith(it, true) } - -fun String.isRawFast() = rawExtensions.any { endsWith(it, true) } - -fun String.isImageSlow() = - isImageFast() || - getMimeType().startsWith("image") || - startsWith( - MediaStore.Images.Media.EXTERNAL_CONTENT_URI - .toString(), - ) - -fun String.isVideoSlow() = - isVideoFast() || - getMimeType().startsWith("video") || - startsWith( - MediaStore.Video.Media.EXTERNAL_CONTENT_URI - .toString(), - ) - -fun String.isAudioSlow() = - isAudioFast() || - getMimeType().startsWith("audio") || - startsWith( - MediaStore.Audio.Media.EXTERNAL_CONTENT_URI - .toString(), - ) - -fun String.areDigitsOnly() = matches(Regex("[0-9]+")) - -fun String.getGenericMimeType(): String { - if (!contains("/")) { - return this - } - - val type = substring(0, indexOf("/")) - return "$type/*" -} - -fun String.getParentPath() = removeSuffix("/${getFilenameFromPath()}") - -// remove diacritics, for example č -> c -fun String.normalizeString() = - Normalizer.normalize(this, Normalizer.Form.NFD).replace( - normalizeRegex, - "", - ) - -// if we are comparing phone numbers, compare just the last 9 digits -fun String.trimToComparableNumber(): String { - val normalizedNumber = this.normalizeString() - val startIndex = Math.max(0, normalizedNumber.length - 9) - return normalizedNumber.substring(startIndex) -} - -fun String.getMimeType(): String { - val typesMap = - HashMap().apply { - put("323", "text/h323") - put("3g2", "video/3gpp2") - put("3gp", "video/3gpp") - put("3gp2", "video/3gpp2") - put("3gpp", "video/3gpp") - put("7z", "application/x-7z-compressed") - put("aa", "audio/audible") - put("aac", "audio/aac") - put("aaf", "application/octet-stream") - put("aax", "audio/vnd.audible.aax") - put("ac3", "audio/ac3") - put("aca", "application/octet-stream") - put("accda", "application/msaccess.addin") - put("accdb", "application/msaccess") - put("accdc", "application/msaccess.cab") - put("accde", "application/msaccess") - put("accdr", "application/msaccess.runtime") - put("accdt", "application/msaccess") - put("accdw", "application/msaccess.webapplication") - put("accft", "application/msaccess.ftemplate") - put("acx", "application/internet-property-stream") - put("addin", "text/xml") - put("ade", "application/msaccess") - put("adobebridge", "application/x-bridge-url") - put("adp", "application/msaccess") - put("adt", "audio/vnd.dlna.adts") - put("adts", "audio/aac") - put("afm", "application/octet-stream") - put("ai", "application/postscript") - put("aif", "audio/aiff") - put("aifc", "audio/aiff") - put("aiff", "audio/aiff") - put("air", "application/vnd.adobe.air-application-installer-package+zip") - put("amc", "application/mpeg") - put("anx", "application/annodex") - put("apk", "application/vnd.android.package-archive") - put("application", "application/x-ms-application") - put("art", "image/x-jg") - put("asa", "application/xml") - put("asax", "application/xml") - put("ascx", "application/xml") - put("asd", "application/octet-stream") - put("asf", "video/x-ms-asf") - put("ashx", "application/xml") - put("asi", "application/octet-stream") - put("asm", "text/plain") - put("asmx", "application/xml") - put("aspx", "application/xml") - put("asr", "video/x-ms-asf") - put("asx", "video/x-ms-asf") - put("atom", "application/atom+xml") - put("au", "audio/basic") - put("avi", "video/x-msvideo") - put("axa", "audio/annodex") - put("axs", "application/olescript") - put("axv", "video/annodex") - put("bas", "text/plain") - put("bcpio", "application/x-bcpio") - put("bin", "application/octet-stream") - put("bmp", "image/bmp") - put("c", "text/plain") - put("cab", "application/octet-stream") - put("caf", "audio/x-caf") - put("calx", "application/vnd.ms-office.calx") - put("cat", "application/vnd.ms-pki.seccat") - put("cc", "text/plain") - put("cd", "text/plain") - put("cdda", "audio/aiff") - put("cdf", "application/x-cdf") - put("cer", "application/x-x509-ca-cert") - put("cfg", "text/plain") - put("chm", "application/octet-stream") - put("class", "application/x-java-applet") - put("clp", "application/x-msclip") - put("cmd", "text/plain") - put("cmx", "image/x-cmx") - put("cnf", "text/plain") - put("cod", "image/cis-cod") - put("config", "application/xml") - put("contact", "text/x-ms-contact") - put("coverage", "application/xml") - put("cpio", "application/x-cpio") - put("cpp", "text/plain") - put("crd", "application/x-mscardfile") - put("crl", "application/pkix-crl") - put("crt", "application/x-x509-ca-cert") - put("cs", "text/plain") - put("csdproj", "text/plain") - put("csh", "application/x-csh") - put("csproj", "text/plain") - put("css", "text/css") - put("csv", "text/csv") - put("cur", "application/octet-stream") - put("cxx", "text/plain") - put("dat", "application/octet-stream") - put("datasource", "application/xml") - put("dbproj", "text/plain") - put("dcr", "application/x-director") - put("def", "text/plain") - put("deploy", "application/octet-stream") - put("der", "application/x-x509-ca-cert") - put("dgml", "application/xml") - put("dib", "image/bmp") - put("dif", "video/x-dv") - put("dir", "application/x-director") - put("disco", "text/xml") - put("divx", "video/divx") - put("dll", "application/x-msdownload") - put("dll.config", "text/xml") - put("dlm", "text/dlm") - put("dng", "image/x-adobe-dng") - put("doc", "application/msword") - put("docm", "application/vnd.ms-word.document.macroEnabled.12") - put("docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document") - put("dot", "application/msword") - put("dotm", "application/vnd.ms-word.template.macroEnabled.12") - put("dotx", "application/vnd.openxmlformats-officedocument.wordprocessingml.template") - put("dsp", "application/octet-stream") - put("dsw", "text/plain") - put("dtd", "text/xml") - put("dtsconfig", "text/xml") - put("dv", "video/x-dv") - put("dvi", "application/x-dvi") - put("dwf", "drawing/x-dwf") - put("dwp", "application/octet-stream") - put("dxr", "application/x-director") - put("eml", "message/rfc822") - put("emz", "application/octet-stream") - put("eot", "application/vnd.ms-fontobject") - put("eps", "application/postscript") - put("etl", "application/etl") - put("etx", "text/x-setext") - put("evy", "application/envoy") - put("exe", "application/octet-stream") - put("exe.config", "text/xml") - put("fdf", "application/vnd.fdf") - put("fif", "application/fractals") - put("filters", "application/xml") - put("fla", "application/octet-stream") - put("flac", "audio/flac") - put("flr", "x-world/x-vrml") - put("flv", "video/x-flv") - put("fsscript", "application/fsharp-script") - put("fsx", "application/fsharp-script") - put("generictest", "application/xml") - put("gif", "image/gif") - put("group", "text/x-ms-group") - put("gsm", "audio/x-gsm") - put("gtar", "application/x-gtar") - put("gz", "application/x-gzip") - put("h", "text/plain") - put("hdf", "application/x-hdf") - put("hdml", "text/x-hdml") - put("hhc", "application/x-oleobject") - put("hhk", "application/octet-stream") - put("hhp", "application/octet-stream") - put("hlp", "application/winhlp") - put("hpp", "text/plain") - put("hqx", "application/mac-binhex40") - put("hta", "application/hta") - put("htc", "text/x-component") - put("htm", "text/html") - put("html", "text/html") - put("htt", "text/webviewhtml") - put("hxa", "application/xml") - put("hxc", "application/xml") - put("hxd", "application/octet-stream") - put("hxe", "application/xml") - put("hxf", "application/xml") - put("hxh", "application/octet-stream") - put("hxi", "application/octet-stream") - put("hxk", "application/xml") - put("hxq", "application/octet-stream") - put("hxr", "application/octet-stream") - put("hxs", "application/octet-stream") - put("hxt", "text/html") - put("hxv", "application/xml") - put("hxw", "application/octet-stream") - put("hxx", "text/plain") - put("i", "text/plain") - put("ico", "image/x-icon") - put("ics", "text/calendar") - put("idl", "text/plain") - put("ief", "image/ief") - put("iii", "application/x-iphone") - put("inc", "text/plain") - put("inf", "application/octet-stream") - put("ini", "text/plain") - put("inl", "text/plain") - put("ins", "application/x-internet-signup") - put("ipa", "application/x-itunes-ipa") - put("ipg", "application/x-itunes-ipg") - put("ipproj", "text/plain") - put("ipsw", "application/x-itunes-ipsw") - put("iqy", "text/x-ms-iqy") - put("isp", "application/x-internet-signup") - put("ite", "application/x-itunes-ite") - put("itlp", "application/x-itunes-itlp") - put("itms", "application/x-itunes-itms") - put("itpc", "application/x-itunes-itpc") - put("ivf", "video/x-ivf") - put("jar", "application/java-archive") - put("java", "application/octet-stream") - put("jck", "application/liquidmotion") - put("jcz", "application/liquidmotion") - put("jfif", "image/pjpeg") - put("jnlp", "application/x-java-jnlp-file") - put("jpb", "application/octet-stream") - put("jpe", "image/jpeg") - put("jpeg", "image/jpeg") - put("jpg", "image/jpeg") - put("js", "application/javascript") - put("json", "application/json") - put("jsx", "text/jscript") - put("jsxbin", "text/plain") - put("latex", "application/x-latex") - put("library-ms", "application/windows-library+xml") - put("lit", "application/x-ms-reader") - put("loadtest", "application/xml") - put("lpk", "application/octet-stream") - put("lsf", "video/x-la-asf") - put("lst", "text/plain") - put("lsx", "video/x-la-asf") - put("lzh", "application/octet-stream") - put("m13", "application/x-msmediaview") - put("m14", "application/x-msmediaview") - put("m1v", "video/mpeg") - put("m2t", "video/vnd.dlna.mpeg-tts") - put("m2ts", "video/vnd.dlna.mpeg-tts") - put("m2v", "video/mpeg") - put("m3u", "audio/x-mpegurl") - put("m3u8", "audio/x-mpegurl") - put("m4a", "audio/m4a") - put("m4b", "audio/m4b") - put("m4p", "audio/m4p") - put("m4r", "audio/x-m4r") - put("m4v", "video/x-m4v") - put("mac", "image/x-macpaint") - put("mak", "text/plain") - put("man", "application/x-troff-man") - put("manifest", "application/x-ms-manifest") - put("map", "text/plain") - put("master", "application/xml") - put("mda", "application/msaccess") - put("mdb", "application/x-msaccess") - put("mde", "application/msaccess") - put("mdp", "application/octet-stream") - put("me", "application/x-troff-me") - put("mfp", "application/x-shockwave-flash") - put("mht", "message/rfc822") - put("mhtml", "message/rfc822") - put("mid", "audio/mid") - put("midi", "audio/mid") - put("mix", "application/octet-stream") - put("mk", "text/plain") - put("mkv", "video/x-matroska") - put("mmf", "application/x-smaf") - put("mno", "text/xml") - put("mny", "application/x-msmoney") - put("mod", "video/mpeg") - put("mov", "video/quicktime") - put("movie", "video/x-sgi-movie") - put("mp2", "video/mpeg") - put("mp2v", "video/mpeg") - put("mp3", "audio/mpeg") - put("mp4", "video/mp4") - put("mp4v", "video/mp4") - put("mpa", "video/mpeg") - put("mpe", "video/mpeg") - put("mpeg", "video/mpeg") - put("mpf", "application/vnd.ms-mediapackage") - put("mpg", "video/mpeg") - put("mpp", "application/vnd.ms-project") - put("mpv2", "video/mpeg") - put("mqv", "video/quicktime") - put("ms", "application/x-troff-ms") - put("msi", "application/octet-stream") - put("mso", "application/octet-stream") - put("mts", "video/vnd.dlna.mpeg-tts") - put("mtx", "application/xml") - put("mvb", "application/x-msmediaview") - put("mvc", "application/x-miva-compiled") - put("mxp", "application/x-mmxp") - put("nc", "application/x-netcdf") - put("nsc", "video/x-ms-asf") - put("nws", "message/rfc822") - put("ocx", "application/octet-stream") - put("oda", "application/oda") - put("odb", "application/vnd.oasis.opendocument.database") - put("odc", "application/vnd.oasis.opendocument.chart") - put("odf", "application/vnd.oasis.opendocument.formula") - put("odg", "application/vnd.oasis.opendocument.graphics") - put("odh", "text/plain") - put("odi", "application/vnd.oasis.opendocument.image") - put("odl", "text/plain") - put("odm", "application/vnd.oasis.opendocument.text-master") - put("odp", "application/vnd.oasis.opendocument.presentation") - put("ods", "application/vnd.oasis.opendocument.spreadsheet") - put("odt", "application/vnd.oasis.opendocument.text") - put("oga", "audio/ogg") - put("ogg", "audio/ogg") - put("ogv", "video/ogg") - put("ogx", "application/ogg") - put("one", "application/onenote") - put("onea", "application/onenote") - put("onepkg", "application/onenote") - put("onetmp", "application/onenote") - put("onetoc", "application/onenote") - put("onetoc2", "application/onenote") - put("opus", "audio/ogg") - put("orderedtest", "application/xml") - put("osdx", "application/opensearchdescription+xml") - put("otf", "application/font-sfnt") - put("otg", "application/vnd.oasis.opendocument.graphics-template") - put("oth", "application/vnd.oasis.opendocument.text-web") - put("otp", "application/vnd.oasis.opendocument.presentation-template") - put("ots", "application/vnd.oasis.opendocument.spreadsheet-template") - put("ott", "application/vnd.oasis.opendocument.text-template") - put("oxt", "application/vnd.openofficeorg.extension") - put("p10", "application/pkcs10") - put("p12", "application/x-pkcs12") - put("p7b", "application/x-pkcs7-certificates") - put("p7c", "application/pkcs7-mime") - put("p7m", "application/pkcs7-mime") - put("p7r", "application/x-pkcs7-certreqresp") - put("p7s", "application/pkcs7-signature") - put("pbm", "image/x-portable-bitmap") - put("pcast", "application/x-podcast") - put("pct", "image/pict") - put("pcx", "application/octet-stream") - put("pcz", "application/octet-stream") - put("pdf", "application/pdf") - put("pfb", "application/octet-stream") - put("pfm", "application/octet-stream") - put("pfx", "application/x-pkcs12") - put("pgm", "image/x-portable-graymap") - put("php", "text/plain") - put("pic", "image/pict") - put("pict", "image/pict") - put("pkgdef", "text/plain") - put("pkgundef", "text/plain") - put("pko", "application/vnd.ms-pki.pko") - put("pls", "audio/scpls") - put("pma", "application/x-perfmon") - put("pmc", "application/x-perfmon") - put("pml", "application/x-perfmon") - put("pmr", "application/x-perfmon") - put("pmw", "application/x-perfmon") - put("png", "image/png") - put("pnm", "image/x-portable-anymap") - put("pnt", "image/x-macpaint") - put("pntg", "image/x-macpaint") - put("pnz", "image/png") - put("pot", "application/vnd.ms-powerpoint") - put("potm", "application/vnd.ms-powerpoint.template.macroEnabled.12") - put("potx", "application/vnd.openxmlformats-officedocument.presentationml.template") - put("ppa", "application/vnd.ms-powerpoint") - put("ppam", "application/vnd.ms-powerpoint.addin.macroEnabled.12") - put("ppm", "image/x-portable-pixmap") - put("pps", "application/vnd.ms-powerpoint") - put("ppsm", "application/vnd.ms-powerpoint.slideshow.macroEnabled.12") - put("ppsx", "application/vnd.openxmlformats-officedocument.presentationml.slideshow") - put("ppt", "application/vnd.ms-powerpoint") - put("pptm", "application/vnd.ms-powerpoint.presentation.macroEnabled.12") - put("pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation") - put("prf", "application/pics-rules") - put("prm", "application/octet-stream") - put("prx", "application/octet-stream") - put("ps", "application/postscript") - put("psc1", "application/PowerShell") - put("psd", "application/octet-stream") - put("psess", "application/xml") - put("psm", "application/octet-stream") - put("psp", "application/octet-stream") - put("pub", "application/x-mspublisher") - put("pwz", "application/vnd.ms-powerpoint") - put("py", "text/plain") - put("qht", "text/x-html-insertion") - put("qhtm", "text/x-html-insertion") - put("qt", "video/quicktime") - put("qti", "image/x-quicktime") - put("qtif", "image/x-quicktime") - put("qtl", "application/x-quicktimeplayer") - put("qxd", "application/octet-stream") - put("ra", "audio/x-pn-realaudio") - put("ram", "audio/x-pn-realaudio") - put("rar", "application/x-rar-compressed") - put("ras", "image/x-cmu-raster") - put("rat", "application/rat-file") - put("rb", "text/plain") - put("rc", "text/plain") - put("rc2", "text/plain") - put("rct", "text/plain") - put("rdlc", "application/xml") - put("reg", "text/plain") - put("resx", "application/xml") - put("rf", "image/vnd.rn-realflash") - put("rgb", "image/x-rgb") - put("rgs", "text/plain") - put("rm", "application/vnd.rn-realmedia") - put("rmi", "audio/mid") - put("rmp", "application/vnd.rn-rn_music_package") - put("roff", "application/x-troff") - put("rpm", "audio/x-pn-realaudio-plugin") - put("rqy", "text/x-ms-rqy") - put("rtf", "application/rtf") - put("rtx", "text/richtext") - put("ruleset", "application/xml") - put("s", "text/plain") - put("safariextz", "application/x-safari-safariextz") - put("scd", "application/x-msschedule") - put("scr", "text/plain") - put("sct", "text/scriptlet") - put("sd2", "audio/x-sd2") - put("sdp", "application/sdp") - put("sea", "application/octet-stream") - put("searchConnector-ms", "application/windows-search-connector+xml") - put("setpay", "application/set-payment-initiation") - put("setreg", "application/set-registration-initiation") - put("settings", "application/xml") - put("sgimb", "application/x-sgimb") - put("sgml", "text/sgml") - put("sh", "application/x-sh") - put("shar", "application/x-shar") - put("shtml", "text/html") - put("sit", "application/x-stuffit") - put("sitemap", "application/xml") - put("skin", "application/xml") - put("sldm", "application/vnd.ms-powerpoint.slide.macroEnabled.12") - put("sldx", "application/vnd.openxmlformats-officedocument.presentationml.slide") - put("slk", "application/vnd.ms-excel") - put("sln", "text/plain") - put("slupkg-ms", "application/x-ms-license") - put("smd", "audio/x-smd") - put("smi", "application/octet-stream") - put("smx", "audio/x-smd") - put("smz", "audio/x-smd") - put("snd", "audio/basic") - put("snippet", "application/xml") - put("snp", "application/octet-stream") - put("sol", "text/plain") - put("sor", "text/plain") - put("spc", "application/x-pkcs7-certificates") - put("spl", "application/futuresplash") - put("spx", "audio/ogg") - put("src", "application/x-wais-source") - put("srf", "text/plain") - put("ssisdeploymentmanifest", "text/xml") - put("ssm", "application/streamingmedia") - put("sst", "application/vnd.ms-pki.certstore") - put("stl", "application/vnd.ms-pki.stl") - put("sv4cpio", "application/x-sv4cpio") - put("sv4crc", "application/x-sv4crc") - put("svc", "application/xml") - put("svg", "image/svg+xml") - put("swf", "application/x-shockwave-flash") - put("t", "application/x-troff") - put("tar", "application/x-tar") - put("tcl", "application/x-tcl") - put("testrunconfig", "application/xml") - put("testsettings", "application/xml") - put("tex", "application/x-tex") - put("texi", "application/x-texinfo") - put("texinfo", "application/x-texinfo") - put("tgz", "application/x-compressed") - put("thmx", "application/vnd.ms-officetheme") - put("thn", "application/octet-stream") - put("tif", "image/tiff") - put("tiff", "image/tiff") - put("tlh", "text/plain") - put("tli", "text/plain") - put("toc", "application/octet-stream") - put("tr", "application/x-troff") - put("trm", "application/x-msterminal") - put("trx", "application/xml") - put("ts", "video/vnd.dlna.mpeg-tts") - put("tsv", "text/tab-separated-values") - put("ttf", "application/font-sfnt") - put("tts", "video/vnd.dlna.mpeg-tts") - put("txt", "text/plain") - put("u32", "application/octet-stream") - put("uls", "text/iuls") - put("user", "text/plain") - put("ustar", "application/x-ustar") - put("vb", "text/plain") - put("vbdproj", "text/plain") - put("vbk", "video/mpeg") - put("vbproj", "text/plain") - put("vbs", "text/vbscript") - put("vcf", "text/x-vcard") - put("vcproj", "application/xml") - put("vcs", "text/calendar") - put("vcxproj", "application/xml") - put("vddproj", "text/plain") - put("vdp", "text/plain") - put("vdproj", "text/plain") - put("vdx", "application/vnd.ms-visio.viewer") - put("vml", "text/xml") - put("vscontent", "application/xml") - put("vsct", "text/xml") - put("vsd", "application/vnd.visio") - put("vsi", "application/ms-vsi") - put("vsix", "application/vsix") - put("vsixlangpack", "text/xml") - put("vsixmanifest", "text/xml") - put("vsmdi", "application/xml") - put("vspscc", "text/plain") - put("vss", "application/vnd.visio") - put("vsscc", "text/plain") - put("vssettings", "text/xml") - put("vssscc", "text/plain") - put("vst", "application/vnd.visio") - put("vstemplate", "text/xml") - put("vsto", "application/x-ms-vsto") - put("vsw", "application/vnd.visio") - put("vsx", "application/vnd.visio") - put("vtx", "application/vnd.visio") - put("wav", "audio/wav") - put("wave", "audio/wav") - put("wax", "audio/x-ms-wax") - put("wbk", "application/msword") - put("wbmp", "image/vnd.wap.wbmp") - put("wcm", "application/vnd.ms-works") - put("wdb", "application/vnd.ms-works") - put("wdp", "image/vnd.ms-photo") - put("webarchive", "application/x-safari-webarchive") - put("webm", "video/webm") - put("webp", "image/webp") - put("webtest", "application/xml") - put("wiq", "application/xml") - put("wiz", "application/msword") - put("wks", "application/vnd.ms-works") - put("wlmp", "application/wlmoviemaker") - put("wlpginstall", "application/x-wlpg-detect") - put("wlpginstall3", "application/x-wlpg3-detect") - put("wm", "video/x-ms-wm") - put("wma", "audio/x-ms-wma") - put("wmd", "application/x-ms-wmd") - put("wmf", "application/x-msmetafile") - put("wml", "text/vnd.wap.wml") - put("wmlc", "application/vnd.wap.wmlc") - put("wmls", "text/vnd.wap.wmlscript") - put("wmlsc", "application/vnd.wap.wmlscriptc") - put("wmp", "video/x-ms-wmp") - put("wmv", "video/x-ms-wmv") - put("wmx", "video/x-ms-wmx") - put("wmz", "application/x-ms-wmz") - put("woff", "application/font-woff") - put("wpl", "application/vnd.ms-wpl") - put("wps", "application/vnd.ms-works") - put("wri", "application/x-mswrite") - put("wrl", "x-world/x-vrml") - put("wrz", "x-world/x-vrml") - put("wsc", "text/scriptlet") - put("wsdl", "text/xml") - put("wvx", "video/x-ms-wvx") - put("x", "application/directx") - put("xaf", "x-world/x-vrml") - put("xaml", "application/xaml+xml") - put("xap", "application/x-silverlight-app") - put("xbap", "application/x-ms-xbap") - put("xbm", "image/x-xbitmap") - put("xdr", "text/plain") - put("xht", "application/xhtml+xml") - put("xhtml", "application/xhtml+xml") - put("xla", "application/vnd.ms-excel") - put("xlam", "application/vnd.ms-excel.addin.macroEnabled.12") - put("xlc", "application/vnd.ms-excel") - put("xld", "application/vnd.ms-excel") - put("xlk", "application/vnd.ms-excel") - put("xll", "application/vnd.ms-excel") - put("xlm", "application/vnd.ms-excel") - put("xls", "application/vnd.ms-excel") - put("xlsb", "application/vnd.ms-excel.sheet.binary.macroEnabled.12") - put("xlsm", "application/vnd.ms-excel.sheet.macroEnabled.12") - put("xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") - put("xlt", "application/vnd.ms-excel") - put("xltm", "application/vnd.ms-excel.template.macroEnabled.12") - put("xltx", "application/vnd.openxmlformats-officedocument.spreadsheetml.template") - put("xlw", "application/vnd.ms-excel") - put("xml", "text/xml") - put("xmta", "application/xml") - put("xof", "x-world/x-vrml") - put("xoml", "text/plain") - put("xpm", "image/x-xpixmap") - put("xps", "application/vnd.ms-xpsdocument") - put("xrm-ms", "text/xml") - put("xsc", "application/xml") - put("xsd", "text/xml") - put("xsf", "text/xml") - put("xsl", "text/xml") - put("xslt", "text/xml") - put("xsn", "application/octet-stream") - put("xss", "application/xml") - put("xspf", "application/xspf+xml") - put("xtp", "application/octet-stream") - put("xwd", "image/x-xwindowdump") - put("z", "application/x-compress") - put("zip", "application/zip") - } - - return typesMap[getFilenameExtension().toLowerCase()] ?: "" -} diff --git a/app/src/main/java/be/scri/extensions/TabLayout.kt b/app/src/main/java/be/scri/extensions/TabLayout.kt deleted file mode 100644 index c4ae1050..00000000 --- a/app/src/main/java/be/scri/extensions/TabLayout.kt +++ /dev/null @@ -1,21 +0,0 @@ -package be.scri.extensions - -import com.google.android.material.tabs.TabLayout - -fun TabLayout.onTabSelectionChanged( - tabUnselectedAction: ((inactiveTab: TabLayout.Tab) -> Unit)? = null, - tabSelectedAction: ((activeTab: TabLayout.Tab) -> Unit)? = null, -) = setOnTabSelectedListener( - object : TabLayout.OnTabSelectedListener { - override fun onTabSelected(tab: TabLayout.Tab) { - tabSelectedAction?.invoke(tab) - } - - override fun onTabUnselected(tab: TabLayout.Tab) { - tabUnselectedAction?.invoke(tab) - } - - override fun onTabReselected(tab: TabLayout.Tab) { - } - }, -) diff --git a/app/src/main/java/be/scri/extensions/TextView.kt b/app/src/main/java/be/scri/extensions/TextView.kt deleted file mode 100644 index 62794e29..00000000 --- a/app/src/main/java/be/scri/extensions/TextView.kt +++ /dev/null @@ -1,31 +0,0 @@ -package be.scri.extensions - -import android.graphics.Paint -import android.text.SpannableString -import android.text.TextPaint -import android.text.style.URLSpan -import android.widget.TextView - -val TextView.value: String get() = text.toString().trim() - -fun TextView.underlineText() { - paintFlags = paintFlags or Paint.UNDERLINE_TEXT_FLAG -} - -fun TextView.removeUnderlines() { - val spannable = SpannableString(text) - for (u in spannable.getSpans(0, spannable.length, URLSpan::class.java)) { - spannable.setSpan( - object : URLSpan(u.url) { - override fun updateDrawState(textPaint: TextPaint) { - super.updateDrawState(textPaint) - textPaint.isUnderlineText = false - } - }, - spannable.getSpanStart(u), - spannable.getSpanEnd(u), - 0, - ) - } - text = spannable -} diff --git a/app/src/main/java/be/scri/extensions/View.kt b/app/src/main/java/be/scri/extensions/View.kt index 27e1cfd4..7c45ea8c 100644 --- a/app/src/main/java/be/scri/extensions/View.kt +++ b/app/src/main/java/be/scri/extensions/View.kt @@ -2,19 +2,11 @@ package be.scri.extensions import android.view.HapticFeedbackConstants import android.view.View -import android.view.ViewTreeObserver -import be.scri.helpers.SHORT_ANIMATION_DURATION - -fun View.beInvisibleIf(beInvisible: Boolean) = if (beInvisible) beInvisible() else beVisible() fun View.beVisibleIf(beVisible: Boolean) = if (beVisible) beVisible() else beGone() fun View.beGoneIf(beGone: Boolean) = beVisibleIf(!beGone) -fun View.beInvisible() { - visibility = View.INVISIBLE -} - fun View.beVisible() { visibility = View.VISIBLE } @@ -23,37 +15,4 @@ fun View.beGone() { visibility = View.GONE } -fun View.onGlobalLayout(callback: () -> Unit) { - viewTreeObserver.addOnGlobalLayoutListener( - object : ViewTreeObserver.OnGlobalLayoutListener { - override fun onGlobalLayout() { - viewTreeObserver.removeOnGlobalLayoutListener(this) - callback() - } - }, - ) -} - -fun View.isVisible() = visibility == View.VISIBLE - -fun View.isInvisible() = visibility == View.INVISIBLE - -fun View.isGone() = visibility == View.GONE - fun View.performHapticFeedback() = performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY) - -fun View.fadeIn() { - animate() - .alpha(1f) - .setDuration(SHORT_ANIMATION_DURATION) - .withStartAction { beVisible() } - .start() -} - -fun View.fadeOut() { - animate() - .alpha(0f) - .setDuration(SHORT_ANIMATION_DURATION) - .withEndAction { beGone() } - .start() -} diff --git a/app/src/main/java/be/scri/extensions/ViewPager.kt b/app/src/main/java/be/scri/extensions/ViewPager.kt deleted file mode 100644 index ed1474bc..00000000 --- a/app/src/main/java/be/scri/extensions/ViewPager.kt +++ /dev/null @@ -1,20 +0,0 @@ -package be.scri.extensions - -import androidx.viewpager.widget.ViewPager - -fun ViewPager.onPageChangeListener(pageChangedAction: (newPosition: Int) -> Unit) = - addOnPageChangeListener( - object : ViewPager.OnPageChangeListener { - override fun onPageScrollStateChanged(state: Int) {} - - override fun onPageScrolled( - position: Int, - positionOffset: Float, - positionOffsetPixels: Int, - ) {} - - override fun onPageSelected(position: Int) { - pageChangedAction(position) - } - }, - ) diff --git a/app/src/main/java/be/scri/fragments/PrivacyPolicyFragment.kt b/app/src/main/java/be/scri/fragments/PrivacyPolicyFragment.kt index 397bf87e..407343ae 100644 --- a/app/src/main/java/be/scri/fragments/PrivacyPolicyFragment.kt +++ b/app/src/main/java/be/scri/fragments/PrivacyPolicyFragment.kt @@ -21,7 +21,6 @@ class PrivacyPolicyFragment : Fragment() { super.onCreate(savedInstanceState) val viewpager = requireActivity().findViewById(R.id.view_pager) - val frameLayout = requireActivity().findViewById(R.id.fragment_container) val callback = requireActivity().onBackPressedDispatcher.addCallback(this) { viewpager.setCurrentItem(2, true) @@ -50,7 +49,7 @@ class PrivacyPolicyFragment : Fragment() { inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?, - ): View? { + ): View { binding = FragmentPrivacyPolicyBinding.inflate(inflater, container, false) (requireActivity() as MainActivity).showFragmentContainer() (requireActivity() as MainActivity).setActionBarTitle(R.string.app_about_legal_privacy_policy) @@ -62,7 +61,6 @@ class PrivacyPolicyFragment : Fragment() { val viewpager = requireActivity().findViewById(R.id.view_pager) val frameLayout = requireActivity().findViewById(R.id.fragment_container) (requireActivity() as MainActivity).setActionBarTitle(R.string.app_about_title) - val textView = (requireActivity() as MainActivity).supportActionBar?.customView?.findViewById(R.id.name) (requireActivity() as MainActivity).setActionBarButtonInvisible() (requireActivity() as MainActivity).unsetActionBarLayoutMargin() if (viewpager.currentItem == 2) { diff --git a/app/src/main/java/be/scri/fragments/ThirdPartyFragment.kt b/app/src/main/java/be/scri/fragments/ThirdPartyFragment.kt index a80f46f3..af50eb99 100644 --- a/app/src/main/java/be/scri/fragments/ThirdPartyFragment.kt +++ b/app/src/main/java/be/scri/fragments/ThirdPartyFragment.kt @@ -41,7 +41,7 @@ class ThirdPartyFragment : Fragment() { inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?, - ): View? { + ): View { binding = FragmentThirdPartyBinding.inflate(inflater, container, false) (requireActivity() as MainActivity).showFragmentContainer() (requireActivity() as MainActivity).setActionBarTitle(R.string.app_about_legal_third_party) diff --git a/app/src/main/java/be/scri/fragments/WikimediaScribeFragment.kt b/app/src/main/java/be/scri/fragments/WikimediaScribeFragment.kt index 3fd602ac..3c3cf245 100644 --- a/app/src/main/java/be/scri/fragments/WikimediaScribeFragment.kt +++ b/app/src/main/java/be/scri/fragments/WikimediaScribeFragment.kt @@ -41,7 +41,7 @@ class WikimediaScribeFragment : Fragment() { inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?, - ): View? { + ): View { binding = FragmentWikimediaScribeBinding.inflate(inflater, container, false) (requireActivity() as MainActivity).showFragmentContainer() requireActivity().onBackPressedDispatcher.addCallback( diff --git a/app/src/main/java/be/scri/helpers/BaseConfig.kt b/app/src/main/java/be/scri/helpers/BaseConfig.kt index c9b9f909..185edeaa 100644 --- a/app/src/main/java/be/scri/helpers/BaseConfig.kt +++ b/app/src/main/java/be/scri/helpers/BaseConfig.kt @@ -1,15 +1,8 @@ package be.scri.helpers import android.content.Context -import android.text.format.DateFormat import be.scri.R -import be.scri.extensions.getInternalStoragePath -import be.scri.extensions.getSDCardPath import be.scri.extensions.getSharedPrefs -import java.text.SimpleDateFormat -import java.util.Calendar -import java.util.LinkedList -import java.util.Locale open class BaseConfig( val context: Context, @@ -20,66 +13,6 @@ open class BaseConfig( fun newInstance(context: Context) = BaseConfig(context) } - var appRunCount: Int - get() = prefs.getInt(APP_RUN_COUNT, 0) - set(appRunCount) = prefs.edit().putInt(APP_RUN_COUNT, appRunCount).apply() - - var lastVersion: Int - get() = prefs.getInt(LAST_VERSION, 0) - set(lastVersion) = prefs.edit().putInt(LAST_VERSION, lastVersion).apply() - - var primaryAndroidDataTreeUri: String - get() = prefs.getString(PRIMARY_ANDROID_DATA_TREE_URI, "")!! - set(uri) = prefs.edit().putString(PRIMARY_ANDROID_DATA_TREE_URI, uri).apply() - - var sdAndroidDataTreeUri: String - get() = prefs.getString(SD_ANDROID_DATA_TREE_URI, "")!! - set(uri) = prefs.edit().putString(SD_ANDROID_DATA_TREE_URI, uri).apply() - - var otgAndroidDataTreeUri: String - get() = prefs.getString(OTG_ANDROID_DATA_TREE_URI, "")!! - set(uri) = prefs.edit().putString(OTG_ANDROID_DATA_TREE_URI, uri).apply() - - var primaryAndroidObbTreeUri: String - get() = prefs.getString(PRIMARY_ANDROID_OBB_TREE_URI, "")!! - set(uri) = prefs.edit().putString(PRIMARY_ANDROID_OBB_TREE_URI, uri).apply() - - var sdAndroidObbTreeUri: String - get() = prefs.getString(SD_ANDROID_OBB_TREE_URI, "")!! - set(uri) = prefs.edit().putString(SD_ANDROID_OBB_TREE_URI, uri).apply() - - var otgAndroidObbTreeUri: String - get() = prefs.getString(OTG_ANDROID_OBB_TREE_URI, "")!! - set(uri) = prefs.edit().putString(OTG_ANDROID_OBB_TREE_URI, uri).apply() - - var sdTreeUri: String - get() = prefs.getString(SD_TREE_URI, "")!! - set(uri) = prefs.edit().putString(SD_TREE_URI, uri).apply() - - var otgTreeUri: String - get() = prefs.getString(OTG_TREE_URI, "")!! - set(OTGTreeUri) = prefs.edit().putString(OTG_TREE_URI, OTGTreeUri).apply() - - var otgPartition: String - get() = prefs.getString(OTG_PARTITION, "")!! - set(OTGPartition) = prefs.edit().putString(OTG_PARTITION, OTGPartition).apply() - - var otgPath: String - get() = prefs.getString(OTG_REAL_PATH, "")!! - set(OTGPath) = prefs.edit().putString(OTG_REAL_PATH, OTGPath).apply() - - var sdCardPath: String - get() = prefs.getString(SD_CARD_PATH, getDefaultSDCardPath())!! - set(sdCardPath) = prefs.edit().putString(SD_CARD_PATH, sdCardPath).apply() - - private fun getDefaultSDCardPath() = if (prefs.contains(SD_CARD_PATH)) "" else context.getSDCardPath() - - var internalStoragePath: String - get() = prefs.getString(INTERNAL_STORAGE_PATH, getDefaultInternalPath())!! - set(internalStoragePath) = prefs.edit().putString(INTERNAL_STORAGE_PATH, internalStoragePath).apply() - - private fun getDefaultInternalPath() = if (prefs.contains(INTERNAL_STORAGE_PATH)) "" else context.getInternalStoragePath() - var textColor: Int get() = prefs.getInt(TEXT_COLOR, context.resources.getColor(R.color.default_text_color)) set(textColor) = prefs.edit().putInt(TEXT_COLOR, textColor).apply() @@ -100,131 +33,6 @@ open class BaseConfig( get() = prefs.getInt(ACCENT_COLOR, context.resources.getColor(R.color.color_primary)) set(accentColor) = prefs.edit().putInt(ACCENT_COLOR, accentColor).apply() - var navigationBarColor: Int - get() = prefs.getInt(NAVIGATION_BAR_COLOR, INVALID_NAVIGATION_BAR_COLOR) - set(navigationBarColor) = prefs.edit().putInt(NAVIGATION_BAR_COLOR, navigationBarColor).apply() - - var defaultNavigationBarColor: Int - get() = prefs.getInt(DEFAULT_NAVIGATION_BAR_COLOR, INVALID_NAVIGATION_BAR_COLOR) - set(defaultNavigationBarColor) = prefs.edit().putInt(DEFAULT_NAVIGATION_BAR_COLOR, defaultNavigationBarColor).apply() - - var lastHandledShortcutColor: Int - get() = prefs.getInt(LAST_HANDLED_SHORTCUT_COLOR, 1) - set(lastHandledShortcutColor) = prefs.edit().putInt(LAST_HANDLED_SHORTCUT_COLOR, lastHandledShortcutColor).apply() - - var appIconColor: Int - get() = prefs.getInt(APP_ICON_COLOR, context.resources.getColor(R.color.color_primary)) - set(appIconColor) { - isUsingModifiedAppIcon = appIconColor != context.resources.getColor(R.color.color_primary) - prefs.edit().putInt(APP_ICON_COLOR, appIconColor).apply() - } - - var lastIconColor: Int - get() = prefs.getInt(LAST_ICON_COLOR, context.resources.getColor(R.color.color_primary)) - set(lastIconColor) = prefs.edit().putInt(LAST_ICON_COLOR, lastIconColor).apply() - - var customTextColor: Int - get() = prefs.getInt(CUSTOM_TEXT_COLOR, textColor) - set(customTextColor) = prefs.edit().putInt(CUSTOM_TEXT_COLOR, customTextColor).apply() - - var customBackgroundColor: Int - get() = prefs.getInt(CUSTOM_BACKGROUND_COLOR, backgroundColor) - set(customBackgroundColor) = prefs.edit().putInt(CUSTOM_BACKGROUND_COLOR, customBackgroundColor).apply() - - var customPrimaryColor: Int - get() = prefs.getInt(CUSTOM_PRIMARY_COLOR, primaryColor) - set(customPrimaryColor) = prefs.edit().putInt(CUSTOM_PRIMARY_COLOR, customPrimaryColor).apply() - - var customAccentColor: Int - get() = prefs.getInt(CUSTOM_ACCENT_COLOR, accentColor) - set(customAccentColor) = prefs.edit().putInt(CUSTOM_ACCENT_COLOR, customAccentColor).apply() - - var customAppIconColor: Int - get() = prefs.getInt(CUSTOM_APP_ICON_COLOR, appIconColor) - set(customAppIconColor) = prefs.edit().putInt(CUSTOM_APP_ICON_COLOR, customAppIconColor).apply() - - var customNavigationBarColor: Int - get() = prefs.getInt(CUSTOM_NAVIGATION_BAR_COLOR, INVALID_NAVIGATION_BAR_COLOR) - set(customNavigationBarColor) = prefs.edit().putInt(CUSTOM_NAVIGATION_BAR_COLOR, customNavigationBarColor).apply() - - var widgetBgColor: Int - get() = prefs.getInt(WIDGET_BG_COLOR, DEFAULT_WIDGET_BG_COLOR) - set(widgetBgColor) = prefs.edit().putInt(WIDGET_BG_COLOR, widgetBgColor).apply() - - var widgetTextColor: Int - get() = prefs.getInt(WIDGET_TEXT_COLOR, context.resources.getColor(R.color.color_primary)) - set(widgetTextColor) = prefs.edit().putInt(WIDGET_TEXT_COLOR, widgetTextColor).apply() - - // hidden folder visibility protection - var isHiddenPasswordProtectionOn: Boolean - get() = prefs.getBoolean(PASSWORD_PROTECTION, false) - set(isHiddenPasswordProtectionOn) = prefs.edit().putBoolean(PASSWORD_PROTECTION, isHiddenPasswordProtectionOn).apply() - - var hiddenPasswordHash: String - get() = prefs.getString(PASSWORD_HASH, "")!! - set(hiddenPasswordHash) = prefs.edit().putString(PASSWORD_HASH, hiddenPasswordHash).apply() - - var hiddenProtectionType: Int - get() = prefs.getInt(PROTECTION_TYPE, PROTECTION_PATTERN) - set(hiddenProtectionType) = prefs.edit().putInt(PROTECTION_TYPE, hiddenProtectionType).apply() - - // whole app launch protection - var isAppPasswordProtectionOn: Boolean - get() = prefs.getBoolean(APP_PASSWORD_PROTECTION, false) - set(isAppPasswordProtectionOn) = prefs.edit().putBoolean(APP_PASSWORD_PROTECTION, isAppPasswordProtectionOn).apply() - - var appPasswordHash: String - get() = prefs.getString(APP_PASSWORD_HASH, "")!! - set(appPasswordHash) = prefs.edit().putString(APP_PASSWORD_HASH, appPasswordHash).apply() - - var appProtectionType: Int - get() = prefs.getInt(APP_PROTECTION_TYPE, PROTECTION_PATTERN) - set(appProtectionType) = prefs.edit().putInt(APP_PROTECTION_TYPE, appProtectionType).apply() - - // file delete and move protection - var isDeletePasswordProtectionOn: Boolean - get() = prefs.getBoolean(DELETE_PASSWORD_PROTECTION, false) - set(isDeletePasswordProtectionOn) = prefs.edit().putBoolean(DELETE_PASSWORD_PROTECTION, isDeletePasswordProtectionOn).apply() - - var deletePasswordHash: String - get() = prefs.getString(DELETE_PASSWORD_HASH, "")!! - set(deletePasswordHash) = prefs.edit().putString(DELETE_PASSWORD_HASH, deletePasswordHash).apply() - - var deleteProtectionType: Int - get() = prefs.getInt(DELETE_PROTECTION_TYPE, PROTECTION_PATTERN) - set(deleteProtectionType) = prefs.edit().putInt(DELETE_PROTECTION_TYPE, deleteProtectionType).apply() - - // folder locking - fun addFolderProtection( - path: String, - hash: String, - type: Int, - ) { - prefs - .edit() - .putString("$PROTECTED_FOLDER_HASH$path", hash) - .putInt("$PROTECTED_FOLDER_TYPE$path", type) - .apply() - } - - fun removeFolderProtection(path: String) { - prefs - .edit() - .remove("$PROTECTED_FOLDER_HASH$path") - .remove("$PROTECTED_FOLDER_TYPE$path") - .apply() - } - - fun isFolderProtected(path: String) = getFolderProtectionType(path) != PROTECTION_NONE - - fun getFolderProtectionHash(path: String) = prefs.getString("$PROTECTED_FOLDER_HASH$path", "") ?: "" - - fun getFolderProtectionType(path: String) = prefs.getInt("$PROTECTED_FOLDER_TYPE$path", PROTECTION_NONE) - - var keepLastModified: Boolean - get() = prefs.getBoolean(KEEP_LAST_MODIFIED, true) - set(keepLastModified) = prefs.edit().putBoolean(KEEP_LAST_MODIFIED, keepLastModified).apply() - var useEnglish: Boolean get() = prefs.getBoolean(USE_ENGLISH, false) set(useEnglish) { @@ -236,283 +44,7 @@ open class BaseConfig( get() = prefs.getBoolean(WAS_USE_ENGLISH_TOGGLED, false) set(wasUseEnglishToggled) = prefs.edit().putBoolean(WAS_USE_ENGLISH_TOGGLED, wasUseEnglishToggled).apply() - var wasSharedThemeEverActivated: Boolean - get() = prefs.getBoolean(WAS_SHARED_THEME_EVER_ACTIVATED, false) - set(wasSharedThemeEverActivated) = prefs.edit().putBoolean(WAS_SHARED_THEME_EVER_ACTIVATED, wasSharedThemeEverActivated).apply() - - var isUsingSharedTheme: Boolean - get() = prefs.getBoolean(IS_USING_SHARED_THEME, false) - set(isUsingSharedTheme) = prefs.edit().putBoolean(IS_USING_SHARED_THEME, isUsingSharedTheme).apply() - - // used by Simple Thank You, stop using shared Shared Theme if it has been changed in it - var shouldUseSharedTheme: Boolean - get() = prefs.getBoolean(SHOULD_USE_SHARED_THEME, false) - set(shouldUseSharedTheme) = prefs.edit().putBoolean(SHOULD_USE_SHARED_THEME, shouldUseSharedTheme).apply() - - var isUsingAutoTheme: Boolean - get() = prefs.getBoolean(IS_USING_AUTO_THEME, false) - set(isUsingAutoTheme) = prefs.edit().putBoolean(IS_USING_AUTO_THEME, isUsingAutoTheme).apply() - var isUsingSystemTheme: Boolean get() = prefs.getBoolean(IS_USING_SYSTEM_THEME, false) set(isUsingSystemTheme) = prefs.edit().putBoolean(IS_USING_SYSTEM_THEME, isUsingSystemTheme).apply() - - var wasCustomThemeSwitchDescriptionShown: Boolean - get() = prefs.getBoolean(WAS_CUSTOM_THEME_SWITCH_DESCRIPTION_SHOWN, false) - set(wasCustomThemeSwitchDescriptionShown) = - prefs - .edit() - .putBoolean(WAS_CUSTOM_THEME_SWITCH_DESCRIPTION_SHOWN, wasCustomThemeSwitchDescriptionShown) - .apply() - - var wasSharedThemeForced: Boolean - get() = prefs.getBoolean(WAS_SHARED_THEME_FORCED, false) - set(wasSharedThemeForced) = prefs.edit().putBoolean(WAS_SHARED_THEME_FORCED, wasSharedThemeForced).apply() - - var showInfoBubble: Boolean - get() = prefs.getBoolean(SHOW_INFO_BUBBLE, true) - set(showInfoBubble) = prefs.edit().putBoolean(SHOW_INFO_BUBBLE, showInfoBubble).apply() - - var lastConflictApplyToAll: Boolean - get() = prefs.getBoolean(LAST_CONFLICT_APPLY_TO_ALL, true) - set(lastConflictApplyToAll) = prefs.edit().putBoolean(LAST_CONFLICT_APPLY_TO_ALL, lastConflictApplyToAll).apply() - - var lastConflictResolution: Int - get() = prefs.getInt(LAST_CONFLICT_RESOLUTION, CONFLICT_SKIP) - set(lastConflictResolution) = prefs.edit().putInt(LAST_CONFLICT_RESOLUTION, lastConflictResolution).apply() - - var sorting: Int - get() = prefs.getInt(SORT_ORDER, context.resources.getInteger(R.integer.default_sorting)) - set(sorting) = prefs.edit().putInt(SORT_ORDER, sorting).apply() - - fun saveCustomSorting( - path: String, - value: Int, - ) { - if (path.isEmpty()) { - sorting = value - } else { - prefs.edit().putInt(SORT_FOLDER_PREFIX + path.toLowerCase(), value).apply() - } - } - - fun getFolderSorting(path: String) = prefs.getInt(SORT_FOLDER_PREFIX + path.toLowerCase(), sorting) - - fun removeCustomSorting(path: String) { - prefs.edit().remove(SORT_FOLDER_PREFIX + path.toLowerCase()).apply() - } - - fun hasCustomSorting(path: String) = prefs.contains(SORT_FOLDER_PREFIX + path.toLowerCase()) - - var hadThankYouInstalled: Boolean - get() = prefs.getBoolean(HAD_THANK_YOU_INSTALLED, false) - set(hadThankYouInstalled) = prefs.edit().putBoolean(HAD_THANK_YOU_INSTALLED, hadThankYouInstalled).apply() - - var skipDeleteConfirmation: Boolean - get() = prefs.getBoolean(SKIP_DELETE_CONFIRMATION, false) - set(skipDeleteConfirmation) = prefs.edit().putBoolean(SKIP_DELETE_CONFIRMATION, skipDeleteConfirmation).apply() - - var enablePullToRefresh: Boolean - get() = prefs.getBoolean(ENABLE_PULL_TO_REFRESH, true) - set(enablePullToRefresh) = prefs.edit().putBoolean(ENABLE_PULL_TO_REFRESH, enablePullToRefresh).apply() - - var scrollHorizontally: Boolean - get() = prefs.getBoolean(SCROLL_HORIZONTALLY, false) - set(scrollHorizontally) = prefs.edit().putBoolean(SCROLL_HORIZONTALLY, scrollHorizontally).apply() - - var preventPhoneFromSleeping: Boolean - get() = prefs.getBoolean(PREVENT_PHONE_FROM_SLEEPING, true) - set(preventPhoneFromSleeping) = prefs.edit().putBoolean(PREVENT_PHONE_FROM_SLEEPING, preventPhoneFromSleeping).apply() - - var lastUsedViewPagerPage: Int - get() = prefs.getInt(LAST_USED_VIEW_PAGER_PAGE, context.resources.getInteger(R.integer.default_viewpager_page)) - set(lastUsedViewPagerPage) = prefs.edit().putInt(LAST_USED_VIEW_PAGER_PAGE, lastUsedViewPagerPage).apply() - - var use24HourFormat: Boolean - get() = prefs.getBoolean(USE_24_HOUR_FORMAT, DateFormat.is24HourFormat(context)) - set(use24HourFormat) = prefs.edit().putBoolean(USE_24_HOUR_FORMAT, use24HourFormat).apply() - - var isSundayFirst: Boolean - get() { - val isSundayFirst = Calendar.getInstance(Locale.getDefault()).firstDayOfWeek == Calendar.SUNDAY - return prefs.getBoolean(SUNDAY_FIRST, isSundayFirst) - } - set(sundayFirst) = prefs.edit().putBoolean(SUNDAY_FIRST, sundayFirst).apply() - - var wasAlarmWarningShown: Boolean - get() = prefs.getBoolean(WAS_ALARM_WARNING_SHOWN, false) - set(wasAlarmWarningShown) = prefs.edit().putBoolean(WAS_ALARM_WARNING_SHOWN, wasAlarmWarningShown).apply() - - var wasReminderWarningShown: Boolean - get() = prefs.getBoolean(WAS_REMINDER_WARNING_SHOWN, false) - set(wasReminderWarningShown) = prefs.edit().putBoolean(WAS_REMINDER_WARNING_SHOWN, wasReminderWarningShown).apply() - - var useSameSnooze: Boolean - get() = prefs.getBoolean(USE_SAME_SNOOZE, true) - set(useSameSnooze) = prefs.edit().putBoolean(USE_SAME_SNOOZE, useSameSnooze).apply() - - var snoozeTime: Int - get() = prefs.getInt(SNOOZE_TIME, 10) - set(snoozeDelay) = prefs.edit().putInt(SNOOZE_TIME, snoozeDelay).apply() - - var vibrateOnButtonPress: Boolean - get() = prefs.getBoolean(VIBRATE_ON_BUTTON_PRESS, context.resources.getBoolean(R.bool.default_vibrate_on_press)) - set(vibrateOnButton) = prefs.edit().putBoolean(VIBRATE_ON_BUTTON_PRESS, vibrateOnButton).apply() - - var yourAlarmSounds: String - get() = prefs.getString(YOUR_ALARM_SOUNDS, "")!! - set(yourAlarmSounds) = prefs.edit().putString(YOUR_ALARM_SOUNDS, yourAlarmSounds).apply() - - var isUsingModifiedAppIcon: Boolean - get() = prefs.getBoolean(IS_USING_MODIFIED_APP_ICON, false) - set(isUsingModifiedAppIcon) = prefs.edit().putBoolean(IS_USING_MODIFIED_APP_ICON, isUsingModifiedAppIcon).apply() - - var appId: String - get() = prefs.getString(APP_ID, "")!! - set(appId) = prefs.edit().putString(APP_ID, appId).apply() - - var initialWidgetHeight: Int - get() = prefs.getInt(INITIAL_WIDGET_HEIGHT, 0) - set(initialWidgetHeight) = prefs.edit().putInt(INITIAL_WIDGET_HEIGHT, initialWidgetHeight).apply() - - var widgetIdToMeasure: Int - get() = prefs.getInt(WIDGET_ID_TO_MEASURE, 0) - set(widgetIdToMeasure) = prefs.edit().putInt(WIDGET_ID_TO_MEASURE, widgetIdToMeasure).apply() - - var wasOrangeIconChecked: Boolean - get() = prefs.getBoolean(WAS_ORANGE_ICON_CHECKED, false) - set(wasOrangeIconChecked) = prefs.edit().putBoolean(WAS_ORANGE_ICON_CHECKED, wasOrangeIconChecked).apply() - - var wasAppOnSDShown: Boolean - get() = prefs.getBoolean(WAS_APP_ON_SD_SHOWN, false) - set(wasAppOnSDShown) = prefs.edit().putBoolean(WAS_APP_ON_SD_SHOWN, wasAppOnSDShown).apply() - - var wasBeforeAskingShown: Boolean - get() = prefs.getBoolean(WAS_BEFORE_ASKING_SHOWN, false) - set(wasBeforeAskingShown) = prefs.edit().putBoolean(WAS_BEFORE_ASKING_SHOWN, wasBeforeAskingShown).apply() - - var wasBeforeRateShown: Boolean - get() = prefs.getBoolean(WAS_BEFORE_RATE_SHOWN, false) - set(wasBeforeRateShown) = prefs.edit().putBoolean(WAS_BEFORE_RATE_SHOWN, wasBeforeRateShown).apply() - - var wasInitialUpgradeToProShown: Boolean - get() = prefs.getBoolean(WAS_INITIAL_UPGRADE_TO_PRO_SHOWN, false) - set(wasInitialUpgradeToProShown) = prefs.edit().putBoolean(WAS_INITIAL_UPGRADE_TO_PRO_SHOWN, wasInitialUpgradeToProShown).apply() - - var wasAppIconCustomizationWarningShown: Boolean - get() = prefs.getBoolean(WAS_APP_ICON_CUSTOMIZATION_WARNING_SHOWN, false) - set(wasAppIconCustomizationWarningShown) = - prefs - .edit() - .putBoolean(WAS_APP_ICON_CUSTOMIZATION_WARNING_SHOWN, wasAppIconCustomizationWarningShown) - .apply() - - var appSideloadingStatus: Int - get() = prefs.getInt(APP_SIDELOADING_STATUS, SIDELOADING_UNCHECKED) - set(appSideloadingStatus) = prefs.edit().putInt(APP_SIDELOADING_STATUS, appSideloadingStatus).apply() - - var dateFormat: String - get() = prefs.getString(DATE_FORMAT, getDefaultDateFormat())!! - set(dateFormat) = prefs.edit().putString(DATE_FORMAT, dateFormat).apply() - - private fun getDefaultDateFormat(): String { - val format = DateFormat.getDateFormat(context) - val pattern = (format as SimpleDateFormat).toLocalizedPattern() - return when (pattern.toLowerCase().replace(" ", "")) { - "d.M.y" -> DATE_FORMAT_ONE - "dd/mm/y" -> DATE_FORMAT_TWO - "mm/dd/y" -> DATE_FORMAT_THREE - "y-mm-dd" -> DATE_FORMAT_FOUR - "dmmmmy" -> DATE_FORMAT_FIVE - "mmmmdy" -> DATE_FORMAT_SIX - "mm-dd-y" -> DATE_FORMAT_SEVEN - "dd-mm-y" -> DATE_FORMAT_EIGHT - else -> DATE_FORMAT_ONE - } - } - - var wasOTGHandled: Boolean - get() = prefs.getBoolean(WAS_OTG_HANDLED, false) - set(wasOTGHandled) = prefs.edit().putBoolean(WAS_OTG_HANDLED, wasOTGHandled).apply() - - var wasUpgradedFromFreeShown: Boolean - get() = prefs.getBoolean(WAS_UPGRADED_FROM_FREE_SHOWN, false) - set(wasUpgradedFromFreeShown) = prefs.edit().putBoolean(WAS_UPGRADED_FROM_FREE_SHOWN, wasUpgradedFromFreeShown).apply() - - var wasRateUsPromptShown: Boolean - get() = prefs.getBoolean(WAS_RATE_US_PROMPT_SHOWN, false) - set(wasRateUsPromptShown) = prefs.edit().putBoolean(WAS_RATE_US_PROMPT_SHOWN, wasRateUsPromptShown).apply() - - var wasAppRated: Boolean - get() = prefs.getBoolean(WAS_APP_RATED, false) - set(wasAppRated) = prefs.edit().putBoolean(WAS_APP_RATED, wasAppRated).apply() - - var wasSortingByNumericValueAdded: Boolean - get() = prefs.getBoolean(WAS_SORTING_BY_NUMERIC_VALUE_ADDED, false) - set(wasSortingByNumericValueAdded) = prefs.edit().putBoolean(WAS_SORTING_BY_NUMERIC_VALUE_ADDED, wasSortingByNumericValueAdded).apply() - - var wasFolderLockingNoticeShown: Boolean - get() = prefs.getBoolean(WAS_FOLDER_LOCKING_NOTICE_SHOWN, false) - set(wasFolderLockingNoticeShown) = prefs.edit().putBoolean(WAS_FOLDER_LOCKING_NOTICE_SHOWN, wasFolderLockingNoticeShown).apply() - - var lastRenameUsed: Int - get() = prefs.getInt(LAST_RENAME_USED, RENAME_SIMPLE) - set(lastRenameUsed) = prefs.edit().putInt(LAST_RENAME_USED, lastRenameUsed).apply() - - var lastRenamePatternUsed: String - get() = prefs.getString(LAST_RENAME_PATTERN_USED, "")!! - set(lastRenamePatternUsed) = prefs.edit().putString(LAST_RENAME_PATTERN_USED, lastRenamePatternUsed).apply() - - var lastExportedSettingsFolder: String - get() = prefs.getString(LAST_EXPORTED_SETTINGS_FOLDER, "")!! - set(lastExportedSettingsFolder) = prefs.edit().putString(LAST_EXPORTED_SETTINGS_FOLDER, lastExportedSettingsFolder).apply() - - var lastBlockedNumbersExportPath: String - get() = prefs.getString(LAST_BLOCKED_NUMBERS_EXPORT_PATH, "")!! - set(lastBlockedNumbersExportPath) = prefs.edit().putString(LAST_BLOCKED_NUMBERS_EXPORT_PATH, lastBlockedNumbersExportPath).apply() - - var fontSize: Int - get() = prefs.getInt(FONT_SIZE, context.resources.getInteger(R.integer.default_font_size)) - set(size) = prefs.edit().putInt(FONT_SIZE, size).apply() - - // notify the users about new SMS Messenger and Voice Recorder released - var wasMessengerRecorderShown: Boolean - get() = prefs.getBoolean(WAS_MESSENGER_RECORDER_SHOWN, false) - set(wasMessengerRecorderShown) = prefs.edit().putBoolean(WAS_MESSENGER_RECORDER_SHOWN, wasMessengerRecorderShown).apply() - - var defaultTab: Int - get() = prefs.getInt(DEFAULT_TAB, TAB_LAST_USED) - set(defaultTab) = prefs.edit().putInt(DEFAULT_TAB, defaultTab).apply() - - var startNameWithSurname: Boolean - get() = prefs.getBoolean(START_NAME_WITH_SURNAME, false) - set(startNameWithSurname) = prefs.edit().putBoolean(START_NAME_WITH_SURNAME, startNameWithSurname).apply() - - var favorites: MutableSet - get() = prefs.getStringSet(FAVORITES, HashSet())!! - set(favorites) = - prefs - .edit() - .remove(FAVORITES) - .putStringSet(FAVORITES, favorites) - .apply() - - var showCallConfirmation: Boolean - get() = prefs.getBoolean(SHOW_CALL_CONFIRMATION, false) - set(showCallConfirmation) = prefs.edit().putBoolean(SHOW_CALL_CONFIRMATION, showCallConfirmation).apply() - - // color picker last used colors - internal var colorPickerRecentColors: LinkedList - get(): LinkedList { - val defaultList = - arrayListOf( - context.resources.getColor(R.color.md_red_700), - context.resources.getColor(R.color.md_blue_700), - context.resources.getColor(R.color.md_green_700), - context.resources.getColor(R.color.md_yellow_700), - context.resources.getColor(R.color.md_orange_700), - ) - return LinkedList(prefs.getString(COLOR_PICKER_RECENT_COLORS, null)?.lines()?.map { it.toInt() } ?: defaultList) - } - set(recentColors) = prefs.edit().putString(COLOR_PICKER_RECENT_COLORS, recentColors.joinToString(separator = "\n")).apply() } diff --git a/app/src/main/java/be/scri/helpers/ClipsHelper.kt b/app/src/main/java/be/scri/helpers/ClipsHelper.kt deleted file mode 100644 index d6ed6b82..00000000 --- a/app/src/main/java/be/scri/helpers/ClipsHelper.kt +++ /dev/null @@ -1,19 +0,0 @@ -package be.scri.helpers - -import android.content.Context -import be.scri.extensions.clipsDB -import be.scri.models.Clip - -class ClipsHelper( - val context: Context, -) { - // make sure clips have unique values - fun insertClip(clip: Clip): Long { - clip.value = clip.value.trim() - return if (context.clipsDB.getClipWithValue(clip.value) == null) { - context.clipsDB.insertOrUpdate(clip) - } else { - -1 - } - } -} diff --git a/app/src/main/java/be/scri/helpers/CommonsConstants.kt b/app/src/main/java/be/scri/helpers/CommonsConstants.kt index 2c2a593f..c5627827 100644 --- a/app/src/main/java/be/scri/helpers/CommonsConstants.kt +++ b/app/src/main/java/be/scri/helpers/CommonsConstants.kt @@ -1,452 +1,14 @@ package be.scri.helpers -import android.graphics.Color -import android.os.Build -import android.os.Looper -import android.util.Log -import androidx.annotation.ChecksSdkIntAtLeast - -const val EXTERNAL_STORAGE_PROVIDER_AUTHORITY = "com.android.externalstorage.documents" -const val EXTRA_SHOW_ADVANCED = "android.content.extra.SHOW_ADVANCED" - -const val APP_NAME = "app_name" -const val APP_LICENSES = "app_licenses" -const val APP_FAQ = "app_faq" -const val APP_VERSION_NAME = "app_version_name" -const val APP_ICON_IDS = "app_icon_ids" -const val APP_ID = "app_id" -const val APP_LAUNCHER_NAME = "app_launcher_name" -const val REAL_FILE_PATH = "real_file_path_2" -const val IS_FROM_GALLERY = "is_from_gallery" -const val BROADCAST_REFRESH_MEDIA = "com.simplemobiletools.REFRESH_MEDIA" -const val REFRESH_PATH = "refresh_path" -const val IS_CUSTOMIZING_COLORS = "is_customizing_colors" -const val BLOCKED_NUMBERS_EXPORT_DELIMITER = "," -const val BLOCKED_NUMBERS_EXPORT_EXTENSION = ".txt" -const val NOMEDIA = ".nomedia" -const val YOUR_ALARM_SOUNDS_MIN_ID = 1000 -const val SHOW_FAQ_BEFORE_MAIL = "show_faq_before_mail" -const val INVALID_NAVIGATION_BAR_COLOR = -1 -const val CHOPPED_LIST_DEFAULT_SIZE = 50 -const val SAVE_DISCARD_PROMPT_INTERVAL = 1000L -val DEFAULT_WIDGET_BG_COLOR = Color.parseColor("#AA000000") -const val SD_OTG_PATTERN = "^/storage/[A-Za-z0-9]{4}-[A-Za-z0-9]{4}$" -const val SD_OTG_SHORT = "^[A-Za-z0-9]{4}-[A-Za-z0-9]{4}$" -const val KEY_PHONE = "phone" -const val CONTACT_ID = "contact_id" -const val IS_PRIVATE = "is_private" -const val MD5 = "MD5" -const val SHORT_ANIMATION_DURATION = 150L val DARK_GREY = 0xFF333333.toInt() -const val LOWER_ALPHA = 0.25f -const val MEDIUM_ALPHA = 0.5f -const val HIGHER_ALPHA = 0.75f - -const val HOUR_MINUTES = 60 -const val DAY_MINUTES = 24 * HOUR_MINUTES -const val WEEK_MINUTES = DAY_MINUTES * 7 -const val MONTH_MINUTES = DAY_MINUTES * 30 -const val YEAR_MINUTES = DAY_MINUTES * 365 - -const val MINUTE_SECONDS = 60 -const val HOUR_SECONDS = HOUR_MINUTES * 60 -const val DAY_SECONDS = DAY_MINUTES * 60 -const val WEEK_SECONDS = WEEK_MINUTES * 60 -const val MONTH_SECONDS = MONTH_MINUTES * 60 -const val YEAR_SECONDS = YEAR_MINUTES * 60 - // shared preferences const val PREFS_KEY = "Prefs" -const val APP_RUN_COUNT = "app_run_count" -const val LAST_VERSION = "last_version" -const val SD_TREE_URI = "tree_uri_2" -const val PRIMARY_ANDROID_DATA_TREE_URI = "primary_android_data_tree_uri_2" -const val OTG_ANDROID_DATA_TREE_URI = "otg_android_data_tree__uri_2" -const val SD_ANDROID_DATA_TREE_URI = "sd_android_data_tree_uri_2" -const val PRIMARY_ANDROID_OBB_TREE_URI = "primary_android_obb_tree_uri_2" -const val OTG_ANDROID_OBB_TREE_URI = "otg_android_obb_tree_uri_2" -const val SD_ANDROID_OBB_TREE_URI = "sd_android_obb_tree_uri_2" -const val OTG_TREE_URI = "otg_tree_uri_2" -const val SD_CARD_PATH = "sd_card_path_2" -const val OTG_REAL_PATH = "otg_real_path_2" -const val INTERNAL_STORAGE_PATH = "internal_storage_path" const val TEXT_COLOR = "text_color" const val KEY_COLOR = "key_color" const val BACKGROUND_COLOR = "background_color" const val PRIMARY_COLOR = "primary_color_2" const val ACCENT_COLOR = "accent_color" -const val APP_ICON_COLOR = "app_icon_color" -const val NAVIGATION_BAR_COLOR = "navigation_bar_color" -const val DEFAULT_NAVIGATION_BAR_COLOR = "default_navigation_bar_color" -const val LAST_HANDLED_SHORTCUT_COLOR = "last_handled_shortcut_color" -const val LAST_ICON_COLOR = "last_icon_color" -const val CUSTOM_TEXT_COLOR = "custom_text_color" -const val CUSTOM_BACKGROUND_COLOR = "custom_background_color" -const val CUSTOM_PRIMARY_COLOR = "custom_primary_color" -const val CUSTOM_ACCENT_COLOR = "custom_accent_color" -const val CUSTOM_NAVIGATION_BAR_COLOR = "custom_navigation_bar_color" -const val CUSTOM_APP_ICON_COLOR = "custom_app_icon_color" -const val WIDGET_BG_COLOR = "widget_bg_color" -const val WIDGET_TEXT_COLOR = "widget_text_color" -const val PASSWORD_PROTECTION = "password_protection" -const val PASSWORD_HASH = "password_hash" -const val PROTECTION_TYPE = "protection_type" -const val APP_PASSWORD_PROTECTION = "app_password_protection" -const val APP_PASSWORD_HASH = "app_password_hash" -const val APP_PROTECTION_TYPE = "app_protection_type" -const val DELETE_PASSWORD_PROTECTION = "delete_password_protection" -const val DELETE_PASSWORD_HASH = "delete_password_hash" -const val DELETE_PROTECTION_TYPE = "delete_protection_type" -const val PROTECTED_FOLDER_PATH = "protected_folder_path_" -const val PROTECTED_FOLDER_HASH = "protected_folder_hash_" -const val PROTECTED_FOLDER_TYPE = "protected_folder_type_" -const val KEEP_LAST_MODIFIED = "keep_last_modified" const val USE_ENGLISH = "use_english" const val WAS_USE_ENGLISH_TOGGLED = "was_use_english_toggled" -const val WAS_SHARED_THEME_EVER_ACTIVATED = "was_shared_theme_ever_activated" -const val IS_USING_SHARED_THEME = "is_using_shared_theme" -const val IS_USING_AUTO_THEME = "is_using_auto_theme" const val IS_USING_SYSTEM_THEME = "is_using_system_theme" -const val SHOULD_USE_SHARED_THEME = "should_use_shared_theme" -const val WAS_SHARED_THEME_FORCED = "was_shared_theme_forced" -const val WAS_CUSTOM_THEME_SWITCH_DESCRIPTION_SHOWN = "was_custom_theme_switch_description_shown" -const val SHOW_INFO_BUBBLE = "show_info_bubble" -const val LAST_CONFLICT_RESOLUTION = "last_conflict_resolution" -const val LAST_CONFLICT_APPLY_TO_ALL = "last_conflict_apply_to_all" -const val HAD_THANK_YOU_INSTALLED = "had_thank_you_installed" -const val SKIP_DELETE_CONFIRMATION = "skip_delete_confirmation" -const val ENABLE_PULL_TO_REFRESH = "enable_pull_to_refresh" -const val SCROLL_HORIZONTALLY = "scroll_horizontally" -const val PREVENT_PHONE_FROM_SLEEPING = "prevent_phone_from_sleeping" -const val LAST_USED_VIEW_PAGER_PAGE = "last_used_view_pager_page" -const val USE_24_HOUR_FORMAT = "use_24_hour_format" -const val SUNDAY_FIRST = "sunday_first" -const val WAS_ALARM_WARNING_SHOWN = "was_alarm_warning_shown" -const val WAS_REMINDER_WARNING_SHOWN = "was_reminder_warning_shown" -const val USE_SAME_SNOOZE = "use_same_snooze" -const val SNOOZE_TIME = "snooze_delay" -const val VIBRATE_ON_BUTTON_PRESS = "vibrate_on_button_press" -const val YOUR_ALARM_SOUNDS = "your_alarm_sounds" -const val SILENT = "silent" -const val OTG_PARTITION = "otg_partition_2" -const val IS_USING_MODIFIED_APP_ICON = "is_using_modified_app_icon" -const val INITIAL_WIDGET_HEIGHT = "initial_widget_height" -const val WIDGET_ID_TO_MEASURE = "widget_id_to_measure" -const val WAS_ORANGE_ICON_CHECKED = "was_orange_icon_checked" -const val WAS_APP_ON_SD_SHOWN = "was_app_on_sd_shown" -const val WAS_BEFORE_ASKING_SHOWN = "was_before_asking_shown" -const val WAS_BEFORE_RATE_SHOWN = "was_before_rate_shown" -const val WAS_INITIAL_UPGRADE_TO_PRO_SHOWN = "was_initial_upgrade_to_pro_shown" -const val WAS_APP_ICON_CUSTOMIZATION_WARNING_SHOWN = "was_app_icon_customization_warning_shown" -const val APP_SIDELOADING_STATUS = "app_sideloading_status" -const val DATE_FORMAT = "date_format" -const val WAS_OTG_HANDLED = "was_otg_handled_2" -const val WAS_UPGRADED_FROM_FREE_SHOWN = "was_upgraded_from_free_shown" -const val WAS_RATE_US_PROMPT_SHOWN = "was_rate_us_prompt_shown" -const val WAS_APP_RATED = "was_app_rated" -const val WAS_SORTING_BY_NUMERIC_VALUE_ADDED = "was_sorting_by_numeric_value_added" -const val WAS_FOLDER_LOCKING_NOTICE_SHOWN = "was_folder_locking_notice_shown" -const val LAST_RENAME_USED = "last_rename_used" -const val LAST_RENAME_PATTERN_USED = "last_rename_pattern_used" -const val LAST_EXPORTED_SETTINGS_FOLDER = "last_exported_settings_folder" -const val LAST_EXPORTED_SETTINGS_FILE = "last_exported_settings_file" -const val LAST_BLOCKED_NUMBERS_EXPORT_PATH = "last_blocked_numbers_export_path" -const val FONT_SIZE = "font_size" -const val WAS_MESSENGER_RECORDER_SHOWN = "was_messenger_recorder_shown" -const val DEFAULT_TAB = "default_tab" -const val START_NAME_WITH_SURNAME = "start_name_with_surname" -const val FAVORITES = "favorites" -const val SHOW_CALL_CONFIRMATION = "show_call_confirmation" -internal const val COLOR_PICKER_RECENT_COLORS = "color_picker_recent_colors" - -// licenses -internal const val LICENSE_KOTLIN = 1 -const val LICENSE_SUBSAMPLING = 2 -const val LICENSE_GLIDE = 4 -const val LICENSE_CROPPER = 8 -const val LICENSE_FILTERS = 16 -const val LICENSE_RTL = 32 -const val LICENSE_JODA = 64 -const val LICENSE_STETHO = 128 -const val LICENSE_OTTO = 256 -const val LICENSE_PHOTOVIEW = 512 -const val LICENSE_PICASSO = 1024 -const val LICENSE_PATTERN = 2048 -const val LICENSE_REPRINT = 4096 -const val LICENSE_GIF_DRAWABLE = 8192 -const val LICENSE_AUTOFITTEXTVIEW = 16384 -const val LICENSE_ROBOLECTRIC = 32768 -const val LICENSE_ESPRESSO = 65536 -const val LICENSE_GSON = 131072 -const val LICENSE_LEAK_CANARY = 262144 -const val LICENSE_NUMBER_PICKER = 524288 -const val LICENSE_EXOPLAYER = 1048576 -const val LICENSE_PANORAMA_VIEW = 2097152 -const val LICENSE_SANSELAN = 4194304 -const val LICENSE_GESTURE_VIEWS = 8388608 -const val LICENSE_INDICATOR_FAST_SCROLL = 16777216 -const val LICENSE_EVENT_BUS = 33554432 -const val LICENSE_AUDIO_RECORD_VIEW = 67108864 -const val LICENSE_SMS_MMS = 134217728 -const val LICENSE_APNG = 268435456 - -// global intents -const val OPEN_DOCUMENT_TREE_FOR_ANDROID_DATA_OR_OBB = 1000 -const val OPEN_DOCUMENT_TREE_OTG = 1001 -const val OPEN_DOCUMENT_TREE_SD = 1002 -const val OPEN_DOCUMENT_TREE_FOR_SDK_30 = 1003 -const val REQUEST_SET_AS = 1004 -const val REQUEST_EDIT_IMAGE = 1005 -const val SELECT_EXPORT_SETTINGS_FILE_INTENT = 1006 -const val REQUEST_CODE_SET_DEFAULT_DIALER = 1007 -const val CREATE_DOCUMENT_SDK_30 = 1008 - -// sorting -const val SORT_ORDER = "sort_order" -const val SORT_FOLDER_PREFIX = "sort_folder_" // storing folder specific values at using "Use for this folder only" -const val SORT_BY_NAME = 1 -const val SORT_BY_DATE_MODIFIED = 2 -const val SORT_BY_SIZE = 4 -const val SORT_BY_DATE_TAKEN = 8 -const val SORT_BY_EXTENSION = 16 -const val SORT_BY_PATH = 32 -const val SORT_BY_NUMBER = 64 -const val SORT_BY_FIRST_NAME = 128 -const val SORT_BY_MIDDLE_NAME = 256 -const val SORT_BY_SURNAME = 512 -const val SORT_DESCENDING = 1024 -const val SORT_BY_TITLE = 2048 -const val SORT_BY_ARTIST = 4096 -const val SORT_BY_DURATION = 8192 -const val SORT_BY_RANDOM = 16384 -const val SORT_USE_NUMERIC_VALUE = 32768 -const val SORT_BY_FULL_NAME = 65536 -const val SORT_BY_CUSTOM = 131072 -const val SORT_BY_DATE_CREATED = 262144 - -// security -const val WAS_PROTECTION_HANDLED = "was_protection_handled" -const val PROTECTION_NONE = -1 -const val PROTECTION_PATTERN = 0 -const val PROTECTION_PIN = 1 -const val PROTECTION_FINGERPRINT = 2 - -// renaming -const val RENAME_SIMPLE = 0 -const val RENAME_PATTERN = 1 - -const val SHOW_ALL_TABS = -1 -const val SHOW_PATTERN = 0 -const val SHOW_PIN = 1 -const val SHOW_FINGERPRINT = 2 - -// permissions -const val PERMISSION_READ_STORAGE = 1 -const val PERMISSION_WRITE_STORAGE = 2 -const val PERMISSION_CAMERA = 3 -const val PERMISSION_RECORD_AUDIO = 4 -const val PERMISSION_READ_CONTACTS = 5 -const val PERMISSION_WRITE_CONTACTS = 6 -const val PERMISSION_READ_CALENDAR = 7 -const val PERMISSION_WRITE_CALENDAR = 8 -const val PERMISSION_CALL_PHONE = 9 -const val PERMISSION_READ_CALL_LOG = 10 -const val PERMISSION_WRITE_CALL_LOG = 11 -const val PERMISSION_GET_ACCOUNTS = 12 -const val PERMISSION_READ_SMS = 13 -const val PERMISSION_SEND_SMS = 14 -const val PERMISSION_READ_PHONE_STATE = 15 - -// conflict resolving -const val CONFLICT_SKIP = 1 -const val CONFLICT_OVERWRITE = 2 -const val CONFLICT_MERGE = 3 -const val CONFLICT_KEEP_BOTH = 4 - -// font sizes -const val FONT_SIZE_SMALL = 0 -const val FONT_SIZE_MEDIUM = 1 -const val FONT_SIZE_LARGE = 2 -const val FONT_SIZE_EXTRA_LARGE = 3 - -const val MONDAY_BIT = 1 -const val TUESDAY_BIT = 2 -const val WEDNESDAY_BIT = 4 -const val THURSDAY_BIT = 8 -const val FRIDAY_BIT = 16 -const val SATURDAY_BIT = 32 -const val SUNDAY_BIT = 64 -const val EVERY_DAY_BIT = MONDAY_BIT or TUESDAY_BIT or WEDNESDAY_BIT or THURSDAY_BIT or FRIDAY_BIT or SATURDAY_BIT or SUNDAY_BIT -const val WEEK_DAYS_BIT = MONDAY_BIT or TUESDAY_BIT or WEDNESDAY_BIT or THURSDAY_BIT or FRIDAY_BIT -const val WEEKENDS_BIT = SATURDAY_BIT or SUNDAY_BIT - -const val SIDELOADING_UNCHECKED = 0 -const val SIDELOADING_TRUE = 1 -const val SIDELOADING_FALSE = 2 - -// default tabs -const val TAB_LAST_USED = 0 -const val TAB_CONTACTS = 1 -const val TAB_FAVORITES = 2 -const val TAB_CALL_HISTORY = 4 -const val TAB_GROUPS = 8 -const val TAB_FILES = 16 -const val TAB_RECENT_FILES = 32 -const val TAB_STORAGE_ANALYSIS = 64 - -internal const val STORE_URL = "https://play.google.com/store/apps/" - -val photoExtensions: Array get() = arrayOf(".jpg", ".png", ".jpeg", ".bmp", ".webp", ".heic", ".heif", ".apng") -val videoExtensions: Array get() = arrayOf(".mp4", ".mkv", ".webm", ".avi", ".3gp", ".mov", ".m4v", ".3gpp") -val audioExtensions: Array get() = arrayOf(".mp3", ".wav", ".wma", ".ogg", ".m4a", ".opus", ".flac", ".aac") -val rawExtensions: Array get() = arrayOf(".dng", ".orf", ".nef", ".arw", ".rw2", ".cr2", ".cr3") - -val extensionsSupportingEXIF: Array get() = arrayOf(".jpg", ".jpeg", ".png", ".webp", ".dng") - -const val DATE_FORMAT_ONE = "dd.MM.yyyy" -const val DATE_FORMAT_TWO = "dd/MM/yyyy" -const val DATE_FORMAT_THREE = "MM/dd/yyyy" -const val DATE_FORMAT_FOUR = "yyyy-MM-dd" -const val DATE_FORMAT_FIVE = "d MMMM yyyy" -const val DATE_FORMAT_SIX = "MMMM d yyyy" -const val DATE_FORMAT_SEVEN = "MM-dd-yyyy" -const val DATE_FORMAT_EIGHT = "dd-MM-yyyy" -const val DATE_FORMAT_NINE = "yyyyMMdd" -const val DATE_FORMAT_TEN = "yyyy.MM.dd" -const val DATE_FORMAT_ELEVEN = "yy-MM-dd" -const val DATE_FORMAT_TWELVE = "yyMMdd" -const val DATE_FORMAT_THIRTEEN = "yy.MM.dd" -const val DATE_FORMAT_FOURTEEN = "yy/MM/dd" - -const val TIME_FORMAT_12 = "hh:mm a" -const val TIME_FORMAT_24 = "HH:mm" - -val appIconColorStrings = - arrayListOf( - ".Red", - ".Pink", - ".Purple", - ".Deep_purple", - ".Indigo", - ".Blue", - ".Light_blue", - ".Cyan", - ".Teal", - ".Green", - ".Light_green", - ".Lime", - ".Yellow", - ".Amber", - ".Orange", - ".Deep_orange", - ".Brown", - ".Blue_grey", - ".Grey_black", - ) - -// most app icon colors from md_app_icon_colors with reduced alpha -// used at showing contact placeholders without image -val letterBackgroundColors = - arrayListOf( - 0xCCD32F2F, - 0xCCC2185B, - 0xCC1976D2, - 0xCC0288D1, - 0xCC0097A7, - 0xCC00796B, - 0xCC388E3C, - 0xCC689F38, - 0xCCF57C00, - 0xCCE64A19, - ) - -// view types -const val VIEW_TYPE_GRID = 1 -const val VIEW_TYPE_LIST = 2 - -fun isOnMainThread() = Looper.myLooper() == Looper.getMainLooper() - -fun ensureBackgroundThread(callback: () -> Unit) { - if (isOnMainThread()) { - Thread { - callback() - }.start() - } else { - callback() - } -} - -@ChecksSdkIntAtLeast(api = Build.VERSION_CODES.M) -fun isMarshmallowPlus() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M - -@ChecksSdkIntAtLeast(api = Build.VERSION_CODES.N) -fun isNougatPlus() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N - -@ChecksSdkIntAtLeast(api = Build.VERSION_CODES.N_MR1) -fun isNougatMR1Plus() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1 - -@ChecksSdkIntAtLeast(api = Build.VERSION_CODES.O) -fun isOreoPlus() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O - -@ChecksSdkIntAtLeast(api = Build.VERSION_CODES.O_MR1) -fun isOreoMr1Plus() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1 - -@ChecksSdkIntAtLeast(api = Build.VERSION_CODES.P) -fun isPiePlus() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P - -@ChecksSdkIntAtLeast(api = Build.VERSION_CODES.Q) -fun isQPlus() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q - -@ChecksSdkIntAtLeast(api = Build.VERSION_CODES.R) -fun isRPlus() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.R - -@ChecksSdkIntAtLeast(api = Build.VERSION_CODES.S) -fun isSPlus() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S - -fun getDateFormats() = - arrayListOf( - "--MM-dd", - "yyyy-MM-dd", - "yyyyMMdd", - "yyyy.MM.dd", - "yy-MM-dd", - "yyMMdd", - "yy.MM.dd", - "yy/MM/dd", - "MM-dd", - "MMdd", - "MM/dd", - "MM.dd", - ) - -fun getDateFormatsWithYear() = - arrayListOf( - DATE_FORMAT_FOUR, - DATE_FORMAT_NINE, - DATE_FORMAT_TEN, - DATE_FORMAT_ELEVEN, - DATE_FORMAT_TWELVE, - DATE_FORMAT_THIRTEEN, - DATE_FORMAT_FOURTEEN, - ) - -val normalizeRegex = "\\p{InCombiningDiacriticalMarks}+".toRegex() - -fun getConflictResolution( - resolutions: LinkedHashMap, - path: String, -): Int = - if (resolutions.size == 1 && resolutions.containsKey("")) { - resolutions[""]!! - } else if (resolutions.containsKey(path)) { - resolutions[path]!! - } else { - CONFLICT_SKIP - } - -val proPackages = arrayListOf("draw", "gallery", "filemanager", "contacts", "notes", "calendar") - -fun mydebug(message: String) = Log.e("DEBUG", message) - -fun getQuestionMarks(size: Int) = Array(size) { "?" }.joinToString(separator = ",") diff --git a/app/src/main/java/be/scri/helpers/Config.kt b/app/src/main/java/be/scri/helpers/Config.kt index 2f7d9da5..56aed664 100644 --- a/app/src/main/java/be/scri/helpers/Config.kt +++ b/app/src/main/java/be/scri/helpers/Config.kt @@ -1,7 +1,6 @@ package be.scri.helpers import android.content.Context -import java.util.Locale class Config( context: Context, @@ -22,28 +21,7 @@ class Config( get() = prefs.getBoolean(DARK_THEME, true) set(darkTheme) = prefs.edit().putBoolean(DARK_THEME, darkTheme).apply() - var lastExportedClipsFolder: String - get() = prefs.getString(LAST_EXPORTED_CLIPS_FOLDER, "")!! - set(lastExportedClipsFolder) = prefs.edit().putString(LAST_EXPORTED_CLIPS_FOLDER, lastExportedClipsFolder).apply() - - var keyboardLanguage: Int - get() = prefs.getInt(KEYBOARD_LANGUAGE, getDefaultLanguage()) - set(keyboardLanguage) = prefs.edit().putInt(KEYBOARD_LANGUAGE, keyboardLanguage).apply() - var periodOnDoubleTap: Boolean get() = prefs.getBoolean(PERIOD_ON_DOUBLE_TAP, true) set(periodOnDoubleTap) = prefs.edit().putBoolean(PERIOD_ON_DOUBLE_TAP, periodOnDoubleTap).apply() - - private fun getDefaultLanguage(): Int { - val conf = context.resources.configuration - return if (conf.locale - .toString() - .toLowerCase(Locale.getDefault()) - .startsWith("ru_") - ) { - LANGUAGE_RUSSIAN - } else { - LANGUAGE_ENGLISH_QWERTY - } - } } diff --git a/app/src/main/java/be/scri/helpers/Constants.kt b/app/src/main/java/be/scri/helpers/Constants.kt index 5ff89562..f261b8c0 100644 --- a/app/src/main/java/be/scri/helpers/Constants.kt +++ b/app/src/main/java/be/scri/helpers/Constants.kt @@ -10,20 +10,5 @@ const val MAX_KEYS_PER_MINI_ROW = 9 // shared prefs const val VIBRATE_ON_KEYPRESS = "vibrate_on_keypress" const val SHOW_POPUP_ON_KEYPRESS = "show_popup_on_keypress" -const val LAST_EXPORTED_CLIPS_FOLDER = "last_exported_clips_folder" -const val KEYBOARD_LANGUAGE = "keyboard_language" const val DARK_THEME = "dark_theme" const val PERIOD_ON_DOUBLE_TAP = "period_on_double_tap" - -// differentiate current and pinned clips at the keyboards' Clipboard section -const val ITEM_SECTION_LABEL = 0 -const val ITEM_CLIP = 1 - -const val LANGUAGE_ENGLISH_QWERTY = 0 -const val LANGUAGE_FRENCH = 1 -const val LANGUAGE_GERMAN = 2 -const val LANGUAGE_ITALIAN = 3 -const val LANGUAGE_PORTUGUESE = 4 -const val LANGUAGE_RUSSIAN = 5 -const val LANGUAGE_SPANISH = 6 -const val LANGUAGE_SWEDISH = 7 diff --git a/app/src/main/java/be/scri/helpers/ExternalStorageProviderHack.kt b/app/src/main/java/be/scri/helpers/ExternalStorageProviderHack.kt deleted file mode 100644 index 345cc690..00000000 --- a/app/src/main/java/be/scri/helpers/ExternalStorageProviderHack.kt +++ /dev/null @@ -1,108 +0,0 @@ -package be.scri.helpers - -import android.database.Cursor -import android.database.MatrixCursor -import android.database.MergeCursor -import android.net.Uri -import android.provider.DocumentsContract -import be.scri.extensions.getStringValue - -// On Android 11, ExternalStorageProvider no longer returns Android/data and Android/obb as children -// of the Android directory on primary storage. However, the two child directories are actually -// still accessible. -// Inspired by: https://github.com/zhanghai/MaterialFiles/blob/master/app/src/main/java/me/zhanghai/android/files/provider/document/resolver/ExternalStorageProviderPrimaryAndroidDataHack.kt -object ExternalStorageProviderHack { - private const val ANDROID_DATA_DISPLAY_NAME = "data" - private const val ANDROID_OBB_DISPLAY_NAME = "obb" - - private val CHILD_DOCUMENTS_CURSOR_COLUMN_NAMES = - arrayOf( - DocumentsContract.Document.COLUMN_DOCUMENT_ID, - DocumentsContract.Document.COLUMN_DISPLAY_NAME, - DocumentsContract.Document.COLUMN_MIME_TYPE, - DocumentsContract.Document.COLUMN_LAST_MODIFIED, - DocumentsContract.Document.COLUMN_SIZE, - ) - - private fun getAndroidDocumentId(rootDocId: String): String = "$rootDocId:Android" - - private fun getAndroidDataDocumentId(rootDocId: String): String = "${getAndroidDocumentId(rootDocId)}/data" - - private fun getAndroidObbDocumentId(rootDocId: String): String = "${getAndroidDocumentId(rootDocId)}/obb" - - fun transformQueryResult( - rootDocId: String, - uri: Uri, - cursor: Cursor, - ): Cursor { - val documentId = DocumentsContract.getDocumentId(uri) - if (uri.authority == EXTERNAL_STORAGE_PROVIDER_AUTHORITY && documentId == getAndroidDocumentId(rootDocId)) { - var hasDataRow = false - var hasObbRow = false - try { - while (cursor.moveToNext()) { - when (cursor.getStringValue(DocumentsContract.Document.COLUMN_DOCUMENT_ID)) { - getAndroidDataDocumentId(rootDocId) -> - hasDataRow = true - getAndroidObbDocumentId(rootDocId) -> - hasObbRow = true - } - if (hasDataRow && hasObbRow) { - break - } - } - } finally { - cursor.moveToPosition(-1) - } - - if (hasDataRow && hasObbRow) { - return cursor - } - - val extraCursor = MatrixCursor(CHILD_DOCUMENTS_CURSOR_COLUMN_NAMES) - if (!hasDataRow) { - extraCursor - .newRow() - .add( - DocumentsContract.Document.COLUMN_DOCUMENT_ID, - getAndroidDataDocumentId(rootDocId), - ).add( - DocumentsContract.Document.COLUMN_DISPLAY_NAME, - ANDROID_DATA_DISPLAY_NAME, - ).add( - DocumentsContract.Document.COLUMN_MIME_TYPE, - DocumentsContract.Document.MIME_TYPE_DIR, - ).add( - DocumentsContract.Document.COLUMN_LAST_MODIFIED, - System.currentTimeMillis(), - ).add( - DocumentsContract.Document.COLUMN_SIZE, - 0L, - ) - } - - if (!hasObbRow) { - extraCursor - .newRow() - .add( - DocumentsContract.Document.COLUMN_DOCUMENT_ID, - getAndroidObbDocumentId(rootDocId), - ).add( - DocumentsContract.Document.COLUMN_DISPLAY_NAME, - ANDROID_OBB_DISPLAY_NAME, - ).add( - DocumentsContract.Document.COLUMN_MIME_TYPE, - DocumentsContract.Document.MIME_TYPE_DIR, - ).add( - DocumentsContract.Document.COLUMN_LAST_MODIFIED, - System.currentTimeMillis(), - ).add( - DocumentsContract.Document.COLUMN_SIZE, - 0L, - ) - } - return MergeCursor(arrayOf(cursor, extraCursor)) - } - return cursor - } -} diff --git a/app/src/main/java/be/scri/helpers/MyContentProvider.kt b/app/src/main/java/be/scri/helpers/MyContentProvider.kt deleted file mode 100644 index 9bdfb5e9..00000000 --- a/app/src/main/java/be/scri/helpers/MyContentProvider.kt +++ /dev/null @@ -1,34 +0,0 @@ -package be.scri.helpers - -import android.content.ContentValues -import android.net.Uri -import be.scri.models.SharedTheme - -class MyContentProvider { - companion object { - private const val AUTHORITY = "be.scri.commons.provider" - const val SHARED_THEME_ACTIVATED = "be.scri.commons.SHARED_THEME_ACTIVATED" - const val SHARED_THEME_UPDATED = "be.scri.commons.SHARED_THEME_UPDATED" - val MY_CONTENT_URI = Uri.parse("content://$AUTHORITY/themes") - - const val COL_ID = "_id" // used in Simple Thank You - const val COL_TEXT_COLOR = "text_color" - const val COL_BACKGROUND_COLOR = "background_color" - const val COL_PRIMARY_COLOR = "primary_color" - const val COL_ACCENT_COLOR = "accent_color" - const val COL_APP_ICON_COLOR = "app_icon_color" - const val COL_NAVIGATION_BAR_COLOR = "navigation_bar_color" - const val COL_LAST_UPDATED_TS = "last_updated_ts" - - fun fillThemeContentValues(sharedTheme: SharedTheme) = - ContentValues().apply { - put(COL_TEXT_COLOR, sharedTheme.textColor) - put(COL_BACKGROUND_COLOR, sharedTheme.backgroundColor) - put(COL_PRIMARY_COLOR, sharedTheme.primaryColor) - put(COL_ACCENT_COLOR, sharedTheme.accentColor) - put(COL_APP_ICON_COLOR, sharedTheme.appIconColor) - put(COL_NAVIGATION_BAR_COLOR, sharedTheme.navigationBarColor) - put(COL_LAST_UPDATED_TS, System.currentTimeMillis() / 1000) - } - } -} diff --git a/app/src/main/java/be/scri/helpers/MyContextWrapper.kt b/app/src/main/java/be/scri/helpers/MyContextWrapper.kt deleted file mode 100644 index b500983c..00000000 --- a/app/src/main/java/be/scri/helpers/MyContextWrapper.kt +++ /dev/null @@ -1,62 +0,0 @@ -package be.scri.helpers - -import android.annotation.TargetApi -import android.content.Context -import android.content.ContextWrapper -import android.content.res.Configuration -import android.os.Build -import java.util.Locale - -// language forcing used at "Use english language", taken from https://stackoverflow.com/a/40704077/1967672 -class MyContextWrapper( - context: Context, -) : ContextWrapper(context) { - fun wrap( - context: Context, - language: String, - ): ContextWrapper { - var newContext = context - val config = newContext.resources.configuration - val sysLocale: Locale? - - sysLocale = - if (isNougatPlus()) { - getSystemLocale(config) - } else { - getSystemLocaleLegacy(config) - } - - if (language != "" && sysLocale!!.language != language) { - val locale = Locale(language) - Locale.setDefault(locale) - if (isNougatPlus()) { - setSystemLocale(config, locale) - } else { - setSystemLocaleLegacy(config, locale) - } - } - - newContext = newContext.createConfigurationContext(config) - return MyContextWrapper(newContext) - } - - private fun getSystemLocaleLegacy(config: Configuration) = config.locale - - @TargetApi(Build.VERSION_CODES.N) - private fun getSystemLocale(config: Configuration) = config.locales.get(0) - - private fun setSystemLocaleLegacy( - config: Configuration, - locale: Locale, - ) { - config.locale = locale - } - - @TargetApi(Build.VERSION_CODES.N) - private fun setSystemLocale( - config: Configuration, - locale: Locale, - ) { - config.setLocale(locale) - } -} diff --git a/app/src/main/java/be/scri/helpers/MyKeyboard.kt b/app/src/main/java/be/scri/helpers/MyKeyboard.kt index 85dd27ea..982097bd 100644 --- a/app/src/main/java/be/scri/helpers/MyKeyboard.kt +++ b/app/src/main/java/be/scri/helpers/MyKeyboard.kt @@ -212,7 +212,7 @@ class MyKeyboard { topSmallNumber = a.getString(R.styleable.MyKeyboard_Key_topSmallNumber) ?: "" if (label.isNotEmpty() && code != KEYCODE_MODE_CHANGE && code != KEYCODE_SHIFT) { - code = label[0].toInt() + code = label[0].code } a.recycle() } @@ -299,7 +299,7 @@ class MyKeyboard { key.x = x key.y = y key.label = character.toString() - key.code = character.toInt() + key.code = character.code column++ x += key.width + key.gap mKeys!!.add(key) diff --git a/app/src/main/java/be/scri/interfaces/ClipsDao.kt b/app/src/main/java/be/scri/interfaces/ClipsDao.kt deleted file mode 100644 index a1f6a0ea..00000000 --- a/app/src/main/java/be/scri/interfaces/ClipsDao.kt +++ /dev/null @@ -1,25 +0,0 @@ -package be.scri.interfaces - -import androidx.room.Dao -import androidx.room.Insert -import androidx.room.OnConflictStrategy -import androidx.room.Query -import be.scri.models.Clip - -@Dao -interface ClipsDao { - @Query("SELECT * FROM clips ORDER BY id") - fun getClips(): List - - @Query("SELECT id FROM clips WHERE value = :value COLLATE NOCASE") - fun getClipWithValue(value: String): Long? - - @Insert(onConflict = OnConflictStrategy.REPLACE) - fun insertOrUpdate(clip: Clip): Long - - @Query("DELETE FROM clips WHERE id = :id") - fun delete(id: Long) - - @Query("DELETE FROM clips") - fun deleteAll() -} diff --git a/app/src/main/java/be/scri/interfaces/HashListener.kt b/app/src/main/java/be/scri/interfaces/HashListener.kt deleted file mode 100644 index 008156d0..00000000 --- a/app/src/main/java/be/scri/interfaces/HashListener.kt +++ /dev/null @@ -1,8 +0,0 @@ -package be.scri.interfaces - -interface HashListener { - fun receivedHash( - hash: String, - type: Int, - ) -} diff --git a/app/src/main/java/be/scri/interfaces/LineColorPickerListener.kt b/app/src/main/java/be/scri/interfaces/LineColorPickerListener.kt deleted file mode 100644 index 5818ac34..00000000 --- a/app/src/main/java/be/scri/interfaces/LineColorPickerListener.kt +++ /dev/null @@ -1,8 +0,0 @@ -package be.scri.interfaces - -interface LineColorPickerListener { - fun colorChanged( - index: Int, - color: Int, - ) -} diff --git a/app/src/main/java/be/scri/interfaces/MyActionModeCallback.kt b/app/src/main/java/be/scri/interfaces/MyActionModeCallback.kt deleted file mode 100644 index 7184b904..00000000 --- a/app/src/main/java/be/scri/interfaces/MyActionModeCallback.kt +++ /dev/null @@ -1,7 +0,0 @@ -package be.scri.interfaces - -import android.view.ActionMode - -abstract class MyActionModeCallback : ActionMode.Callback { - var isSelectable = false -} diff --git a/app/src/main/java/be/scri/interfaces/RefreshClipsListener.kt b/app/src/main/java/be/scri/interfaces/RefreshClipsListener.kt deleted file mode 100644 index b90435e4..00000000 --- a/app/src/main/java/be/scri/interfaces/RefreshClipsListener.kt +++ /dev/null @@ -1,5 +0,0 @@ -package be.scri.interfaces - -interface RefreshClipsListener { - fun refreshClips() -} diff --git a/app/src/main/java/be/scri/interfaces/RenameTab.kt b/app/src/main/java/be/scri/interfaces/RenameTab.kt deleted file mode 100644 index d6ce83a2..00000000 --- a/app/src/main/java/be/scri/interfaces/RenameTab.kt +++ /dev/null @@ -1,15 +0,0 @@ -package be.scri.interfaces - -import be.scri.activities.BaseSimpleActivity - -interface RenameTab { - fun initTab( - activity: BaseSimpleActivity, - paths: ArrayList, - ) - - fun dialogConfirmed( - useMediaFileExtension: Boolean, - callback: (success: Boolean) -> Unit, - ) -} diff --git a/app/src/main/java/be/scri/interfaces/SecurityTab.kt b/app/src/main/java/be/scri/interfaces/SecurityTab.kt deleted file mode 100644 index e226e69a..00000000 --- a/app/src/main/java/be/scri/interfaces/SecurityTab.kt +++ /dev/null @@ -1,16 +0,0 @@ -package be.scri.interfaces - -import androidx.biometric.auth.AuthPromptHost -import be.scri.views.MyScrollView - -interface SecurityTab { - fun initTab( - requiredHash: String, - listener: HashListener, - scrollView: MyScrollView, - biometricPromptHost: AuthPromptHost, - showBiometricAuthentication: Boolean, - ) - - fun visibilityChanged(isVisible: Boolean) -} diff --git a/app/src/main/java/be/scri/models/AlarmSound.kt b/app/src/main/java/be/scri/models/AlarmSound.kt deleted file mode 100644 index 036cb3f5..00000000 --- a/app/src/main/java/be/scri/models/AlarmSound.kt +++ /dev/null @@ -1,7 +0,0 @@ -package be.scri.models - -data class AlarmSound( - val id: Int, - var title: String, - var uri: String, -) diff --git a/app/src/main/java/be/scri/models/Android30RenameFormat.kt b/app/src/main/java/be/scri/models/Android30RenameFormat.kt deleted file mode 100644 index d9697bb3..00000000 --- a/app/src/main/java/be/scri/models/Android30RenameFormat.kt +++ /dev/null @@ -1,7 +0,0 @@ -package be.scri.models - -enum class Android30RenameFormat { - SAF, - CONTENT_RESOLVER, - NONE, -} diff --git a/app/src/main/java/be/scri/models/BlockedNumber.kt b/app/src/main/java/be/scri/models/BlockedNumber.kt deleted file mode 100644 index 9a9adb92..00000000 --- a/app/src/main/java/be/scri/models/BlockedNumber.kt +++ /dev/null @@ -1,8 +0,0 @@ -package be.scri.models - -data class BlockedNumber( - val id: Long, - val number: String, - val normalizedNumber: String, - val numberToCompare: String, -) diff --git a/app/src/main/java/be/scri/models/Clip.kt b/app/src/main/java/be/scri/models/Clip.kt deleted file mode 100644 index c441539f..00000000 --- a/app/src/main/java/be/scri/models/Clip.kt +++ /dev/null @@ -1,12 +0,0 @@ -package be.scri.models - -import androidx.room.ColumnInfo -import androidx.room.Entity -import androidx.room.Index -import androidx.room.PrimaryKey - -@Entity(tableName = "clips", indices = [(Index(value = ["id"], unique = true))]) -data class Clip( - @PrimaryKey(autoGenerate = true) var id: Long?, - @ColumnInfo(name = "value") var value: String, -) : ListItem() diff --git a/app/src/main/java/be/scri/models/ClipsSectionLabel.kt b/app/src/main/java/be/scri/models/ClipsSectionLabel.kt deleted file mode 100644 index 0e39c9d8..00000000 --- a/app/src/main/java/be/scri/models/ClipsSectionLabel.kt +++ /dev/null @@ -1,6 +0,0 @@ -package be.scri.models - -data class ClipsSectionLabel( - val value: String, - val isCurrent: Boolean, -) : ListItem() diff --git a/app/src/main/java/be/scri/models/FileDirItem.kt b/app/src/main/java/be/scri/models/FileDirItem.kt deleted file mode 100644 index 16ed504c..00000000 --- a/app/src/main/java/be/scri/models/FileDirItem.kt +++ /dev/null @@ -1,201 +0,0 @@ -package be.scri.models - -import android.content.Context -import android.net.Uri -import be.scri.extensions.formatDate -import be.scri.extensions.formatSize -import be.scri.extensions.getAlbum -import be.scri.extensions.getAndroidSAFDirectChildrenCount -import be.scri.extensions.getAndroidSAFFileCount -import be.scri.extensions.getAndroidSAFFileSize -import be.scri.extensions.getAndroidSAFLastModified -import be.scri.extensions.getArtist -import be.scri.extensions.getDirectChildrenCount -import be.scri.extensions.getDocumentFile -import be.scri.extensions.getDuration -import be.scri.extensions.getFastDocumentFile -import be.scri.extensions.getFileCount -import be.scri.extensions.getFormattedDuration -import be.scri.extensions.getImageResolution -import be.scri.extensions.getItemSize -import be.scri.extensions.getMediaStoreLastModified -import be.scri.extensions.getParentPath -import be.scri.extensions.getProperSize -import be.scri.extensions.getResolution -import be.scri.extensions.getSizeFromContentUri -import be.scri.extensions.getTitle -import be.scri.extensions.getVideoResolution -import be.scri.extensions.isPathOnOTG -import be.scri.extensions.isRestrictedSAFOnlyRoot -import be.scri.extensions.normalizeString -import be.scri.helpers.AlphanumericComparator -import be.scri.helpers.SORT_BY_DATE_MODIFIED -import be.scri.helpers.SORT_BY_EXTENSION -import be.scri.helpers.SORT_BY_NAME -import be.scri.helpers.SORT_BY_SIZE -import be.scri.helpers.SORT_DESCENDING -import be.scri.helpers.SORT_USE_NUMERIC_VALUE -import be.scri.helpers.isNougatPlus -import com.bumptech.glide.signature.ObjectKey -import java.io.File -import java.io.FileNotFoundException -import java.io.IOException - -open class FileDirItem( - val path: String, - val name: String = "", - var isDirectory: Boolean = false, - var children: Int = 0, - var size: Long = 0L, - var modified: Long = 0L, -) : Comparable { - companion object { - var sorting = 0 - } - - override fun toString() = "FileDirItem(path=$path, name=$name, isDirectory=$isDirectory, children=$children, size=$size, modified=$modified)" - - override fun compareTo(other: FileDirItem): Int = - if (isDirectory && !other.isDirectory) { - -1 - } else if (!isDirectory && other.isDirectory) { - 1 - } else { - var result: Int - when { - sorting and SORT_BY_NAME != 0 -> { - result = - if (sorting and SORT_USE_NUMERIC_VALUE != 0) { - AlphanumericComparator().compare(name.normalizeString().toLowerCase(), other.name.normalizeString().toLowerCase()) - } else { - name.normalizeString().toLowerCase().compareTo(other.name.normalizeString().toLowerCase()) - } - } - sorting and SORT_BY_SIZE != 0 -> - result = - when { - size == other.size -> 0 - size > other.size -> 1 - else -> -1 - } - sorting and SORT_BY_DATE_MODIFIED != 0 -> { - result = - when { - modified == other.modified -> 0 - modified > other.modified -> 1 - else -> -1 - } - } - else -> { - result = getExtension().toLowerCase().compareTo(other.getExtension().toLowerCase()) - } - } - - if (sorting and SORT_DESCENDING != 0) { - result *= -1 - } - result - } - - fun getExtension() = if (isDirectory) name else path.substringAfterLast('.', "") - - fun getBubbleText( - context: Context, - dateFormat: String? = null, - timeFormat: String? = null, - ) = when { - sorting and SORT_BY_SIZE != 0 -> size.formatSize() - sorting and SORT_BY_DATE_MODIFIED != 0 -> modified.formatDate(context, dateFormat, timeFormat) - sorting and SORT_BY_EXTENSION != 0 -> getExtension().toLowerCase() - else -> name - } - - fun getProperSize( - context: Context, - countHidden: Boolean, - ): Long = - when { - context.isRestrictedSAFOnlyRoot(path) -> context.getAndroidSAFFileSize(path) - context.isPathOnOTG(path) -> context.getDocumentFile(path)?.getItemSize(countHidden) ?: 0 - isNougatPlus() && path.startsWith("content://") -> { - try { - context.contentResolver - .openInputStream(Uri.parse(path)) - ?.available() - ?.toLong() ?: 0L - } catch (e: FileNotFoundException) { - context.getSizeFromContentUri(Uri.parse(path)) - } catch (e: IOException) { - context.getSizeFromContentUri(Uri.parse(path)) - } - } - else -> File(path).getProperSize(countHidden) - } - - fun getProperFileCount( - context: Context, - countHidden: Boolean, - ): Int = - when { - context.isRestrictedSAFOnlyRoot(path) -> context.getAndroidSAFFileCount(path, countHidden) - context.isPathOnOTG(path) -> context.getDocumentFile(path)?.getFileCount(countHidden) ?: 0 - else -> File(path).getFileCount(countHidden) - } - - fun getDirectChildrenCount( - context: Context, - countHiddenItems: Boolean, - ): Int = - when { - context.isRestrictedSAFOnlyRoot(path) -> context.getAndroidSAFDirectChildrenCount(path, countHiddenItems) - context.isPathOnOTG(path) -> - context - .getDocumentFile(path) - ?.listFiles() - ?.filter { if (countHiddenItems) true else !it.name!!.startsWith(".") } - ?.size - ?: 0 - else -> File(path).getDirectChildrenCount(context, countHiddenItems) - } - - fun getLastModified(context: Context): Long = - when { - context.isRestrictedSAFOnlyRoot(path) -> context.getAndroidSAFLastModified(path) - context.isPathOnOTG(path) -> context.getFastDocumentFile(path)?.lastModified() ?: 0L - isNougatPlus() && path.startsWith("content://") -> context.getMediaStoreLastModified(path) - else -> File(path).lastModified() - } - - fun getParentPath() = path.getParentPath() - - fun getDuration(context: Context) = context.getDuration(path)?.getFormattedDuration() - - fun getFileDurationSeconds(context: Context) = context.getDuration(path) - - fun getArtist(context: Context) = context.getArtist(path) - - fun getAlbum(context: Context) = context.getAlbum(path) - - fun getTitle(context: Context) = context.getTitle(path) - - fun getResolution(context: Context) = context.getResolution(path) - - fun getVideoResolution(context: Context) = context.getVideoResolution(path) - - fun getImageResolution(context: Context) = context.getImageResolution(path) - - fun getPublicUri(context: Context) = context.getDocumentFile(path)?.uri ?: "" - - fun getSignature(): String { - val lastModified = - if (modified > 1) { - modified - } else { - File(path).lastModified() - } - - return "$path-$lastModified-$size" - } - - fun getKey() = ObjectKey(getSignature()) -} diff --git a/app/src/main/java/be/scri/models/ListItem.kt b/app/src/main/java/be/scri/models/ListItem.kt deleted file mode 100644 index 13dd1e85..00000000 --- a/app/src/main/java/be/scri/models/ListItem.kt +++ /dev/null @@ -1,3 +0,0 @@ -package be.scri.models - -open class ListItem diff --git a/app/src/main/java/be/scri/models/MyTheme.kt b/app/src/main/java/be/scri/models/MyTheme.kt deleted file mode 100644 index 7d1c3887..00000000 --- a/app/src/main/java/be/scri/models/MyTheme.kt +++ /dev/null @@ -1,9 +0,0 @@ -package be.scri.models - -data class MyTheme( - val nameId: Int, - val textColorId: Int, - val backgroundColorId: Int, - val primaryColorId: Int, - val appIconColorId: Int, -) diff --git a/app/src/main/java/be/scri/models/RadioItem.kt b/app/src/main/java/be/scri/models/RadioItem.kt deleted file mode 100644 index b545ae33..00000000 --- a/app/src/main/java/be/scri/models/RadioItem.kt +++ /dev/null @@ -1,7 +0,0 @@ -package be.scri.models - -data class RadioItem( - val id: Int, - val title: String, - val value: Any = id, -) diff --git a/app/src/main/java/be/scri/models/SharedTheme.kt b/app/src/main/java/be/scri/models/SharedTheme.kt deleted file mode 100644 index f02d0c2b..00000000 --- a/app/src/main/java/be/scri/models/SharedTheme.kt +++ /dev/null @@ -1,11 +0,0 @@ -package be.scri.models - -data class SharedTheme( - val textColor: Int, - val backgroundColor: Int, - val primaryColor: Int, - val appIconColor: Int, - val navigationBarColor: Int, - val lastUpdatedTS: Int = 0, - val accentColor: Int, -) diff --git a/app/src/main/java/be/scri/views/LineColorPicker.kt b/app/src/main/java/be/scri/views/LineColorPicker.kt deleted file mode 100644 index 4572a6d7..00000000 --- a/app/src/main/java/be/scri/views/LineColorPicker.kt +++ /dev/null @@ -1,116 +0,0 @@ -package be.scri.views - -import android.content.Context -import android.util.AttributeSet -import android.view.LayoutInflater -import android.view.MotionEvent -import android.widget.LinearLayout -import be.scri.R -import be.scri.extensions.isRTLLayout -import be.scri.extensions.onGlobalLayout -import be.scri.interfaces.LineColorPickerListener - -class LineColorPicker( - context: Context, - attrs: AttributeSet, -) : LinearLayout(context, attrs) { - private var colorsCount = 0 - private var pickerWidth = 0 - private var stripeWidth = 0 - private var unselectedMargin = 0 - private var lastColorIndex = -1 - private var wasInit = false - private var colors = ArrayList() - - var listener: LineColorPickerListener? = null - - init { - unselectedMargin = context.resources.getDimension(R.dimen.line_color_picker_margin).toInt() - onGlobalLayout { - if (pickerWidth == 0) { - pickerWidth = width - - if (colorsCount != 0) { - stripeWidth = width / colorsCount - } - } - - if (!wasInit) { - wasInit = true - initColorPicker() - updateItemMargin(lastColorIndex, false) - } - } - orientation = LinearLayout.HORIZONTAL - - setOnTouchListener { view, motionEvent -> - when (motionEvent.action) { - MotionEvent.ACTION_MOVE, MotionEvent.ACTION_UP -> { - if (pickerWidth != 0 && stripeWidth != 0) { - touchAt(motionEvent.x.toInt()) - } - } - } - true - } - } - - fun updateColors( - colors: ArrayList, - selectColorIndex: Int = -1, - ) { - this.colors = colors - colorsCount = colors.size - if (pickerWidth != 0) { - stripeWidth = pickerWidth / colorsCount - } - - if (selectColorIndex != -1) { - lastColorIndex = selectColorIndex - } - - initColorPicker() - updateItemMargin(lastColorIndex, false) - } - - // do not remove ": Int", it causes "NoSuchMethodError" for some reason - fun getCurrentColor(): Int = colors[lastColorIndex] - - private fun initColorPicker() { - removeAllViews() - val inflater = LayoutInflater.from(context) - colors.forEach { - inflater.inflate(R.layout.empty_image_view, this, false).apply { - setBackgroundColor(it) - addView(this) - } - } - } - - private fun touchAt(touchX: Int) { - var colorIndex = touchX / stripeWidth - if (context.isRTLLayout) { - colorIndex = colors.size - colorIndex - 1 - } - val index = Math.max(0, Math.min(colorIndex, colorsCount - 1)) - if (lastColorIndex != index) { - updateItemMargin(lastColorIndex, true) - lastColorIndex = index - updateItemMargin(index, false) - listener?.colorChanged(index, colors[index]) - } - } - - private fun updateItemMargin( - index: Int, - addMargin: Boolean, - ) { - getChildAt(index)?.apply { - (layoutParams as LinearLayout.LayoutParams).apply { - topMargin = if (addMargin) unselectedMargin else 0 - bottomMargin = if (addMargin) unselectedMargin else 0 - } - requestLayout() - } - } -} diff --git a/app/src/main/java/be/scri/views/MyAppCompatCheckbox.kt b/app/src/main/java/be/scri/views/MyAppCompatCheckbox.kt deleted file mode 100644 index 275a711a..00000000 --- a/app/src/main/java/be/scri/views/MyAppCompatCheckbox.kt +++ /dev/null @@ -1,31 +0,0 @@ -package be.scri.views - -import android.content.Context -import android.content.res.ColorStateList -import android.util.AttributeSet -import androidx.appcompat.widget.AppCompatCheckBox -import be.scri.extensions.adjustAlpha - -class MyAppCompatCheckbox : AppCompatCheckBox { - constructor(context: Context) : super(context) - - constructor(context: Context, attrs: AttributeSet) : super(context, attrs) - - constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle) - - fun setColors( - textColor: Int, - accentColor: Int, - ) { - setTextColor(textColor) - val colorStateList = - ColorStateList( - arrayOf( - intArrayOf(-android.R.attr.state_checked), - intArrayOf(android.R.attr.state_checked), - ), - intArrayOf(textColor.adjustAlpha(0.6f), accentColor), - ) - supportButtonTintList = colorStateList - } -} diff --git a/app/src/main/java/be/scri/views/MyCompatRadioButton.kt b/app/src/main/java/be/scri/views/MyCompatRadioButton.kt deleted file mode 100644 index 247d9fd9..00000000 --- a/app/src/main/java/be/scri/views/MyCompatRadioButton.kt +++ /dev/null @@ -1,31 +0,0 @@ -package be.scri.views - -import android.content.Context -import android.content.res.ColorStateList -import android.util.AttributeSet -import androidx.appcompat.widget.AppCompatRadioButton -import be.scri.extensions.adjustAlpha - -class MyCompatRadioButton : AppCompatRadioButton { - constructor(context: Context) : super(context) - - constructor(context: Context, attrs: AttributeSet) : super(context, attrs) - - constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle) - - fun setColors( - textColor: Int, - accentColor: Int, - ) { - setTextColor(textColor) - val colorStateList = - ColorStateList( - arrayOf( - intArrayOf(-android.R.attr.state_checked), - intArrayOf(android.R.attr.state_checked), - ), - intArrayOf(textColor.adjustAlpha(0.6f), accentColor), - ) - supportButtonTintList = colorStateList - } -} diff --git a/app/src/main/java/be/scri/views/MyEditText.kt b/app/src/main/java/be/scri/views/MyEditText.kt deleted file mode 100644 index aece3552..00000000 --- a/app/src/main/java/be/scri/views/MyEditText.kt +++ /dev/null @@ -1,27 +0,0 @@ -package be.scri.views - -import android.content.Context -import android.util.AttributeSet -import be.scri.extensions.adjustAlpha -import be.scri.extensions.applyColorFilter -import be.scri.helpers.MEDIUM_ALPHA - -class MyEditText : androidx.appcompat.widget.AppCompatEditText { - constructor(context: Context) : super(context) - - constructor(context: Context, attrs: AttributeSet) : super(context, attrs) - - constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle) - - fun setColors( - textColor: Int, - accentColor: Int, - ) { - background?.mutate()?.applyColorFilter(accentColor) - - // requires android:textCursorDrawable="@null" in xml to color the cursor too - setTextColor(textColor) - setHintTextColor(textColor.adjustAlpha(MEDIUM_ALPHA)) - setLinkTextColor(accentColor) - } -} diff --git a/app/src/main/java/be/scri/views/MyFloatingActionButton.kt b/app/src/main/java/be/scri/views/MyFloatingActionButton.kt deleted file mode 100644 index 2d9035d7..00000000 --- a/app/src/main/java/be/scri/views/MyFloatingActionButton.kt +++ /dev/null @@ -1,21 +0,0 @@ -package be.scri.views - -import android.content.Context -import android.content.res.ColorStateList -import android.util.AttributeSet -import be.scri.extensions.applyColorFilter -import be.scri.extensions.getContrastColor -import com.google.android.material.floatingactionbutton.FloatingActionButton - -class MyFloatingActionButton : FloatingActionButton { - constructor(context: Context) : super(context) - - constructor(context: Context, attrs: AttributeSet) : super(context, attrs) - - constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle) - - fun setColors(accentColor: Int) { - backgroundTintList = ColorStateList.valueOf(accentColor) - applyColorFilter(accentColor.getContrastColor()) - } -} diff --git a/app/src/main/java/be/scri/views/MyKeyboardView.kt b/app/src/main/java/be/scri/views/MyKeyboardView.kt index 61afe1be..2e530b4a 100644 --- a/app/src/main/java/be/scri/views/MyKeyboardView.kt +++ b/app/src/main/java/be/scri/views/MyKeyboardView.kt @@ -55,6 +55,7 @@ import be.scri.helpers.SHIFT_OFF import be.scri.helpers.SHIFT_ON_ONE_CHAR import be.scri.helpers.SHIFT_ON_PERMANENT import java.util.Arrays +import java.util.Locale @SuppressLint("UseCompatLoadingForDrawables") @Suppress("LargeClass") @@ -280,8 +281,7 @@ class MyKeyboardView try { for (i in 0 until indexCnt) { - val attr = attributes.getIndex(i) - when (attr) { + when (val attr = attributes.getIndex(i)) { R.styleable.MyKeyboardView_keyTextSize -> mKeyTextSize = attributes.getDimensionPixelSize(attr, 18) } } @@ -469,7 +469,7 @@ class MyKeyboardView private fun adjustCase(label: CharSequence): CharSequence? { var newLabel: CharSequence? = label if (newLabel != null && newLabel.isNotEmpty() && mKeyboard!!.mShiftState > SHIFT_OFF && newLabel.length < 3 && Character.isLowerCase(newLabel[0])) { - newLabel = newLabel.toString().toUpperCase() + newLabel = newLabel.toString().uppercase(Locale.getDefault()) } return newLabel } diff --git a/app/src/main/java/be/scri/views/MyLinearLayoutManager.kt b/app/src/main/java/be/scri/views/MyLinearLayoutManager.kt deleted file mode 100644 index 36c88e5e..00000000 --- a/app/src/main/java/be/scri/views/MyLinearLayoutManager.kt +++ /dev/null @@ -1,17 +0,0 @@ -package be.scri.views - -import android.content.Context -import android.util.AttributeSet -import androidx.recyclerview.widget.LinearLayoutManager - -class MyLinearLayoutManager : LinearLayoutManager { - constructor(context: Context) : super(context) - - constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes) - - constructor(context: Context, orientation: Int, reverseLayout: Boolean) : super(context, orientation, reverseLayout) - - // fixes crash java.lang.IndexOutOfBoundsException: Inconsistency detected... - // taken from https://stackoverflow.com/a/33985508/1967672 - override fun supportsPredictiveItemAnimations() = false -} diff --git a/app/src/main/java/be/scri/views/MyScrollView.kt b/app/src/main/java/be/scri/views/MyScrollView.kt deleted file mode 100644 index 4b1cc2c7..00000000 --- a/app/src/main/java/be/scri/views/MyScrollView.kt +++ /dev/null @@ -1,35 +0,0 @@ -package be.scri.views - -import android.content.Context -import android.util.AttributeSet -import android.view.MotionEvent -import android.widget.ScrollView - -class MyScrollView : ScrollView { - var isScrollable = true - - constructor(context: Context) : super(context) - - constructor(context: Context, attrs: AttributeSet) : super(context, attrs) - - constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle) - - override fun onTouchEvent(ev: MotionEvent): Boolean = - when (ev.action) { - MotionEvent.ACTION_DOWN -> { - if (isScrollable) { - super.onTouchEvent(ev) - } else { - isScrollable - } - } - else -> super.onTouchEvent(ev) - } - - override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean = - if (!isScrollable) { - false - } else { - super.onInterceptTouchEvent(ev) - } -} diff --git a/app/src/main/java/be/scri/views/MyTextView.kt b/app/src/main/java/be/scri/views/MyTextView.kt deleted file mode 100644 index fc600898..00000000 --- a/app/src/main/java/be/scri/views/MyTextView.kt +++ /dev/null @@ -1,20 +0,0 @@ -package be.scri.views - -import android.content.Context -import android.util.AttributeSet - -class MyTextView : androidx.appcompat.widget.AppCompatTextView { - constructor(context: Context) : super(context) - - constructor(context: Context, attrs: AttributeSet) : super(context, attrs) - - constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle) - - fun setColors( - textColor: Int, - accentColor: Int, - ) { - setTextColor(textColor) - setLinkTextColor(accentColor) - } -} diff --git a/app/src/main/java/be/scri/views/bottomactionmenu/BottomActionMenuCallback.kt b/app/src/main/java/be/scri/views/bottomactionmenu/BottomActionMenuCallback.kt deleted file mode 100644 index 3a1ff77e..00000000 --- a/app/src/main/java/be/scri/views/bottomactionmenu/BottomActionMenuCallback.kt +++ /dev/null @@ -1,9 +0,0 @@ -package be.scri.views.bottomactionmenu - -interface BottomActionMenuCallback { - fun onItemClicked(item: BottomActionMenuItem) {} - - fun onViewCreated(view: BottomActionMenuView) {} - - fun onViewDestroyed() {} -} diff --git a/app/src/main/java/be/scri/views/bottomactionmenu/BottomActionMenuItem.kt b/app/src/main/java/be/scri/views/bottomactionmenu/BottomActionMenuItem.kt deleted file mode 100644 index de5d0ed0..00000000 --- a/app/src/main/java/be/scri/views/bottomactionmenu/BottomActionMenuItem.kt +++ /dev/null @@ -1,13 +0,0 @@ -package be.scri.views.bottomactionmenu - -import android.view.View -import androidx.annotation.DrawableRes -import androidx.annotation.IdRes - -data class BottomActionMenuItem( - @IdRes val id: Int, - val title: String, - @DrawableRes val icon: Int = View.NO_ID, - val showAsAction: Boolean, - val isVisible: Boolean = true, -) diff --git a/app/src/main/java/be/scri/views/bottomactionmenu/BottomActionMenuItemPopup.kt b/app/src/main/java/be/scri/views/bottomactionmenu/BottomActionMenuItemPopup.kt deleted file mode 100644 index e83eeee3..00000000 --- a/app/src/main/java/be/scri/views/bottomactionmenu/BottomActionMenuItemPopup.kt +++ /dev/null @@ -1,293 +0,0 @@ -package be.scri.views.bottomactionmenu - -import android.content.Context -import android.graphics.Color -import android.graphics.Rect -import android.view.Gravity -import android.view.LayoutInflater -import android.view.View -import android.view.View.MeasureSpec -import android.view.ViewGroup -import android.view.WindowManager -import android.widget.ArrayAdapter -import android.widget.FrameLayout -import android.widget.ListView -import android.widget.PopupWindow -import androidx.core.content.ContextCompat -import androidx.core.widget.PopupWindowCompat -import be.scri.R -import be.scri.databinding.ItemActionModePopupBinding -import be.scri.extensions.applyColorFilter -import be.scri.extensions.windowManager -import be.scri.helpers.isRPlus - -class BottomActionMenuItemPopup( - private val context: Context, - private val items: List, - private val onSelect: (BottomActionMenuItem) -> Unit, -) { - private val popup = PopupWindow(context, null, android.R.attr.popupMenuStyle) - private var anchorView: View? = null - private var dropDownWidth = ViewGroup.LayoutParams.WRAP_CONTENT - private var dropDownHeight = ViewGroup.LayoutParams.WRAP_CONTENT - private val tempRect = Rect() - private val popupMinWidth: Int - private val popupPaddingBottom: Int - private val popupPaddingStart: Int - private val popupPaddingEnd: Int - private val popupPaddingTop: Int - private lateinit var binding: ItemActionModePopupBinding - - val isShowing: Boolean - get() = popup.isShowing - - private val popupListAdapter = - object : ArrayAdapter(context, R.layout.item_action_mode_popup, items) { - override fun getView( - position: Int, - convertView: View?, - parent: ViewGroup, - ): View { - val binding = ItemActionModePopupBinding.inflate(LayoutInflater.from(context), parent, false) - - val item = items[position] - binding.cabItem.text = item.title - if (item.icon != View.NO_ID) { - val icon = ContextCompat.getDrawable(context, item.icon) - icon?.applyColorFilter(Color.WHITE) - binding.cabItem.setCompoundDrawablesWithIntrinsicBounds(icon, null, null, null) - } - - binding.root.setOnClickListener { - onSelect.invoke(item) - popup.dismiss() - } - - return binding.root - } - } - - init { - popup.isFocusable = true - popupMinWidth = context.resources.getDimensionPixelSize(R.dimen.cab_popup_menu_min_width) - popupPaddingStart = context.resources.getDimensionPixelSize(R.dimen.smaller_margin) - popupPaddingEnd = context.resources.getDimensionPixelSize(R.dimen.smaller_margin) - popupPaddingTop = context.resources.getDimensionPixelSize(R.dimen.smaller_margin) - popupPaddingBottom = context.resources.getDimensionPixelSize(R.dimen.smaller_margin) - binding = ItemActionModePopupBinding.inflate(LayoutInflater.from(context)) - } - - fun show(anchorView: View) { - this.anchorView = anchorView - buildDropDown() - PopupWindowCompat.setWindowLayoutType(popup, WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL) - popup.isOutsideTouchable = true - popup.width = dropDownWidth - popup.height = dropDownHeight - var x = 0 - var y = 0 - val contentView: View = popup.contentView - val windowRect = Rect() - contentView.getWindowVisibleDisplayFrame(windowRect) - val windowW = windowRect.width() - val windowH = windowRect.height() - contentView.measure( - makeDropDownMeasureSpec(dropDownWidth, windowW), - makeDropDownMeasureSpec(dropDownHeight, windowH), - ) - - val anchorLocation = IntArray(2) - anchorView.getLocationInWindow(anchorLocation) - x += anchorLocation[0] - - y += anchorView.height * 2 - x -= dropDownWidth - anchorView.width - - popup.showAtLocation(contentView, Gravity.BOTTOM, x, y) - } - - internal fun dismiss() { - popup.dismiss() - popup.contentView = null - } - - private fun buildDropDown() { - var otherHeights = 0 - val dropDownList = - ListView(context).apply { - adapter = popupListAdapter - isFocusable = true - divider = null - isFocusableInTouchMode = true - clipToPadding = false - isVerticalScrollBarEnabled = true - isHorizontalScrollBarEnabled = false - clipToOutline = true - elevation = 3f - setPaddingRelative(popupPaddingStart, popupPaddingTop, popupPaddingEnd, popupPaddingBottom) - } - - val screenWidth = - if (isRPlus()) { - context.windowManager.currentWindowMetrics.bounds - .width() - } else { - context.windowManager.defaultDisplay.width - } - - val width = measureMenuSizeAndGetWidth((0.8 * screenWidth).toInt()) - updateContentWidth(width) - popup.contentView = dropDownList - - // getMaxAvailableHeight() subtracts the padding, so we put it back - // to get the available height for the whole window. - val padding: Int - val popupBackground = popup.background - padding = - if (popupBackground != null) { - popupBackground.getPadding(tempRect) - tempRect.top + tempRect.bottom - } else { - tempRect.setEmpty() - 0 - } - - val maxHeight = popup.getMaxAvailableHeight(anchorView!!, 0) - val listContent = measureHeightOfChildrenCompat(maxHeight - otherHeights) - if (listContent > 0) { - val listPadding = dropDownList.paddingTop + dropDownList.paddingBottom - otherHeights += padding + listPadding - } - - dropDownHeight = listContent + otherHeights - dropDownList.layoutParams = ViewGroup.LayoutParams(dropDownWidth, dropDownHeight) - } - - private fun updateContentWidth(width: Int) { - val popupBackground = popup.background - dropDownWidth = - if (popupBackground != null) { - popupBackground.getPadding(tempRect) - tempRect.left + tempRect.right + width - } else { - width - } - } - - /** - * @see androidx.appcompat.widget.DropDownListView.measureHeightOfChildrenCompat - */ - private fun measureHeightOfChildrenCompat(maxHeight: Int): Int { - val parent = FrameLayout(context) - val widthMeasureSpec = MeasureSpec.makeMeasureSpec(dropDownWidth, MeasureSpec.EXACTLY) - - // Include the padding of the list - var returnedHeight = 0 - - val count = popupListAdapter.count - - var child: View? = null - var viewType = 0 - for (i in 0 until count) { - val positionType = popupListAdapter.getItemViewType(i) - if (positionType != viewType) { - child = null - viewType = positionType - } - child = popupListAdapter.getView(i, child, parent) - - // Compute child height spec - val heightMeasureSpec: Int - var childLayoutParams: ViewGroup.LayoutParams? = child.layoutParams - - if (childLayoutParams == null) { - childLayoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) - child.layoutParams = childLayoutParams - } - - heightMeasureSpec = - if (childLayoutParams.height > 0) { - MeasureSpec.makeMeasureSpec( - childLayoutParams.height, - MeasureSpec.EXACTLY, - ) - } else { - MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED) - } - child.measure(widthMeasureSpec, heightMeasureSpec) - // Since this view was measured directly against the parent measure - // spec, we must measure it again before reuse. - child.forceLayout() - - val marginLayoutParams = childLayoutParams as? ViewGroup.MarginLayoutParams - val topMargin = marginLayoutParams?.topMargin ?: 0 - val bottomMargin = marginLayoutParams?.bottomMargin ?: 0 - val verticalMargin = topMargin + bottomMargin - - returnedHeight += child.measuredHeight + verticalMargin - - if (returnedHeight >= maxHeight) { - // We went over, figure out which height to return. If returnedHeight > - // maxHeight, then the i'th position did not fit completely. - return maxHeight - } - } - - // At this point, we went through the range of children, and they each - // completely fit, so return the returnedHeight - return returnedHeight - } - - /** - * @see androidx.appcompat.view.menu.MenuPopup.measureIndividualMenuWidth - */ - private fun measureMenuSizeAndGetWidth(maxAllowedWidth: Int): Int { - val parent = FrameLayout(context) - var maxWidth = popupMinWidth - var itemView: View? = null - var itemType = 0 - - val widthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED) - val heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED) - for (i in 0 until popupListAdapter.count) { - val positionType: Int = popupListAdapter.getItemViewType(i) - if (positionType != itemType) { - itemType = positionType - itemView = null - } - itemView = popupListAdapter.getView(i, itemView, parent) - itemView.measure(widthMeasureSpec, heightMeasureSpec) - val itemWidth = itemView.measuredWidth - if (itemWidth >= maxAllowedWidth) { - return maxAllowedWidth - } else if (itemWidth > maxWidth) { - maxWidth = itemWidth - } - } - return maxWidth - } - - private fun makeDropDownMeasureSpec( - measureSpec: Int, - maxSize: Int, - ): Int = - MeasureSpec.makeMeasureSpec( - getDropDownMeasureSpecSize(measureSpec, maxSize), - getDropDownMeasureSpecMode(measureSpec), - ) - - private fun getDropDownMeasureSpecSize( - measureSpec: Int, - maxSize: Int, - ): Int = - when (measureSpec) { - ViewGroup.LayoutParams.MATCH_PARENT -> maxSize - else -> MeasureSpec.getSize(measureSpec) - } - - private fun getDropDownMeasureSpecMode(measureSpec: Int): Int = - when (measureSpec) { - ViewGroup.LayoutParams.WRAP_CONTENT -> MeasureSpec.UNSPECIFIED - else -> MeasureSpec.EXACTLY - } -} diff --git a/app/src/main/java/be/scri/views/bottomactionmenu/BottomActionMenuView.kt b/app/src/main/java/be/scri/views/bottomactionmenu/BottomActionMenuView.kt deleted file mode 100644 index 2f45379a..00000000 --- a/app/src/main/java/be/scri/views/bottomactionmenu/BottomActionMenuView.kt +++ /dev/null @@ -1,226 +0,0 @@ -package be.scri.views.bottomactionmenu - -import android.animation.Animator -import android.animation.AnimatorListenerAdapter -import android.animation.TimeInterpolator -import android.content.Context -import android.graphics.Color -import android.util.AttributeSet -import android.view.LayoutInflater -import android.view.View -import android.view.ViewPropertyAnimator -import android.widget.ImageView -import android.widget.LinearLayout -import androidx.annotation.IdRes -import be.scri.R -import be.scri.extensions.applyColorFilter -import be.scri.extensions.beVisibleIf -import be.scri.extensions.toast -import be.scri.extensions.windowManager -import be.scri.helpers.isRPlus -import com.google.android.material.animation.AnimationUtils - -class BottomActionMenuView : LinearLayout { - companion object { - private const val ENTER_ANIMATION_DURATION = 225 - private const val EXIT_ANIMATION_DURATION = 175 - } - - constructor(context: Context) : super(context) - - constructor(context: Context, attrs: AttributeSet) : super(context, attrs) - - constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle) - - private val inflater = LayoutInflater.from(context) - private val itemsLookup = LinkedHashMap() - private val items: List - get() = - itemsLookup.values - .toList() - .sortedWith( - compareByDescending { - it.showAsAction - }.thenBy { - it.icon != View.NO_ID - }, - ).filter { it.isVisible } - - private var currentAnimator: ViewPropertyAnimator? = null - private var callback: BottomActionMenuCallback? = null - private var itemPopup: BottomActionMenuItemPopup? = null - - init { - orientation = HORIZONTAL - elevation = 2f - } - - fun setCallback(listener: BottomActionMenuCallback?) { - this.callback = listener - } - - fun hide() { - slideDownToGone() - } - - fun show() { - slideUpToVisible() - } - - private fun slideUpToVisible() { - currentAnimator?.also { - it.cancel() - clearAnimation() - } - animateChildTo(0, ENTER_ANIMATION_DURATION.toLong(), AnimationUtils.LINEAR_OUT_SLOW_IN_INTERPOLATOR, true) - } - - private fun slideDownToGone() { - currentAnimator?.also { - currentAnimator?.cancel() - clearAnimation() - } - animateChildTo( - height + (layoutParams as MarginLayoutParams).bottomMargin, - EXIT_ANIMATION_DURATION.toLong(), - AnimationUtils.FAST_OUT_LINEAR_IN_INTERPOLATOR, - ) - } - - private fun animateChildTo( - targetY: Int, - duration: Long, - interpolator: TimeInterpolator, - visible: Boolean = false, - ) { - currentAnimator = - animate() - .translationY(targetY.toFloat()) - .setInterpolator(interpolator) - .setDuration(duration) - .setListener( - object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - currentAnimator = null - beVisibleIf(visible) - } - }, - ) - } - - fun setup(items: List) { - items.forEach { itemsLookup[it.id] = it } - init() - } - - fun add(item: BottomActionMenuItem) { - setItem(item) - } - - private fun setItem(item: BottomActionMenuItem?) { - item?.let { - val oldItem = itemsLookup[item.id] - itemsLookup[item.id] = item - if (oldItem != item) { - init() - } - } - } - - fun toggleItemVisibility( - @IdRes itemId: Int, - show: Boolean, - ) { - val item = itemsLookup[itemId] - setItem(item?.copy(isVisible = show)) - } - - private fun init() { - removeAllViews() - val maxItemsBeforeOverflow = computeMaxItemsBeforeOverflow() - val allItems = items - for (i in allItems.indices) { - if (i <= maxItemsBeforeOverflow) { - drawNormalItem(allItems[i]) - } else { - drawOverflowItem(allItems.slice(i until allItems.size)) - break - } - } - } - - private fun computeMaxItemsBeforeOverflow(): Int { - val itemsToShowAsAction = items.filter { it.showAsAction && it.icon != View.NO_ID } - val itemMinWidth = context.resources.getDimensionPixelSize(R.dimen.cab_item_min_width) - val totalActionWidth = (itemsToShowAsAction.size + 1) * itemMinWidth - val screenWidth = - if (isRPlus()) { - context.windowManager.currentWindowMetrics.bounds - .width() - } else { - context.windowManager.defaultDisplay.width - } - val result = - if (screenWidth > totalActionWidth) { - itemsToShowAsAction.size - } else { - screenWidth / itemMinWidth - } - return result - 1 - } - - private fun drawNormalItem(item: BottomActionMenuItem) { - (inflater.inflate(R.layout.item_action_mode, this, false) as ImageView).apply { - setupItem(item) - setOnClickListener { - if (itemPopup?.isShowing == true) { - itemPopup?.dismiss() - } else { - callback?.onItemClicked(item) - } - } - setOnLongClickListener { - context.toast(item.title) - true - } - addView(this) - } - } - - private fun drawOverflowItem(overFlowItems: List) { - (inflater.inflate(R.layout.item_action_mode, this, false) as ImageView).apply { - setImageResource(R.drawable.ic_three_dots_vector) - val contentDesc = context.getString(R.string.more_info) - contentDescription = contentDesc - applyColorFilter(Color.WHITE) - itemPopup = getOverflowPopup(overFlowItems) - setOnClickListener { - if (itemPopup?.isShowing == true) { - itemPopup?.dismiss() - } else { - itemPopup?.show(it) - } - } - setOnLongClickListener { - context.toast(contentDesc) - true - } - addView(this) - } - } - - private fun ImageView.setupItem(item: BottomActionMenuItem) { - id = item.id - contentDescription = item.title - if (item.icon != View.NO_ID) { - setImageResource(item.icon) - } - beVisibleIf(item.isVisible) - applyColorFilter(Color.WHITE) - } - - private fun getOverflowPopup(overFlowItems: List): BottomActionMenuItemPopup = - BottomActionMenuItemPopup(context, overFlowItems) { - callback?.onItemClicked(it) - } -} diff --git a/app/src/main/res/drawable/actionmenu_background.xml b/app/src/main/res/drawable/actionmenu_background.xml deleted file mode 100644 index 170a05d3..00000000 --- a/app/src/main/res/drawable/actionmenu_background.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/app/src/main/res/drawable/ic_settings_cog_vector.xml b/app/src/main/res/drawable/ic_settings_cog_vector.xml deleted file mode 100644 index d63a9460..00000000 --- a/app/src/main/res/drawable/ic_settings_cog_vector.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_stop_vector.xml b/app/src/main/res/drawable/ic_stop_vector.xml deleted file mode 100644 index 2d77b965..00000000 --- a/app/src/main/res/drawable/ic_stop_vector.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_three_dots_vector.xml b/app/src/main/res/drawable/ic_three_dots_vector.xml deleted file mode 100644 index 3d01e3a0..00000000 --- a/app/src/main/res/drawable/ic_three_dots_vector.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_unhide_vector.xml b/app/src/main/res/drawable/ic_unhide_vector.xml deleted file mode 100644 index 4ad95f89..00000000 --- a/app/src/main/res/drawable/ic_unhide_vector.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/app/src/main/res/layout/dialog_custom_interval_picker.xml b/app/src/main/res/layout/dialog_custom_interval_picker.xml deleted file mode 100644 index d64b1ccd..00000000 --- a/app/src/main/res/layout/dialog_custom_interval_picker.xml +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/dialog_line_color_picker.xml b/app/src/main/res/layout/dialog_line_color_picker.xml deleted file mode 100644 index 5e4e3724..00000000 --- a/app/src/main/res/layout/dialog_line_color_picker.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - - - - - - diff --git a/app/src/main/res/layout/dialog_message.xml b/app/src/main/res/layout/dialog_message.xml deleted file mode 100644 index b1cc86d3..00000000 --- a/app/src/main/res/layout/dialog_message.xml +++ /dev/null @@ -1,11 +0,0 @@ - - diff --git a/app/src/main/res/layout/dialog_radio_group.xml b/app/src/main/res/layout/dialog_radio_group.xml deleted file mode 100644 index 0da0e43d..00000000 --- a/app/src/main/res/layout/dialog_radio_group.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - diff --git a/app/src/main/res/layout/dialog_textview.xml b/app/src/main/res/layout/dialog_textview.xml deleted file mode 100644 index 025954dc..00000000 --- a/app/src/main/res/layout/dialog_textview.xml +++ /dev/null @@ -1,9 +0,0 @@ - - diff --git a/app/src/main/res/layout/dialog_title.xml b/app/src/main/res/layout/dialog_title.xml deleted file mode 100644 index d572c09b..00000000 --- a/app/src/main/res/layout/dialog_title.xml +++ /dev/null @@ -1,11 +0,0 @@ - - diff --git a/app/src/main/res/layout/dialog_write_permission.xml b/app/src/main/res/layout/dialog_write_permission.xml deleted file mode 100644 index 80802e18..00000000 --- a/app/src/main/res/layout/dialog_write_permission.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - - diff --git a/app/src/main/res/layout/dialog_write_permission_otg.xml b/app/src/main/res/layout/dialog_write_permission_otg.xml deleted file mode 100644 index aba828fe..00000000 --- a/app/src/main/res/layout/dialog_write_permission_otg.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - diff --git a/app/src/main/res/layout/empty_image_view.xml b/app/src/main/res/layout/empty_image_view.xml deleted file mode 100644 index baf0890f..00000000 --- a/app/src/main/res/layout/empty_image_view.xml +++ /dev/null @@ -1,9 +0,0 @@ - - diff --git a/app/src/main/res/layout/item_action_mode.xml b/app/src/main/res/layout/item_action_mode.xml deleted file mode 100644 index c8ac1a83..00000000 --- a/app/src/main/res/layout/item_action_mode.xml +++ /dev/null @@ -1,13 +0,0 @@ - - diff --git a/app/src/main/res/layout/item_action_mode_popup.xml b/app/src/main/res/layout/item_action_mode_popup.xml deleted file mode 100644 index 81d3c2ba..00000000 --- a/app/src/main/res/layout/item_action_mode_popup.xml +++ /dev/null @@ -1,15 +0,0 @@ - - diff --git a/app/src/main/res/layout/item_breadcrumb.xml b/app/src/main/res/layout/item_breadcrumb.xml deleted file mode 100644 index 53a40375..00000000 --- a/app/src/main/res/layout/item_breadcrumb.xml +++ /dev/null @@ -1,10 +0,0 @@ - - diff --git a/app/src/main/res/layout/item_breadcrumb_first.xml b/app/src/main/res/layout/item_breadcrumb_first.xml deleted file mode 100644 index 8831567d..00000000 --- a/app/src/main/res/layout/item_breadcrumb_first.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - diff --git a/app/src/main/res/layout/item_faq.xml b/app/src/main/res/layout/item_faq.xml deleted file mode 100644 index 11bfb8a3..00000000 --- a/app/src/main/res/layout/item_faq.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - diff --git a/app/src/main/res/layout/item_filepicker_list.xml b/app/src/main/res/layout/item_filepicker_list.xml deleted file mode 100644 index b4bc1d85..00000000 --- a/app/src/main/res/layout/item_filepicker_list.xml +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/radio_button.xml b/app/src/main/res/layout/radio_button.xml deleted file mode 100644 index 5662ed4d..00000000 --- a/app/src/main/res/layout/radio_button.xml +++ /dev/null @@ -1,7 +0,0 @@ - - diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 2c940855..2bd7192c 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -25,7 +25,6 @@