diff --git a/GhostRun-Android/app/build.gradle.kts b/GhostRun-Android/app/build.gradle.kts index 4f03b60..72d4d55 100644 --- a/GhostRun-Android/app/build.gradle.kts +++ b/GhostRun-Android/app/build.gradle.kts @@ -1,6 +1,10 @@ +import org.jetbrains.kotlin.kapt3.base.Kapt.kapt + plugins { alias(libs.plugins.com.android.application) alias(libs.plugins.org.jetbrains.kotlin.android) + alias(libs.plugins.com.google.dagger.hilt.android) + kotlin("kapt") } android { @@ -66,4 +70,7 @@ dependencies { androidTestImplementation(libs.ui.test.junit4) debugImplementation(libs.ui.tooling) debugImplementation(libs.ui.test.manifest) -} \ No newline at end of file + + implementation(libs.hilt.android) + kapt(libs.hilt.compiler) +} diff --git a/GhostRun-Android/app/src/main/java/com/erooja/ghostrun_android/App.kt b/GhostRun-Android/app/src/main/java/com/erooja/ghostrun_android/App.kt new file mode 100644 index 0000000..9fa9f0c --- /dev/null +++ b/GhostRun-Android/app/src/main/java/com/erooja/ghostrun_android/App.kt @@ -0,0 +1,5 @@ +package com.erooja.ghostrun_android + +import android.app.Application +class App: Application() { +} diff --git a/GhostRun-Android/app/src/main/java/com/erooja/ghostrun_android/permission/LocationPermissionRequester.kt b/GhostRun-Android/app/src/main/java/com/erooja/ghostrun_android/permission/LocationPermissionRequester.kt new file mode 100644 index 0000000..835f07e --- /dev/null +++ b/GhostRun-Android/app/src/main/java/com/erooja/ghostrun_android/permission/LocationPermissionRequester.kt @@ -0,0 +1,80 @@ +package com.erooja.ghostrun_android.permission + +import android.Manifest +import android.content.Context +import androidx.activity.ComponentActivity +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.IntentSenderRequest +import androidx.activity.result.contract.ActivityResultContracts +import androidx.lifecycle.lifecycleScope +import com.erooja.ghostrun_android.permission.LocationPermissionUtil.permissions +import com.erooja.ghostrun_android.state.LocationPermissionState +import dagger.hilt.android.qualifiers.ActivityContext +import dagger.hilt.android.scopes.ActivityScoped +import kotlinx.coroutines.* +import kotlinx.coroutines.flow.MutableSharedFlow +import javax.inject.Inject + +@ActivityScoped +class LocationPermissionRequester @Inject constructor( + @ActivityContext private val context: Context, +) { + private val lifecycleScope: CoroutineScope + get() = (context as ComponentActivity).lifecycleScope + + private var resolutionResultLauncher: ActivityResultLauncher? = null + private var requestPermissionLauncher: ActivityResultLauncher>? = null + + val permissionFlow: MutableSharedFlow = MutableSharedFlow() + + init { + initializeLauncher() + } + + private fun initializeLauncher() { + resolutionResultLauncher = (context as ComponentActivity).activityResultRegistry.register( + RESOLUTION_RESULT, + ActivityResultContracts.StartIntentSenderForResult() + ) { + if (it.resultCode == ComponentActivity.RESULT_OK) { + emitFlow(LocationPermissionState.ObtainLocationPermission) + } else { + emitFlow(LocationPermissionState.Error.PermissionFail) + } + } + + requestPermissionLauncher = context.activityResultRegistry.register( + REQUEST_LOCATION_PERMISSION, ActivityResultContracts.RequestMultiplePermissions() + ) { permissions -> + when { + permissions.getOrDefault(Manifest.permission.ACCESS_FINE_LOCATION, false) -> { + emitFlow(LocationPermissionState.ObtainLocationPermission) + } + permissions.getOrDefault(Manifest.permission.ACCESS_COARSE_LOCATION, false) -> { + emitFlow(LocationPermissionState.Error.PermissionFail) + } + else -> { + emitFlow(LocationPermissionState.Error.PermissionFail) + } + } + } + } + + private fun emitFlow(state: LocationPermissionState) = lifecycleScope.launch { + permissionFlow.emit(state) + } + + fun requestPermission() { + requestPermissionLauncher?.launch(permissions) + } + + fun requestResolution(request: IntentSenderRequest) { + resolutionResultLauncher?.launch(request) + } + + companion object { + private const val RESOLUTION_RESULT = "RESOLUTION_RESULT" + private const val REQUEST_LOCATION_PERMISSION = "REQUEST_LOCATION_PERMISSION" + } +} + diff --git a/GhostRun-Android/app/src/main/java/com/erooja/ghostrun_android/permission/LocationPermissionUtil.kt b/GhostRun-Android/app/src/main/java/com/erooja/ghostrun_android/permission/LocationPermissionUtil.kt new file mode 100644 index 0000000..d9d39d6 --- /dev/null +++ b/GhostRun-Android/app/src/main/java/com/erooja/ghostrun_android/permission/LocationPermissionUtil.kt @@ -0,0 +1,34 @@ +package com.erooja.ghostrun_android.permission + +import android.Manifest +import android.content.Context +import android.content.pm.PackageManager +import androidx.core.content.ContextCompat +import com.erooja.ghostrun_android.state.CurrentLocationState +import kotlinx.coroutines.flow.MutableStateFlow + +object LocationPermissionUtil { + val trackingLocationFlow: MutableStateFlow = MutableStateFlow( + CurrentLocationState.UnInitialized) + + val permissions = arrayOf( + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.ACCESS_COARSE_LOCATION + ) + + fun needPermissionRequest(context: Context): Boolean { + return !isPermissionGranted(context) + } + + fun isAlreadyPermissionDenied(context: Context): Boolean { + return permissions.any { + ContextCompat.checkSelfPermission(context, it) != PackageManager.PERMISSION_GRANTED + } + } + + fun isPermissionGranted(context: Context): Boolean { + return permissions.all { + ContextCompat.checkSelfPermission(context, it) == PackageManager.PERMISSION_GRANTED + } + } +} diff --git a/GhostRun-Android/app/src/main/java/com/erooja/ghostrun_android/permission/NetworkConnectivityManager.kt b/GhostRun-Android/app/src/main/java/com/erooja/ghostrun_android/permission/NetworkConnectivityManager.kt new file mode 100644 index 0000000..75155cd --- /dev/null +++ b/GhostRun-Android/app/src/main/java/com/erooja/ghostrun_android/permission/NetworkConnectivityManager.kt @@ -0,0 +1,64 @@ +package com.erooja.ghostrun_android.permission + +import android.content.Context +import android.net.ConnectivityManager +import android.net.Network +import android.net.NetworkCapabilities +import android.net.NetworkRequest +import dagger.hilt.android.qualifiers.ActivityContext +import javax.inject.Inject + +class NetworkConnectivityManager @Inject constructor( + @ActivityContext context: Context +) { + private val connectivityManager: ConnectivityManager by lazy { + (context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager) + } + + private val networkRequest: NetworkRequest by lazy { + NetworkRequest.Builder() + .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) + .addTransportType(NetworkCapabilities.TRANSPORT_WIFI).build() + } + + private var onNetworkConnectionSucceed: (() -> Unit)? = null + private var onNetworkConnectionFailed: (() -> Unit)? = null + + private val networkConnectivityCallback by lazy { + object: ConnectivityManager.NetworkCallback() { + override fun onAvailable(network: Network) { + super.onAvailable(network) + onNetworkConnectionSucceed?.invoke() + } + + override fun onLost(network: Network) { + super.onLost(network) + onNetworkConnectionFailed?.invoke() + } + } + } + + init { + connectivityManager.registerNetworkCallback(networkRequest, networkConnectivityCallback) + } + + fun isNotAvailableNetwork(): Boolean { + val networkCapabilities = connectivityManager.activeNetwork ?: return true + val activateTransport = + connectivityManager.getNetworkCapabilities(networkCapabilities) ?: return true + + return !(activateTransport.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) + || activateTransport.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) + || activateTransport.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)) + } + + fun setNetworkConnectionSucceed(onNetworkConnectionSucceed: () -> Unit): NetworkConnectivityManager { + this.onNetworkConnectionSucceed = onNetworkConnectionSucceed + return this + } + + fun setNetworkConnectionFailed(onNetworkConnectionFailed: () -> Unit): NetworkConnectivityManager { + this.onNetworkConnectionFailed = onNetworkConnectionFailed + return this + } +} diff --git a/GhostRun-Android/app/src/main/java/com/erooja/ghostrun_android/state/Coordinate.kt b/GhostRun-Android/app/src/main/java/com/erooja/ghostrun_android/state/Coordinate.kt new file mode 100644 index 0000000..53985eb --- /dev/null +++ b/GhostRun-Android/app/src/main/java/com/erooja/ghostrun_android/state/Coordinate.kt @@ -0,0 +1,13 @@ +package com.erooja.ghostrun_android.state + +data class Coordinate( + val x: Double, + val y: Double +) { + companion object { + val Default = Coordinate( + x = Double.NaN, + y = Double.NaN + ) + } +} diff --git a/GhostRun-Android/app/src/main/java/com/erooja/ghostrun_android/state/CurrentLocationState.kt b/GhostRun-Android/app/src/main/java/com/erooja/ghostrun_android/state/CurrentLocationState.kt new file mode 100644 index 0000000..bbd673d --- /dev/null +++ b/GhostRun-Android/app/src/main/java/com/erooja/ghostrun_android/state/CurrentLocationState.kt @@ -0,0 +1,13 @@ +package com.erooja.ghostrun_android.state + +sealed class CurrentLocationState { + object UnInitialized: CurrentLocationState() + + data class Success( + val boundBottomLeftCoordinate: Coordinate = Coordinate.Default, + val boundTopRightCoordinate: Coordinate = Coordinate.Default, + val centerCoordinate: Coordinate = Coordinate.Default, + ) : CurrentLocationState() + + object Error : CurrentLocationState() +} diff --git a/GhostRun-Android/app/src/main/java/com/erooja/ghostrun_android/state/LocationPermissionState.kt b/GhostRun-Android/app/src/main/java/com/erooja/ghostrun_android/state/LocationPermissionState.kt new file mode 100644 index 0000000..ad945d9 --- /dev/null +++ b/GhostRun-Android/app/src/main/java/com/erooja/ghostrun_android/state/LocationPermissionState.kt @@ -0,0 +1,9 @@ +package com.erooja.ghostrun_android.state + +sealed class LocationPermissionState { + object ObtainLocationPermission: LocationPermissionState() + + sealed class Error : LocationPermissionState() { + object PermissionFail : Error() + } +} diff --git a/GhostRun-Android/build.gradle.kts b/GhostRun-Android/build.gradle.kts index 954205d..4c05cc9 100644 --- a/GhostRun-Android/build.gradle.kts +++ b/GhostRun-Android/build.gradle.kts @@ -2,4 +2,5 @@ plugins { alias(libs.plugins.com.android.application) apply false alias(libs.plugins.org.jetbrains.kotlin.android) apply false -} \ No newline at end of file + alias(libs.plugins.com.google.dagger.hilt.android) apply false +} diff --git a/GhostRun-Android/gradle/libs.versions.toml b/GhostRun-Android/gradle/libs.versions.toml index 9402489..7e01e29 100644 --- a/GhostRun-Android/gradle/libs.versions.toml +++ b/GhostRun-Android/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -com-android-application = "8.1.0-alpha11" +com-android-application = "8.1.0-rc01" org-jetbrains-kotlin-android = "1.7.20" core-ktx = "1.9.0" junit = "4.13.2" @@ -8,6 +8,7 @@ espresso-core = "3.5.1" lifecycle-runtime-ktx = "2.3.1" activity-compose = "1.5.1" compose-bom = "2022.10.00" +hilt = "2.44" [libraries] core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "core-ktx" } @@ -24,10 +25,13 @@ ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } material3 = { group = "androidx.compose.material3", name = "material3" } +hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" } +hilt-compiler = { group = "com.google.dagger", name = "hilt-compiler", version.ref = "hilt" } [plugins] com-android-application = { id = "com.android.application", version.ref = "com-android-application" } org-jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "org-jetbrains-kotlin-android" } +com-google-dagger-hilt-android = { id = "com.google.dagger.hilt.android", version.ref = "hilt"} [bundles] diff --git a/GhostRun-Android/local.properties b/GhostRun-Android/local.properties index ddb6016..df78be7 100644 --- a/GhostRun-Android/local.properties +++ b/GhostRun-Android/local.properties @@ -1,10 +1,8 @@ -## This file is automatically generated by Android Studio. -# Do not modify this file -- YOUR CHANGES WILL BE ERASED! -# -# This file should *NOT* be checked into Version Control Systems, +## This file must *NOT* be checked into Version Control Systems, # as it contains information specific to your local configuration. # # Location of the SDK. This is only used by Gradle. # For customization when using a Version Control System, please read the # header note. -sdk.dir=/Users/mintm1pro/Library/Android/sdk \ No newline at end of file +#Sat Jul 08 14:45:25 KST 2023 +sdk.dir=/Users/sh.jeong/Library/Android/sdk