diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 0aa7c870..6506b89b 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -15,8 +15,12 @@ plugins { id("org.jmailen.kotlinter") id("io.gitlab.arturbosch.detekt") id("com.google.devtools.ksp") version "2.0.0-1.0.22" apply true + id("de.mannodermaus.android-junit5") version "1.11.2.0" } + val kotlinVersion by extra("2.0.0") +val junit5Version by extra("5.11.2") +val mockkVersion by extra("1.13.13") android { compileSdk = 34 @@ -114,6 +118,11 @@ dependencies { implementation("androidx.recyclerview:recyclerview:1.3.2") implementation("androidx.cardview:cardview:1.0.0") implementation("androidx.viewpager2:viewpager2:1.1.0") + implementation("com.google.android.play:core:1.10.0") + + testImplementation("org.junit.jupiter:junit-jupiter-api:$junit5Version") + testImplementation("io.mockk:mockk:$mockkVersion") + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:$junit5Version") api("joda-time:joda-time:2.10.13") api("com.github.tibbi:RecyclerView-FastScroller:e7d3e150c4") diff --git a/app/src/main/java/be/scri/activities/BaseSimpleActivity.kt b/app/src/main/java/be/scri/activities/BaseSimpleActivity.kt index 4a9fdaab..5277982b 100644 --- a/app/src/main/java/be/scri/activities/BaseSimpleActivity.kt +++ b/app/src/main/java/be/scri/activities/BaseSimpleActivity.kt @@ -16,20 +16,14 @@ import android.os.Build import android.os.Bundle import android.provider.DocumentsContract import android.provider.MediaStore -import android.provider.Settings import android.telecom.TelecomManager import android.view.Menu import android.view.MenuItem import android.view.View import android.view.WindowManager -import android.widget.Toast import androidx.annotation.RequiresApi import androidx.appcompat.app.AppCompatActivity -import androidx.core.app.ActivityCompat import be.scri.R -import be.scri.dialogs.ConfirmationDialog -import be.scri.dialogs.WritePermissionDialog -import be.scri.dialogs.WritePermissionDialog.Mode import be.scri.extensions.addBit import be.scri.extensions.baseConfig import be.scri.extensions.buildDocumentUriSdk30 @@ -41,21 +35,13 @@ import be.scri.extensions.getColoredDrawableWithColor import be.scri.extensions.getContrastColor import be.scri.extensions.getFirstParentLevel import be.scri.extensions.getFirstParentPath -import be.scri.extensions.getPermissionString import be.scri.extensions.getProperStatusBarColor import be.scri.extensions.getProperTextColor import be.scri.extensions.getThemeId -import be.scri.extensions.hasPermission import be.scri.extensions.hideKeyboard import be.scri.extensions.humanizePath -import be.scri.extensions.isAppInstalledOnSDCard import be.scri.extensions.isPathOnOTG import be.scri.extensions.isPathOnSD -import be.scri.extensions.isShowingAndroidSAFDialog -import be.scri.extensions.isShowingOTGDialog -import be.scri.extensions.isShowingSAFCreateDocumentDialogSdk30 -import be.scri.extensions.isShowingSAFDialog -import be.scri.extensions.isShowingSAFDialogSdk30 import be.scri.extensions.removeBit import be.scri.extensions.showErrorToast import be.scri.extensions.storeAndroidTreeUri @@ -282,16 +268,6 @@ abstract class BaseSimpleActivity : AppCompatActivity() { } override fun onActivityResult( - requestCode: Int, - resultCode: Int, - resultData: Intent?, -) { - super.onActivityResult(requestCode, resultCode, resultData) - val partition = try { - checkedDocumentPath.substring(9, 18) - } catch (e: Exception) { - "" - } val sdOtgPattern = Pattern.compile(SD_OTG_SHORT) @@ -358,8 +334,12 @@ abstract class BaseSimpleActivity : AppCompatActivity() { try { startActivityForResult(this, requestCode) - } catch (e: Exception) { - showErrorToast(e) + } 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}") } } } @@ -460,82 +440,6 @@ abstract class BaseSimpleActivity : AppCompatActivity() { } @RequiresApi(Build.VERSION_CODES.O) - fun launchCustomizeNotificationsIntent() { - Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS).apply { - putExtra(Settings.EXTRA_APP_PACKAGE, packageName) - startActivity(this) - } - } - - // synchronous return value determines only if we are showing the SAF dialog, callback result tells if the SD or OTG permission has been granted - fun handleSAFDialog( - path: String, - callback: (success: Boolean) -> Unit, - ): Boolean { - hideKeyboard() - return if (!packageName.startsWith("be.scri")) { - callback(true) - false - } else if (isShowingSAFDialog(path) || isShowingOTGDialog(path)) { - funAfterSAFPermission = callback - true - } else { - callback(true) - false - } - } - - fun handleSAFDialogSdk30( - path: String, - callback: (success: Boolean) -> Unit, - ): Boolean { - hideKeyboard() - return if (!packageName.startsWith("be.scri")) { - callback(true) - false - } else if (isShowingSAFDialogSdk30(path)) { - funAfterSdk30Action = callback - true - } else { - callback(true) - false - } - } - - fun handleSAFCreateDocumentDialogSdk30( - path: String, - callback: (success: Boolean) -> Unit, - ): Boolean { - hideKeyboard() - return if (!packageName.startsWith("be.scri")) { - callback(true) - false - } else if (isShowingSAFCreateDocumentDialogSdk30(path)) { - funAfterSdk30Action = callback - true - } else { - callback(true) - false - } - } - - fun handleAndroidSAFDialog( - path: String, - callback: (success: Boolean) -> Unit, - ): Boolean { - hideKeyboard() - return if (!packageName.startsWith("be.scri")) { - callback(true) - false - } else if (isShowingAndroidSAFDialog(path)) { - funAfterSAFPermission = callback - true - } else { - callback(true) - false - } - } - fun handleOTGPermission(callback: (success: Boolean) -> Unit) { hideKeyboard() if (baseConfig.otgTreeUri.isNotEmpty()) { @@ -544,24 +448,6 @@ abstract class BaseSimpleActivity : AppCompatActivity() { } funAfterSAFPermission = callback - WritePermissionDialog(this, Mode.Otg) { - Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).apply { - try { - startActivityForResult(this, OPEN_DOCUMENT_TREE_OTG) - return@apply - } catch (e: Exception) { - type = "*/*" - } - - try { - startActivityForResult(this, OPEN_DOCUMENT_TREE_OTG) - } catch (e: ActivityNotFoundException) { - toast(R.string.system_service_disabled, Toast.LENGTH_LONG) - } catch (e: Exception) { - toast(R.string.unknown_error_occurred) - } - } - } } @SuppressLint("NewApi") @@ -575,8 +461,10 @@ abstract class BaseSimpleActivity : AppCompatActivity() { try { val deleteRequest = MediaStore.createDeleteRequest(contentResolver, uris).intentSender startIntentSenderForResult(deleteRequest, Companion.DELETE_FILE_SDK_30_HANDLER, null, 0, 0, 0) - } catch (e: Exception) { - showErrorToast(e) + } catch (e: SecurityException) { + showErrorToast("Security exception: ${e.message}") + } catch (e: IllegalArgumentException) { + showErrorToast("Illegal argument: ${e.message}") } } else { callback(false) @@ -594,8 +482,10 @@ abstract class BaseSimpleActivity : AppCompatActivity() { try { val writeRequest = MediaStore.createWriteRequest(contentResolver, uris).intentSender startIntentSenderForResult(writeRequest, Companion.UPDATE_FILE_SDK_30_HANDLER, null, 0, 0, 0) - } catch (e: Exception) { - showErrorToast(e) + } catch (e: SecurityException) { + showErrorToast("Security exception: ${e.message}") + } catch (e: IllegalArgumentException) { + showErrorToast("Illegal argument: ${e.message}") } } else { callback(false) @@ -618,20 +508,6 @@ abstract class BaseSimpleActivity : AppCompatActivity() { } } - fun handlePermission( - permissionId: Int, - callback: (granted: Boolean) -> Unit, - ) { - actionOnPermission = null - if (hasPermission(permissionId)) { - callback(true) - } else { - isAskingPermissions = true - actionOnPermission = callback - ActivityCompat.requestPermissions(this, arrayOf(getPermissionString(permissionId)), Companion.GENERIC_PERM_HANDLER) - } - } - override fun onRequestPermissionsResult( requestCode: Int, permissions: Array, @@ -644,13 +520,6 @@ abstract class BaseSimpleActivity : AppCompatActivity() { } } - fun checkAppOnSDCard() { - if (!baseConfig.wasAppOnSDShown && isAppInstalledOnSDCard()) { - baseConfig.wasAppOnSDShown = true - ConfirmationDialog(this, "", R.string.app_on_sd_card, R.string.ok, 0) {} - } - } - @SuppressLint("InlinedApi") protected fun launchSetDefaultDialerIntent() { if (isQPlus()) { @@ -665,8 +534,6 @@ abstract class BaseSimpleActivity : AppCompatActivity() { startActivityForResult(this, REQUEST_CODE_SET_DEFAULT_DIALER) } catch (e: ActivityNotFoundException) { toast(R.string.no_app_found) - } catch (e: Exception) { - showErrorToast(e) } } } diff --git a/app/src/main/java/be/scri/activities/BaseSplashActivity.kt b/app/src/main/java/be/scri/activities/BaseSplashActivity.kt index f6ac64a7..6177d6aa 100644 --- a/app/src/main/java/be/scri/activities/BaseSplashActivity.kt +++ b/app/src/main/java/be/scri/activities/BaseSplashActivity.kt @@ -5,14 +5,9 @@ import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import be.scri.R import be.scri.extensions.baseConfig -import be.scri.extensions.checkAppIconColor -import be.scri.extensions.checkAppSideloading import be.scri.extensions.getSharedTheme import be.scri.extensions.isThankYouInstalled import be.scri.extensions.isUsingSystemDarkTheme -import be.scri.extensions.showSideloadingDialog -import be.scri.helpers.SIDELOADING_TRUE -import be.scri.helpers.SIDELOADING_UNCHECKED abstract class BaseSplashActivity : AppCompatActivity() { abstract fun initActivity() @@ -20,15 +15,6 @@ abstract class BaseSplashActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - if (baseConfig.appSideloadingStatus == SIDELOADING_UNCHECKED) { - if (checkAppSideloading()) { - return - } - } else if (baseConfig.appSideloadingStatus == SIDELOADING_TRUE) { - showSideloadingDialog() - return - } - baseConfig.apply { if (isUsingAutoTheme) { val isUsingSystemDarkTheme = isUsingSystemDarkTheme() @@ -53,11 +39,6 @@ abstract class BaseSplashActivity : AppCompatActivity() { navigationBarColor = it.navigationBarColor accentColor = it.accentColor } - - if (baseConfig.appIconColor != it.appIconColor) { - baseConfig.appIconColor = it.appIconColor - checkAppIconColor() - } } initActivity() } diff --git a/app/src/main/java/be/scri/activities/MainActivity.kt b/app/src/main/java/be/scri/activities/MainActivity.kt index 172933d8..c76df508 100644 --- a/app/src/main/java/be/scri/activities/MainActivity.kt +++ b/app/src/main/java/be/scri/activities/MainActivity.kt @@ -3,6 +3,7 @@ package be.scri.activities import android.app.UiModeManager import android.content.Context import android.os.Bundle +import android.util.Log import android.view.View import android.view.ViewGroup import android.widget.Button @@ -183,4 +184,38 @@ class MainActivity : SimpleActivity() { val uiModeManager = context.getSystemService(UI_MODE_SERVICE) as UiModeManager return uiModeManager.nightMode == UiModeManager.MODE_NIGHT_YES } + + fun showHint( + sharedPrefsKey: String, + hintMessageResId: Int, + ) { + val sharedPref = getSharedPreferences("app_preferences", Context.MODE_PRIVATE) + val allEntries = sharedPref.all + for ((key, value) in allEntries) { + Log.i("hint", "$key = $value") + } + val isHintShown = sharedPref.getBoolean(sharedPrefsKey, false) + Log.i("hint", isHintShown.toString()) + if (!isHintShown) { + val hintLayout = findViewById(R.id.hint_layout) + val hintText = findViewById(R.id.hint_text) + hintText.text = getString(hintMessageResId) + + hintLayout.visibility = View.VISIBLE + + val okButton = findViewById