Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

권한 요청 및 gps, 네트워크 상태 관리 다이얼로그 추가 #49

Merged
merged 16 commits into from
Oct 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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" />
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

얜 뭔가요?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gps, 네트워크가 없을 때 뜨는 다이얼로그에 연결동작으로 상단 상태바 내려와서 바로 설정할 수 있게 해주는데 필요한 권한입니다


<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
Loading