Skip to content

Commit

Permalink
Merge pull request #11 from f-lab-edu/feature/9
Browse files Browse the repository at this point in the history
#9 - Permission 작업
  • Loading branch information
kimny927 authored Dec 4, 2024
2 parents 39eb8f3 + 879f0e4 commit ad3f12c
Show file tree
Hide file tree
Showing 10 changed files with 242 additions and 5 deletions.
1 change: 0 additions & 1 deletion .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ dependencies {
implementation(libs.maps.compose)
implementation(libs.maps.compose.utils)
implementation(libs.maps.compose.widgets)
implementation(libs.accompanist.permissions)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
Expand Down
6 changes: 6 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<!-- targetSDK 33 이상 -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />

<!-- targetSDK 34 이상 -->
<uses-permission android:name="android.permission.READ_MEDIA_VISUAL_USER_SELECTED" />

<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
Expand Down
4 changes: 4 additions & 0 deletions app/src/main/java/ny/photomap/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import androidx.compose.ui.Modifier
import ny.photomap.ui.theme.PhotoMapTheme

class MainActivity : ComponentActivity() {


override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
Expand All @@ -22,4 +24,6 @@ class MainActivity : ComponentActivity() {
}
}
}


}
54 changes: 51 additions & 3 deletions app/src/main/java/ny/photomap/MapScreen.kt
Original file line number Diff line number Diff line change
@@ -1,13 +1,61 @@
package ny.photomap

import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.rememberMultiplePermissionsState
import com.google.maps.android.compose.GoogleMap
import ny.photomap.permission.ReadImagePermission

@OptIn(ExperimentalPermissionsApi::class)
@Composable
fun MapScreen(modifier: Modifier) {
GoogleMap(
modifier = modifier.fillMaxSize(),
)
Scaffold(modifier = modifier.fillMaxSize()) { padding ->

val permission = ReadImagePermission
val permissionState = rememberMultiplePermissionsState(permission.permission)
var visualUserSelectedGranted by remember { mutableStateOf(false) }
var onDenied by remember { mutableStateOf(false) }
PermissionRequester(
permission = permission,
permissionsState = permissionState,
onAllGranted = {
// todo 이미지 파일 조회
},
onVisualUserSelectedGranted = {
visualUserSelectedGranted = true
},
onDenied = { _ ->
onDenied = true
})

Column(modifier = Modifier.fillMaxSize()) {
if (visualUserSelectedGranted || onDenied) {
val title: String = stringResource(permission.titleRes)
val description: String = if (visualUserSelectedGranted) {
stringResource(permission.visualUserSelectedDescriptionRes)
} else {
stringResource(permission.needDescriptionRes)
}

//todo UI 작업
}
GoogleMap(
modifier = modifier
.padding(padding)
.fillMaxSize(),
)
}

}

}
68 changes: 68 additions & 0 deletions app/src/main/java/ny/photomap/PermissionRequester.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package ny.photomap

import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.MultiplePermissionsState
import com.google.accompanist.permissions.rememberMultiplePermissionsState
import ny.photomap.permission.Permission
import ny.photomap.permission.PermissionStateCheckerImpl
import ny.photomap.permission.PermissionVisualUserSelected
import ny.photomap.permission.PermissionVisualUserSelectedStateCheckerImpl


@OptIn(ExperimentalPermissionsApi::class)
@Composable
fun PermissionRequester(
permission: Permission,
permissionsState: MultiplePermissionsState = rememberMultiplePermissionsState(permission.permission),
onAllGranted: () -> Unit,
onVisualUserSelectedGranted: (() -> Unit)? = null,
onDenied: (shouldShowRationale: Boolean) -> Unit,
) {
if (permission.permission.isEmpty()) return
var permissionRequested: Boolean by remember { mutableStateOf(false) }

if (permission is PermissionVisualUserSelected) {
val permissionChecker = PermissionVisualUserSelectedStateCheckerImpl(permission)
when {
permissionChecker.isGranted(permissionsState.permissions) -> onAllGranted()
else -> when {
permissionChecker.isVisualUserSelectedGranted(permissionsState.permissions)
-> onVisualUserSelectedGranted?.invoke()

permissionChecker.isDenied(permissionsState.permissions)
-> if (permissionRequested) {
onDenied(permissionChecker.shouldShowRationale(permissionsState.permissions))
} else {
LaunchedEffect(Unit) {
permissionsState.launchMultiplePermissionRequest()
permissionRequested = !permissionRequested
}
}

}
}
} else {
val permissionChecker = PermissionStateCheckerImpl(permission)
when {
permissionChecker.isGranted(permissionsState.permissions)
-> onAllGranted()

permissionChecker.isDenied(permissionsState.permissions)
-> if (!permissionRequested) {
LaunchedEffect(Unit) {
permissionsState.launchMultiplePermissionRequest()
}
permissionRequested = !permissionRequested
} else {
onDenied(permissionChecker.shouldShowRationale(permissionsState.permissions))
}

}
}
}
51 changes: 51 additions & 0 deletions app/src/main/java/ny/photomap/permission/Permission.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package ny.photomap.permission

