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 14 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.wlakiedialog.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.wlakiedialog.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/wlakiedialog/AppManageDialog.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
package com.whyranoid.walkie.wlakiedialog

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)
Copy link
Member

Choose a reason for hiding this comment

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

어디서 33이상의 버전을 요구하는 것인가요??
버전 별 분리처리 부분이 따로 안보여서 질문드립니다!!
하위 버전과의 호환성에 문제가 없나요?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

넵 우선 저 부분이 api level 33이상 앱 상세 설정을 키는 역할을 하는데 미만 버전에서는 동작하지 않는걸로 알고있습니다..
버전 분기해서 관리해줘야하는데 못했어요

Copy link
Member

Choose a reason for hiding this comment

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

@yonghanJu 아하... 추후에 처리해보죳!!

@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 = DialogProvider.LocationPermission,
onAction = { activity.openSettings() },
modifier = Modifier.clip(RoundedCornerShape(20.dp)),
)
} else if (storagePermissionState.status.isGranted.not() && storagePermissionState.status.shouldShowRationale) {
PermissionDialog(
dialog = DialogProvider.StoragePermission,
onAction = { activity.openSettings() },
modifier = Modifier.clip(RoundedCornerShape(20.dp)),
)
} else if (gpsDialogState.value is DialogState.InValid) {
PermissionDialog(
dialog = DialogProvider.GPS,
onAction = {
activity.openStatusBar()
},
modifier = Modifier.clip(RoundedCornerShape(20.dp)),
)
} else if (networkDialogState.value is DialogState.InValid) {
PermissionDialog(
dialog = DialogProvider.Network,
onAction = {
activity.openStatusBar()
},
modifier = Modifier.clip(RoundedCornerShape(20.dp)),
)
}
}

@Composable
fun PermissionDialog(
dialog: DialogProvider,
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.wlakiedialog

sealed class DialogProvider(
Copy link
Member

Choose a reason for hiding this comment

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

DialogProvider는 뭔가 Dialog전체를 제공해주는 것 같은 느낌적인 느낌...
text,Description을 뭔가 묶어서 표현할 방법이 이 없을까요? content?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

좋아요 이름 바꿔서 푸시할게요

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

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

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

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

sealed class DialogState {
object Initialized : DialogState()

object Valid : DialogState()

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

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(DialogProvider.GPS)
}
val networkDialogState = getNetworkState().map {
if (it) DialogState.Valid else DialogState.InValid(DialogProvider.Network)
}

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

override fun onCleared() {
removeGpsListener()
removeNetworkListener()
super.onCleared()
}
}
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()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.whyranoid.domain.usecase.broadcast

import com.whyranoid.domain.repository.GpsRepository
import kotlinx.coroutines.flow.StateFlow

class GetGpsState(private val gpsRepository: GpsRepository) {
operator fun invoke(): StateFlow<Boolean> {
return gpsRepository.getGpsEnabledState()
}
}
Loading
Loading