Skip to content

Commit

Permalink
Merge pull request #49 from Team-Walkie/compose/permission_settings
Browse files Browse the repository at this point in the history
권한 요청 및 gps, 네트워크 상태 관리 다이얼로그 추가
  • Loading branch information
soopeach authored Oct 29, 2023
2 parents cba1cb9 + eed1801 commit 1a20a36
Show file tree
Hide file tree
Showing 21 changed files with 498 additions and 145 deletions.
10 changes: 8 additions & 2 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ plugins {

android {
namespace = "com.whyranoid.walkie"
compileSdk = 33
compileSdk = 34

defaultConfig {
applicationId = "com.whyranoid.walkie"
minSdk = 26
targetSdk = 33
versionCode = 1
versionName = "1.0.3"
versionName = "1.0.4"

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
Expand Down Expand Up @@ -114,4 +114,10 @@ dependencies {
implementation("com.squareup.retrofit2:converter-gson:2.9.0")
implementation("com.squareup.okhttp3:okhttp:4.10.0")
implementation("com.squareup.okhttp3:logging-interceptor:4.10.0")

// rememberPermissionState
implementation("com.google.accompanist:accompanist-permissions:0.33.2-alpha")

// collectAsStateWithLifecycle
implementation("androidx.lifecycle:lifecycle-runtime-compose:2.6.1")
}
1 change: 1 addition & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.EXPAND_STATUS_BAR" />

<application
android:name=".WalkieApplication"
Expand Down
15 changes: 15 additions & 0 deletions app/src/main/java/com/whyranoid/walkie/KoinModules.kt
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ import com.whyranoid.domain.usecase.GetUserDetailUseCase
import com.whyranoid.domain.usecase.GetUserPostPreviewsUseCase
import com.whyranoid.domain.usecase.SignOutUseCase
import com.whyranoid.domain.usecase.UploadPostUseCase
import com.whyranoid.domain.usecase.broadcast.AddGpsListener
import com.whyranoid.domain.usecase.broadcast.AddNetworkListener
import com.whyranoid.domain.usecase.broadcast.GetGpsState
import com.whyranoid.domain.usecase.broadcast.GetNetworkState
import com.whyranoid.domain.usecase.broadcast.RemoveGpsListener
import com.whyranoid.domain.usecase.broadcast.RemoveNetworkListener
import com.whyranoid.domain.usecase.running.GetRunningFollowerUseCase
import com.whyranoid.domain.usecase.running.RunningFinishUseCase
import com.whyranoid.domain.usecase.running.RunningStartUseCase
Expand All @@ -65,6 +71,7 @@ import com.whyranoid.presentation.viewmodel.UserPageViewModel
import com.whyranoid.presentation.viewmodel.challenge.ChallengeDetailViewModel
import com.whyranoid.presentation.viewmodel.challenge.ChallengeExitViewModel
import com.whyranoid.presentation.viewmodel.challenge.ChallengeMainViewModel
import com.whyranoid.walkie.walkiedialog.DialogViewModel
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.Response
Expand All @@ -87,6 +94,7 @@ val viewModelModule = module {
factory { SelectHistoryViewModel(get()) }
factory { EditProfileViewModel(get()) }
factory { AddPostViewModel(get()) }
factory { DialogViewModel(get(), get(), get(), get(), get(), get()) }
}

val repositoryModule = module {
Expand Down Expand Up @@ -125,6 +133,13 @@ val useCaseModule = module {
single { SignOutUseCase(get()) }
single { UploadPostUseCase(get(), get()) }
single { SendLikeUseCase(get(), get()) }

single { AddGpsListener(get()) }
single { AddNetworkListener(get()) }
single { RemoveGpsListener(get()) }
single { RemoveNetworkListener(get()) }
single { GetGpsState(get()) }
single { GetNetworkState(get()) }
}

val databaseModule = module {
Expand Down
6 changes: 5 additions & 1 deletion app/src/main/java/com/whyranoid/walkie/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
package com.whyranoid.walkie

import android.os.Build
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.annotation.RequiresApi
import com.whyranoid.presentation.screens.AppScreen
import com.whyranoid.presentation.theme.WalkieTheme
import com.whyranoid.walkie.walkiedialog.AppManageDialog

class MainActivity : ComponentActivity() {

@RequiresApi(Build.VERSION_CODES.TIRAMISU)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
WalkieTheme {
AppManageDialog()
AppScreen { startWorker(this) }
}
}
Expand Down
151 changes: 151 additions & 0 deletions app/src/main/java/com/whyranoid/walkie/walkiedialog/AppManageDialog.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
package com.whyranoid.walkie.walkiedialog

import android.Manifest
import android.app.Activity
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.AlertDialog
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.isGranted
import com.google.accompanist.permissions.rememberPermissionState
import com.google.accompanist.permissions.shouldShowRationale
import com.whyranoid.presentation.theme.WalkieColor
import com.whyranoid.presentation.theme.WalkieTypography
import com.whyranoid.presentation.util.openSettings
import com.whyranoid.presentation.util.openStatusBar
import org.koin.androidx.compose.koinViewModel

/**
* Provide dialog
*
* 권한, gps, 네트워크 상태에 따른 다이얼로그 보여주기
*
* 분리한 이유: ExperimentalPermissionsApi 로써 언제든 변화 가능하기 때문에 메인 비지니스 로직, UI 로직에 포함시키지 않고 따로 분리
*
*/

@RequiresApi(Build.VERSION_CODES.TIRAMISU)
@OptIn(ExperimentalPermissionsApi::class)
@Composable
fun AppManageDialog() {
val viewModel = koinViewModel<DialogViewModel>()
val activity = LocalContext.current as Activity

val locationPermissionState = rememberPermissionState(
Manifest.permission.ACCESS_FINE_LOCATION,
) { isGranted ->
viewModel.setPermission(Manifest.permission.ACCESS_FINE_LOCATION, isGranted)
}

val storagePermissionState = rememberPermissionState(
Manifest.permission.READ_EXTERNAL_STORAGE,
) { isGranted ->
viewModel.setPermission(Manifest.permission.READ_EXTERNAL_STORAGE, isGranted)
}

val locationDialogState = viewModel.locationPermissionDialogState.collectAsStateWithLifecycle()
val storageDialogState = viewModel.storagePermissionDialogState.collectAsStateWithLifecycle()
val gpsDialogState =
viewModel.gpsDialogState.collectAsStateWithLifecycle(initialValue = DialogState.Initialized)
val networkDialogState =
viewModel.networkDialogState.collectAsStateWithLifecycle(initialValue = DialogState.Initialized)

LaunchedEffect(
locationDialogState.value,
storageDialogState.value,
gpsDialogState.value,
networkDialogState.value,
) {
if (locationPermissionState.status.isGranted.not() && locationPermissionState.status.shouldShowRationale.not()) {
locationPermissionState.launchPermissionRequest()
} else if (storagePermissionState.status.isGranted.not() && storagePermissionState.status.shouldShowRationale.not()) {
storagePermissionState.launchPermissionRequest()
}
}

if (locationPermissionState.status.isGranted.not() && locationPermissionState.status.shouldShowRationale) {
PermissionDialog(
dialog = DialogContentProvider.LocationPermission,
onAction = { activity.openSettings() },
modifier = Modifier.clip(RoundedCornerShape(20.dp)),
)
} else if (storagePermissionState.status.isGranted.not() && storagePermissionState.status.shouldShowRationale) {
PermissionDialog(
dialog = DialogContentProvider.StoragePermission,
onAction = { activity.openSettings() },
modifier = Modifier.clip(RoundedCornerShape(20.dp)),
)
} else if (gpsDialogState.value is DialogState.InValid) {
PermissionDialog(
dialog = DialogContentProvider.GPS,
onAction = {
activity.openStatusBar()
},
modifier = Modifier.clip(RoundedCornerShape(20.dp)),
)
} else if (networkDialogState.value is DialogState.InValid) {
PermissionDialog(
dialog = DialogContentProvider.Network,
onAction = {
activity.openStatusBar()
},
modifier = Modifier.clip(RoundedCornerShape(20.dp)),
)
}
}

@Composable
fun PermissionDialog(
dialog: DialogContentProvider,
onAction: () -> Unit,
modifier: Modifier = Modifier,
) {
AlertDialog(
onDismissRequest = onAction,
buttons = {
Column(modifier = Modifier.fillMaxWidth()) {
Text(
text = "동의",
style = WalkieTypography.SubTitle.copy(color = WalkieColor.Primary),
textAlign = TextAlign.Center,
modifier = Modifier
.align(Alignment.End)
.clickable(
indication = null,
interactionSource = remember { MutableInteractionSource() },
) {
onAction()
}
.padding(bottom = 20.dp)
.padding(horizontal = 20.dp)
.clip(RoundedCornerShape(12.dp)),
)
}
},
title = { Text(dialog.title, style = WalkieTypography.SubTitle) },
text = {
Text(
text = dialog.description,
style = WalkieTypography.Body1_Normal,
)
},
modifier = modifier,
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.whyranoid.walkie.walkiedialog

sealed class DialogContentProvider(
val title: String,
val description: String,
) {
object LocationPermission :
DialogContentProvider(
"위치 정보 제공 동의",
"러닝 기능을 사용하려면 사용자의 위치 권한 동의가 반드시 필요해요.",
) {
const val PERMISSION = android.Manifest.permission.ACCESS_FINE_LOCATION
}

object StoragePermission : DialogContentProvider(
"미디어 및 파일 접근 동의",
"러닝 정보를 기록하려면 미디어 및 파일 접근 권한 동의가 반드시 필요해요.",
) {
const val PERMISSION = android.Manifest.permission.READ_EXTERNAL_STORAGE
}

object GPS : DialogContentProvider(
"위치 상태 확인 요망",
"러닝 기능을 사용하려면 위치 정보 상태가 켜져야해요.",
)

object Network : DialogContentProvider(
"네트워크 상태 확인 요망",
"러닝 기능을 사용하려면 네트워트가 연결되어야해요.",
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.whyranoid.walkie.walkiedialog

sealed class DialogState {
object Initialized : DialogState()

object Valid : DialogState()

data class InValid(val dialogContentProvider: DialogContentProvider) : DialogState()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.whyranoid.walkie.walkiedialog

import androidx.lifecycle.ViewModel
import com.whyranoid.domain.usecase.broadcast.AddGpsListener
import com.whyranoid.domain.usecase.broadcast.AddNetworkListener
import com.whyranoid.domain.usecase.broadcast.GetGpsState
import com.whyranoid.domain.usecase.broadcast.GetNetworkState
import com.whyranoid.domain.usecase.broadcast.RemoveGpsListener
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.map

class DialogViewModel(
private val addNetworkListener: AddNetworkListener,
private val removeNetworkListener: AddNetworkListener,
private val getNetworkState: GetNetworkState,
private val addGpsListener: AddGpsListener,
private val removeGpsListener: RemoveGpsListener,
private val getGpsState: GetGpsState,
) : ViewModel() {
init {
addNetworkListener()
addGpsListener()
}

private val _locationPermissionDialogState =
MutableStateFlow<DialogState>(DialogState.Initialized)
private val _storagePermissionDialogState =
MutableStateFlow<DialogState>(DialogState.Initialized)

val locationPermissionDialogState get() = _locationPermissionDialogState.asStateFlow()
val storagePermissionDialogState get() = _storagePermissionDialogState.asStateFlow()

val gpsDialogState = getGpsState().map {
if (it) DialogState.Valid else DialogState.InValid(DialogContentProvider.GPS)
}
val networkDialogState = getNetworkState().map {
if (it) DialogState.Valid else DialogState.InValid(DialogContentProvider.Network)
}

fun setPermission(permission: String, showDialog: Boolean) {
if (permission == DialogContentProvider.LocationPermission.PERMISSION) {
_locationPermissionDialogState.value =
if (showDialog) DialogState.Valid else DialogState.InValid(DialogContentProvider.LocationPermission)
} else if (permission == DialogContentProvider.StoragePermission.PERMISSION) {
_storagePermissionDialogState.value =
if (showDialog) DialogState.Valid else DialogState.InValid(DialogContentProvider.StoragePermission)
}
}

override fun onCleared() {
removeGpsListener()
removeNetworkListener()
super.onCleared()
}
}
11 changes: 11 additions & 0 deletions app/src/main/java/com/whyranoid/walkie/wlakiedialog/DialogState.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.whyranoid.walkie.wlakiedialog

import com.whyranoid.walkie.walkiedialog.DialogContentProvider

sealed class DialogState {
object Initialized : DialogState()

object Valid : DialogState()

data class InValid(val dialogContentProvider: DialogContentProvider) : DialogState()
}
4 changes: 2 additions & 2 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
id("com.android.application") version "7.3.1" apply false
id("com.android.library") version "7.3.1" apply false
id("com.android.application") version "7.4.2" apply false
id("com.android.library") version "7.4.2" apply false
id("org.jetbrains.kotlin.android") version "1.8.0" apply false
id("org.jetbrains.kotlin.jvm") version "1.8.0" apply false
id("com.google.gms.google-services") version "4.3.15" apply false
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.whyranoid.domain.usecase.broadcast

import com.whyranoid.domain.repository.GpsRepository

class AddGpsListener(private val gpsRepository: GpsRepository) {
operator fun invoke() {
gpsRepository.registerReceiver()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.whyranoid.domain.usecase.broadcast

import com.whyranoid.domain.repository.NetworkRepository

class AddNetworkListener(private val networkRepository: NetworkRepository) {
operator fun invoke() {
networkRepository.addNetworkConnectionCallback()
}
}
Loading

0 comments on commit 1a20a36

Please sign in to comment.