Merge pull request #24 from hmalik144/permissions_dispatcher

Permissions dispatcher
This commit is contained in:
2023-08-31 09:48:30 +01:00
committed by GitHub
8 changed files with 95 additions and 170 deletions

View File

@@ -1,19 +1,18 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
apply plugin: 'org.jetbrains.kotlin.android' apply plugin: 'org.jetbrains.kotlin.android'
apply plugin: 'kotlin-kapt'
def relStorePassword = System.getenv("RELEASE_STORE_PASSWORD") def relStorePassword = System.getenv("RELEASE_STORE_PASSWORD")
def relKeyPassword = System.getenv("RELEASE_KEY_PASSWORD") def relKeyPassword = System.getenv("RELEASE_KEY_PASSWORD")
def relKeyAlias = System.getenv("RELEASE_KEY_ALIAS") def relKeyAlias = System.getenv("RELEASE_KEY_ALIAS")
def keystorePath = "/keystore.jks" def keystorePath = System.getenv('PWD') + "/app/keystore.jks"
def keystore = file(keystorePath).exists() ? file(keystorePath) : null def keystore = file(keystorePath).exists() ? file(keystorePath) : null
android { android {
compileSdkVersion 31 compileSdkVersion COMPILE_SDK_VERSION
defaultConfig { defaultConfig {
applicationId "com.appttude.h_mal.farmr" applicationId "com.appttude.h_mal.farmr"
minSdkVersion 21 minSdkVersion MIN_SDK_VERSION
targetSdkVersion 31 targetSdkVersion TARGET_SDK_VERSION
versionCode 2 versionCode 2
versionName "2.0" versionName "2.0"
testInstrumentationRunner 'com.appttude.h_mal.farmr.application.TestRunner' testInstrumentationRunner 'com.appttude.h_mal.farmr.application.TestRunner'
@@ -38,58 +37,51 @@ android {
} }
dependencies { dependencies {
implementation 'androidx.legacy:legacy-support-v4:1.0.0' implementation "androidx.core:core-ktx:$CORE_KTX_VERSION"
implementation 'androidx.vectordrawable:vectordrawable:1.0.0' implementation "androidx.appcompat:appcompat:$MATERIAL_VERSION"
implementation 'androidx.core:core-ktx:1.1.0' implementation "com.google.android.material:material:$MATERIAL_VERSION"
testImplementation 'junit:junit:4.12' implementation "androidx.constraintlayout:constraintlayout:$CONSTR_LAYOUT_VERSION"
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0' implementation "androidx.preference:preference:$PREFERENCES_VERSION"
implementation 'androidx.appcompat:appcompat:1.0.0' implementation "org.jetbrains.kotlin:kotlin-stdlib:$KOTLIN_VERSION"
implementation 'com.google.android.material:material:1.0.0' implementation "androidx.core:core:$CORE_VERSION"
implementation 'androidx.constraintlayout:constraintlayout:1.1.3' implementation "androidx.fragment:fragment:$FRAGMENT_VERSION"
implementation 'androidx.fragment:fragment-ktx:1.4.0' implementation "androidx.lifecycle:lifecycle-common:$LIFECYCLE_VERSION"
implementation 'androidx.activity:activity-ktx:1.4.0' implementation "androidx.lifecycle:lifecycle-livedata-core:$LIFECYCLE_VERSION"
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1' implementation "androidx.lifecycle:lifecycle-viewmodel:$LIFECYCLE_VERSION"
implementation 'androidx.preference:preference:1.2.1' implementation 'androidx.recyclerview:recyclerview:1.0.0'
implementation 'com.ajts.androidmads.SQLite2Excel:library:1.0.2'
/ * Unit testing * / / * Unit testing * /
testImplementation 'junit:junit:4.13.2' testImplementation "junit:junit:$JUNIT_VERSION"
androidTestImplementation "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version" androidTestRuntimeOnly "org.jetbrains.kotlin:kotlin-test-junit:$KOTLIN_VERSION"
testImplementation "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version" testRuntimeOnly "org.jetbrains.kotlin:kotlin-test-junit:$KOTLIN_VERSION"
implementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version" testImplementation "org.jetbrains.kotlin:kotlin-test:$KOTLIN_VERSION"
androidTestImplementation 'androidx.test:core-ktx:1.4.0' androidTestImplementation "androidx.test:rules:$TEST_KTX_VERSION"
androidTestImplementation 'androidx.test:rules:1.4.0' androidTestImplementation 'androidx.test:core:1.5.0'
androidTestImplementation 'androidx.test:monitor:1.6.1'
androidTestImplementation 'junit:junit:4.13.2'
androidTestImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.1'
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.1'
/ * mockito and livedata testing * / / * mockito and livedata testing * /
testImplementation 'org.mockito:mockito-inline:2.13.0' testImplementation "org.mockito:mockito-inline:$MOKITO_INLINE_VERSION"
testImplementation 'androidx.arch.core:core-testing:2.1.0' testImplementation "androidx.arch.core:core-testing:$CORE_TEST_VERSION"
testImplementation 'org.mockito:mockito-core:2.19.0'
/ * MockK * / / * MockK * /
def mockk_ver = "1.10.5" testImplementation "io.mockk:mockk:$MOCKK_VERSION"
testImplementation "io.mockk:mockk:$mockk_ver" androidTestImplementation "io.mockk:mockk-android:$MOCKK_VERSION"
androidTestImplementation "io.mockk:mockk-android:$mockk_ver" testImplementation "io.mockk:mockk-dsl-jvm:$MOCKK_VERSION"
/ * Android Espresso * / / * Android Espresso * /
def testJunitVersion = "1.1.5" androidTestImplementation "androidx.test.espresso:espresso-core:$ESPRESSO_VERSION"
def testRunnerVersion = "1.5.2" androidTestImplementation "androidx.test.espresso:espresso-idling-resource:$ESPRESSO_VERSION"
def espressoVersion = "3.5.1" androidTestImplementation "androidx.test:runner:$TEST_RUNNER_VERSION"
androidTestImplementation "androidx.test.ext:junit:$testJunitVersion" androidTestImplementation "androidx.test.espresso:espresso-contrib:$ESPRESSO_VERSION"
androidTestImplementation "androidx.test.espresso:espresso-core:$espressoVersion" androidTestImplementation "org.hamcrest:hamcrest:$HAMCREST_VERSION"
androidTestImplementation "androidx.test.espresso.idling:idling-concurrent:$espressoVersion"
implementation "androidx.test.espresso:espresso-idling-resource:$espressoVersion"
androidTestImplementation "androidx.test:runner:$testRunnerVersion"
androidTestImplementation "androidx.test.espresso:espresso-contrib:$espressoVersion"
androidTestImplementation "androidx.test.espresso:espresso-intents:$espressoVersion"
androidTestImplementation "org.hamcrest:hamcrest:2.2"
/ * Room database * / / * Room database * /
def room_version = "2.4.3" runtimeOnly "androidx.room:room-runtime:$ROOM_VERSION"
implementation "androidx.room:room-runtime:$room_version" implementation "androidx.room:room-ktx:$ROOM_VERSION"
kapt "androidx.room:room-compiler:$room_version"
implementation "androidx.room:room-ktx:$room_version"
/ *Kodein Dependency Injection * / / *Kodein Dependency Injection * /
def kodein_version = "6.2.1" implementation "org.kodein.di:kodein-di-generic-jvm:$KODEIN_VERSION"
implementation "org.kodein.di:kodein-di-generic-jvm:$kodein_version" implementation "org.kodein.di:kodein-di-framework-android-x:$KODEIN_VERSION"
implementation "org.kodein.di:kodein-di-framework-android-x:$kodein_version" implementation "org.kodein.di:kodein-di-core-jvm:$KODEIN_VERSION"
implementation "org.kodein.di:kodein-di-framework-android-core:$KODEIN_VERSION"
/ * jxl * / / * jxl * /
implementation 'net.sourceforge.jexcelapi:jxl:2.6.12' implementation "net.sourceforge.jexcelapi:jxl:$JEXCEL_VERSION"
/ * Permissions dispatcher * /
def dispatcher_ver = "4.9.2"
implementation "com.github.permissions-dispatcher:permissionsdispatcher:${dispatcher_ver}"
kapt "com.github.permissions-dispatcher:permissionsdispatcher-processor:${dispatcher_ver}"
} }

View File

@@ -14,7 +14,6 @@ import androidx.test.espresso.assertion.ViewAssertions
import androidx.test.espresso.matcher.RootMatchers.withDecorView import androidx.test.espresso.matcher.RootMatchers.withDecorView
import androidx.test.espresso.matcher.ViewMatchers import androidx.test.espresso.matcher.ViewMatchers
import androidx.test.platform.app.InstrumentationRegistry import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.rule.GrantPermissionRule
import com.appttude.h_mal.farmr.application.TestAppClass import com.appttude.h_mal.farmr.application.TestAppClass
import com.appttude.h_mal.farmr.ui.utils.getShifts import com.appttude.h_mal.farmr.ui.utils.getShifts
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
@@ -36,9 +35,6 @@ open class BaseTest<A : Activity>(
private lateinit var testActivity: Activity private lateinit var testActivity: Activity
private lateinit var decorView: View private lateinit var decorView: View
@get:Rule
var permissionRule = GrantPermissionRule.grant(Manifest.permission.READ_EXTERNAL_STORAGE)
@Before @Before
open fun setUp() { open fun setUp() {
val startIntent = val startIntent =

View File

@@ -3,9 +3,6 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
package="com.appttude.h_mal.farmr"> package="com.appttude.h_mal.farmr">
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application <application
android:name=".di.ShiftApplication" android:name=".di.ShiftApplication"
android:allowBackup="true" android:allowBackup="true"

View File

@@ -1,15 +1,10 @@
package com.appttude.h_mal.farmr.ui package com.appttude.h_mal.farmr.ui
import android.Manifest
import android.app.Activity
import android.app.AlertDialog import android.app.AlertDialog
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager
import android.os.Bundle import android.os.Bundle
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.widget.Toast
import androidx.core.app.ActivityCompat
import androidx.core.content.FileProvider import androidx.core.content.FileProvider
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver
@@ -51,7 +46,7 @@ class FragmentMain : BaseFragment<MainViewModel>(R.layout.fragment_main), BackPr
} }
productListView = view.findViewById(R.id.list_item_view) productListView = view.findViewById(R.id.list_item_view)
productListView.adapter = mAdapter productListView.adapter = mAdapter
emptyView = view.findViewById(R.id.empty_view) emptyView = view.findViewById(R.id.empty_view)
mAdapter.registerAdapterDataObserver(object : AdapterDataObserver() { mAdapter.registerAdapterDataObserver(object : AdapterDataObserver() {
override fun onChanged() { override fun onChanged() {
@@ -114,16 +109,12 @@ class FragmentMain : BaseFragment<MainViewModel>(R.layout.fragment_main), BackPr
} }
R.id.export_data -> { R.id.export_data -> {
if (checkStoragePermissions(activity)) { AlertDialog.Builder(context)
AlertDialog.Builder(context) .setTitle("Export?")
.setTitle("Export?") .setMessage("Exporting current filtered data. Continue?")
.setMessage("Exporting current filtered data. Continue?") .setNegativeButton(android.R.string.cancel, null)
.setNegativeButton(android.R.string.cancel, null) .setPositiveButton(android.R.string.ok) { _, _ -> exportData() }
.setPositiveButton(android.R.string.ok) { _, _ -> exportData() } .create().show()
.create().show()
} else {
displayToast("Storage permissions required")
}
return true return true
} }
@@ -175,13 +166,6 @@ class FragmentMain : BaseFragment<MainViewModel>(R.layout.fragment_main), BackPr
} }
private fun exportData() { private fun exportData() {
val permission =
ActivityCompat.checkSelfPermission(requireActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE)
if (permission != PackageManager.PERMISSION_GRANTED) {
Toast.makeText(context, "Storage permissions not granted", Toast.LENGTH_SHORT).show()
return
}
val fileName = "shifthistory.xls" val fileName = "shifthistory.xls"
val file = File(requireContext().externalCacheDir, fileName) val file = File(requireContext().externalCacheDir, fileName)
@@ -199,24 +183,6 @@ class FragmentMain : BaseFragment<MainViewModel>(R.layout.fragment_main), BackPr
} }
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
println("request code$requestCode")
if (requestCode == MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE) {
if (grantResults.isNotEmpty()
&& grantResults[0] == PackageManager.PERMISSION_GRANTED
) {
exportDialog()
} else {
displayToast("Storage Permissions denied")
}
}
}
private fun exportDialog() { private fun exportDialog() {
AlertDialog.Builder(context) AlertDialog.Builder(context)
.setTitle("Export?") .setTitle("Export?")
@@ -225,22 +191,6 @@ class FragmentMain : BaseFragment<MainViewModel>(R.layout.fragment_main), BackPr
.setPositiveButton(android.R.string.ok) { _, _ -> exportData() }.create().show() .setPositiveButton(android.R.string.ok) { _, _ -> exportData() }.create().show()
} }
private fun checkStoragePermissions(activity: Activity?): Boolean {
var status = false
val permission = ActivityCompat.checkSelfPermission(
activity!!,
Manifest.permission.WRITE_EXTERNAL_STORAGE
)
if (permission == PackageManager.PERMISSION_GRANTED) {
status = true
}
return status
}
companion object {
const val MY_PERMISSIONS_REQUEST_READ_EXTERNAL_STORAGE = 1
}
override fun onBackPressed(): Boolean { override fun onBackPressed(): Boolean {
requireContext().createDialog( requireContext().createDialog(
title = "Leave?", title = "Leave?",

View File

@@ -21,8 +21,6 @@ class MainActivity : BaseActivity() {
toolbar = findViewById(R.id.toolbar) toolbar = findViewById(R.id.toolbar)
setSupportActionBar(toolbar) setSupportActionBar(toolbar)
verifyStoragePermissions(this)
val fragmentTransaction = supportFragmentManager.beginTransaction() val fragmentTransaction = supportFragmentManager.beginTransaction()
fragmentTransaction.replace(R.id.container, FragmentMain()).addToBackStack("main").commit() fragmentTransaction.replace(R.id.container, FragmentMain()).addToBackStack("main").commit()
} }
@@ -45,34 +43,4 @@ class MainActivity : BaseActivity() {
} }
} }
} }
// Storage Permissions
private val REQUEST_EXTERNAL_STORAGE = 1
private val PERMISSIONS_STORAGE = arrayOf(
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
)
/**
* Checks if the app has permission to write to device storage
*
* If the app does not has permission then the user will be prompted to grant permissions
*
* @param activity
*/
fun verifyStoragePermissions(activity: Activity?) {
// Check if we have write permission
val permission = ActivityCompat.checkSelfPermission(
activity!!,
Manifest.permission.WRITE_EXTERNAL_STORAGE
)
if (permission != PackageManager.PERMISSION_GRANTED) {
// We don't have permission so prompt the user
ActivityCompat.requestPermissions(
activity,
PERMISSIONS_STORAGE,
REQUEST_EXTERNAL_STORAGE
)
}
}
} }

View File

@@ -1,7 +1,5 @@
package com.appttude.h_mal.farmr.viewmodel package com.appttude.h_mal.farmr.viewmodel
import android.Manifest.permission.WRITE_EXTERNAL_STORAGE
import androidx.annotation.RequiresPermission
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
@@ -206,7 +204,6 @@ class MainViewModel(
refreshLiveData() refreshLiveData()
} }
@RequiresPermission(WRITE_EXTERNAL_STORAGE)
fun createExcelSheet(file: File): File? { fun createExcelSheet(file: File): File? {
val wbSettings = WorkbookSettings().apply { val wbSettings = WorkbookSettings().apply {
locale = Locale("en", "EN") locale = Locale("en", "EN")

View File

@@ -2,16 +2,20 @@
buildscript { buildscript {
ext { ext {
kotlin_version = '1.7.10' kotlin_version = KOTLIN_VERSION
} }
repositories { repositories {
google() google()
jcenter() jcenter()
// releases
mavenCentral()
// snapshots
maven { url "https://oss.sonatype.org/content/repositories/snapshots/" }
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:7.4.1' classpath "com.android.tools.build:gradle:$GRADLE_PLUGIN_VERSION"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$KOTLIN_VERSION"
classpath "com.autonomousapps:dependency-analysis-gradle-plugin:$GRADLE_ANALYZE_VERSION"
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files // in the individual module build.gradle files
} }
@@ -25,6 +29,8 @@ allprojects {
} }
} }
apply plugin: "com.autonomousapps.dependency-analysis"
task clean(type: Delete) { task clean(type: Delete) {
delete rootProject.buildDir delete rootProject.buildDir
} }

View File

@@ -1,19 +1,38 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users: # Plugin versions
# Gradle settings configured through the IDE *will override* LEGACY_SUPPORT_VERSION = 1.0.0
# any settings specified in this file. CORE_KTX_VERSION = 1.1.0
CORE_VERSION = 1.8.0
FRAGMENT_VERSION = 1.4.0
MATERIAL_VERSION = 1.0.0
CONSTR_LAYOUT_VERSION = 1.1.3
LIFECYCLE_VERSION = 2.5.1
VIEWMODEL_VERSION = 2.4.1
PREFERENCES_VERSION = 1.2.1
MOKITO_INLINE_VERSION = 2.13.0
CORE_TEST_VERSION = 2.1.0
MOCKK_VERSION = 1.10.5
TEST_JUNIT_VERSION = 1.1.5
TEST_RUNNER_VERSION = 1.5.2
ESPRESSO_VERSION = 3.5.1
HAMCREST_VERSION = 2.2
JEXCEL_VERSION = 2.6.12
JUNIT_VERSION = 4.13.2
KODEIN_VERSION = 6.2.1
ROOM_VERSION = 2.4.3
TEST_KTX_VERSION = 1.4.0
GRADLE_PLUGIN_VERSION = 7.4.1
KOTLIN_VERSION = 1.7.10
GRADLE_ANALYZE_VERSION = 1.20.0
# For more details on how to configure your build environment visit # Android configuration
# http://www.gradle.org/docs/current/userguide/build_environment.html COMPILE_SDK_VERSION = android-31
TARGET_SDK_VERSION = 31
MIN_SDK_VERSION = 21
# Specifies the JVM arguments used for the daemon process. # Gradle parameters
# The setting is particularly useful for tweaking memory settings. org.gradle.jvmargs = -Xmx1536m
android.enableJetifier=true
android.useAndroidX=true
org.gradle.jvmargs=-Xmx1536m
# When configured, Gradle will run in incubating parallel mode. # AndroidX
# This option should only be used with decoupled projects. More details, visit android.useAndroidX = true
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects android.enableJetifier = true
# org.gradle.parallel=true