import android.annotation.TargetApi
import android.os.Build
import androidx.annotation.StringRes
import ny.photomap.R

interface Permission {
val permission: List<String>
val titleRes: Int
val needDescriptionRes: Int
}

@TargetApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
interface PermissionVisualUserSelected : Permission {
val visualUserSelectedDescriptionRes: Int

val mainPermission: String
val visualUserSelectedPermission: String
get() = android.Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED
}


object ReadImagePermission : PermissionVisualUserSelected {
override val mainPermission: String =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) android.Manifest.permission.READ_MEDIA_IMAGES
else ""

override val permission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
listOf(
mainPermission,
visualUserSelectedPermission
)
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
listOf(mainPermission)
else listOf()

@StringRes
override val titleRes = R.string.permission_read_image_title

@StringRes
override val needDescriptionRes = R.string.permission_read_image_description

@StringRes
override val visualUserSelectedDescriptionRes: Int =
R.string.permission_read_image_visual_user_selected_description

}



49 changes: 49 additions & 0 deletions app/src/main/java/ny/photomap/permission/PermissionStateChecker.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package ny.photomap.permission

import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.PermissionState
import com.google.accompanist.permissions.isGranted
import com.google.accompanist.permissions.shouldShowRationale


@OptIn(ExperimentalPermissionsApi::class)
interface PermissionStateChecker {
fun isGranted(permissionState: List<PermissionState>): Boolean
fun isDenied(permissionState: List<PermissionState>): Boolean
fun shouldShowRationale(permissionState: List<PermissionState>): Boolean
}

@OptIn(ExperimentalPermissionsApi::class)
interface PermissionVisualUserSelectedStateChecker : PermissionStateChecker {
fun isVisualUserSelectedGranted(permissionState: List<PermissionState>): Boolean
}

@OptIn(ExperimentalPermissionsApi::class)
class PermissionVisualUserSelectedStateCheckerImpl(val permission: PermissionVisualUserSelected) :
PermissionVisualUserSelectedStateChecker {
override fun isVisualUserSelectedGranted(permissionState: List<PermissionState>): Boolean =
permissionState.any { it.permission == permission.mainPermission && it.status.isGranted }

override fun isGranted(permissionState: List<PermissionState>): Boolean =
permissionState.any { it.permission == permission.visualUserSelectedPermission && it.status.isGranted }

override fun isDenied(permissionState: List<PermissionState>): Boolean =
permissionState.all { !it.status.isGranted }

override fun shouldShowRationale(permissionState: List<PermissionState>): Boolean =
permissionState.any { it.permission == permission.mainPermission && it.status.shouldShowRationale }

}

@OptIn(ExperimentalPermissionsApi::class)
class PermissionStateCheckerImpl(val permission: Permission) : PermissionStateChecker {
override fun isGranted(permissionState: List<PermissionState>): Boolean =
permissionState.any { it.permission == permission.permission[0] && it.status.isGranted }

override fun isDenied(permissionState: List<PermissionState>): Boolean =
permissionState.all { !it.status.isGranted }

override fun shouldShowRationale(permissionState: List<PermissionState>): Boolean =
permissionState.any { it.permission == permission.permission[0] && it.status.shouldShowRationale }

}
5 changes: 4 additions & 1 deletion app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
<resources>
<string name="app_name">PhotoMap</string>
<string name="title_activity_main2">MainActivity2</string>
<string name="title_activity_main">MainActivity</string>
<string name="permission_read_image_title">이미지 파일 엑세스</string>
<string name="permission_read_image_description">서비스를 이용하기 위해서 이미지 파일 엑세스 권한이 필요합니다.</string>
<string name="permission_read_image_visual_user_selected_description">선택한 일부 사진에만 엑세스 권한을 부여했습니다.</string>
</resources>
8 changes: 8 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,14 @@ activityCompose = "1.9.3"
composeBom = "2024.11.00"
secretsGradlePlugin = "2.0.1"
mapsCompose = "4.4.1"

accompanish = "0.36.0"

exifinterface = "1.3.7"
mockito-kotlin = "5.4.0"
robolectric = "4.14"


[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
junit = { group = "junit", name = "junit", version.ref = "junit" }
Expand All @@ -41,10 +45,14 @@ secrets-gradle-plugin = { group = "com.google.android.libraries.mapsplatform.sec
maps-compose = { group = "com.google.maps.android", name = "maps-compose", version.ref = "mapsCompose" }
maps-compose-utils = { group = "com.google.maps.android", name = "maps-compose-utils", version.ref = "mapsCompose" }
maps-compose-widgets = { group = "com.google.maps.android", name = "maps-compose-widgets", version.ref = "mapsCompose" }

accompanist-permissions = { group = "com.google.accompanist", name="accompanist-permissions", version.ref="accompanish" }

androidx-exifinterface = { group="androidx.exifinterface", name= "exifinterface", version.ref = "exifinterface" }
mockito-kotlin = { group="org.mockito.kotlin", name="mockito-kotlin", version.ref="mockito-kotlin" }
robolectric = { group="org.robolectric", name="robolectric", version.ref="robolectric" }


[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
Expand Down

0 comments on commit ad3f12c

Please sign in to comment.