From 5c828ce9966bc6dfe32c7f71e81d2cd347381f1a Mon Sep 17 00:00:00 2001 From: dongchyeon Date: Wed, 29 May 2024 21:19:53 +0900 Subject: [PATCH] =?UTF-8?q?#77=20Feat=20:=20=EB=84=A4=ED=8A=B8=EC=9B=8C?= =?UTF-8?q?=ED=81=AC=20=EB=AA=A8=EB=8B=88=ED=84=B0=20=EB=8F=84=EC=9E=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/teamwiney/winey/MainActivity.kt | 9 +- .../java/com/teamwiney/winey/WineyNavHost.kt | 15 ++++ core/common/build.gradle.kts | 13 ++- core/common/src/main/AndroidManifest.xml | 4 +- .../ConnectivityManagerNetworkMonitor.kt | 84 +++++++++++++++++++ .../teamwiney/core/common/WineyAppState.kt | 14 ++++ .../core/common}/di/DispatcherModule.kt | 2 +- .../core/common/di/NetworkMonitorModule.kt | 25 ++++++ core/design/build.gradle.kts | 6 +- .../datasource/auth/AuthDataSourceImpl.kt | 2 +- .../data/datasource/map/MapDataSourceImpl.kt | 2 +- .../tastingnote/TastingNoteDataSourceImpl.kt | 2 +- .../datasource/wine/WineDataSourceImpl.kt | 2 +- .../winebadge/WineBadgeDataSourceImpl.kt | 2 +- .../winegrade/WineGradeDataSourceImpl.kt | 2 +- .../java/com/teamwiney/data/di/DataModule.kt | 1 + .../java/com/teamwiney/home/HomeNavigation.kt | 2 - 17 files changed, 168 insertions(+), 19 deletions(-) create mode 100644 core/common/src/main/java/com/teamwiney/core/common/ConnectivityManagerNetworkMonitor.kt rename {data/src/main/java/com/teamwiney/data => core/common/src/main/java/com/teamwiney/core/common}/di/DispatcherModule.kt (96%) create mode 100644 core/common/src/main/java/com/teamwiney/core/common/di/NetworkMonitorModule.kt diff --git a/app/src/main/java/com/teamwiney/winey/MainActivity.kt b/app/src/main/java/com/teamwiney/winey/MainActivity.kt index 8419b295..d82cbe32 100644 --- a/app/src/main/java/com/teamwiney/winey/MainActivity.kt +++ b/app/src/main/java/com/teamwiney/winey/MainActivity.kt @@ -7,14 +7,19 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.Surface import androidx.compose.ui.Modifier import androidx.core.view.WindowCompat +import com.teamwiney.core.common.NetworkMonitor import com.teamwiney.core.common.rememberWineyAppState import com.teamwiney.core.common.rememberWineyBottomSheetState import com.teamwiney.ui.theme.WineyTheme import dagger.hilt.android.AndroidEntryPoint +import javax.inject.Inject @AndroidEntryPoint class MainActivity : ComponentActivity() { + @Inject + lateinit var networkMonitor: NetworkMonitor + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // WindowInset 직접 조절하기 위해서 @@ -26,7 +31,9 @@ class MainActivity : ComponentActivity() { color = WineyTheme.colors.background_1 ) { WineyNavHost( - appState = rememberWineyAppState(), + appState = rememberWineyAppState( + networkMonitor = networkMonitor + ), bottomSheetState = rememberWineyBottomSheetState() ) } diff --git a/app/src/main/java/com/teamwiney/winey/WineyNavHost.kt b/app/src/main/java/com/teamwiney/winey/WineyNavHost.kt index 53188eb1..d21e5859 100644 --- a/app/src/main/java/com/teamwiney/winey/WineyNavHost.kt +++ b/app/src/main/java/com/teamwiney/winey/WineyNavHost.kt @@ -16,16 +16,20 @@ import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.ModalBottomSheetLayout import androidx.compose.material.Scaffold import androidx.compose.material.Snackbar +import androidx.compose.material.SnackbarDuration import androidx.compose.material.SnackbarHost import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.rememberUpdatedState import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.localbroadcastmanager.content.LocalBroadcastManager import androidx.navigation.NavDestination import androidx.navigation.NavType @@ -85,6 +89,17 @@ fun WineyNavHost( ) { val navController = appState.navController + val isOffline by appState.isOffline.collectAsStateWithLifecycle() + + LaunchedEffect(isOffline) { + if (isOffline) { + appState.scaffoldState.snackbarHostState.showSnackbar( + message = "⚠\uFE0F 인터넷에 연결되어 있지 않습니다.", + duration = SnackbarDuration.Indefinite + ) + } + } + TokenExpiredBroadcastReceiver { intent -> if (intent?.action == "com.teamwiney.winey.TOKEN_EXPIRED") { appState.showSnackbar("토큰이 만료되었습니다. 다시 로그인해주세요.") diff --git a/core/common/build.gradle.kts b/core/common/build.gradle.kts index dfea239e..038fc6ce 100644 --- a/core/common/build.gradle.kts +++ b/core/common/build.gradle.kts @@ -4,6 +4,8 @@ import java.util.Properties plugins { id(libs.plugins.android.library.get().pluginId) id(libs.plugins.jetbrains.kotlin.android.get().pluginId) + id(libs.plugins.dagger.hilt.android.get().pluginId) + id(libs.plugins.kotlin.kapt.get().pluginId) } val properties = Properties().apply { @@ -24,7 +26,6 @@ android { ) testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" - consumerProguardFiles("consumer-rules.pro") } buildFeatures { compose = true @@ -42,11 +43,11 @@ android { } } compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } kotlinOptions { - jvmTarget = "1.8" + jvmTarget = "17" } } @@ -61,5 +62,9 @@ dependencies { implementation(libs.coroutines.android) implementation(libs.lifecycle.runtime.viewmodel) implementation(libs.converter.gson) + implementation(libs.datastore) + implementation(libs.dagger) + implementation(libs.hilt.android) + kapt(libs.hilt.android.compiler) } \ No newline at end of file diff --git a/core/common/src/main/AndroidManifest.xml b/core/common/src/main/AndroidManifest.xml index 44008a43..f0f34af3 100644 --- a/core/common/src/main/AndroidManifest.xml +++ b/core/common/src/main/AndroidManifest.xml @@ -1,4 +1,4 @@ - - + + \ No newline at end of file diff --git a/core/common/src/main/java/com/teamwiney/core/common/ConnectivityManagerNetworkMonitor.kt b/core/common/src/main/java/com/teamwiney/core/common/ConnectivityManagerNetworkMonitor.kt new file mode 100644 index 00000000..003a2e99 --- /dev/null +++ b/core/common/src/main/java/com/teamwiney/core/common/ConnectivityManagerNetworkMonitor.kt @@ -0,0 +1,84 @@ +package com.teamwiney.core.common + +import android.content.Context +import android.net.ConnectivityManager +import android.net.ConnectivityManager.NetworkCallback +import android.net.Network +import android.net.NetworkCapabilities +import android.net.NetworkRequest +import android.os.Build +import android.util.Log +import androidx.core.content.getSystemService +import com.teamwiney.core.common.di.DispatcherModule +import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Dispatchers.IO +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.conflate +import kotlinx.coroutines.flow.flowOn +import javax.inject.Inject + +interface NetworkMonitor { + val isOnline: Flow +} + +class ConnectivityManagerNetworkMonitor @Inject constructor( + @ApplicationContext private val context: Context, + @DispatcherModule.IoDispatcher private val ioDispatcher: CoroutineDispatcher +): NetworkMonitor { + override val isOnline: Flow = callbackFlow { + val connectivityManager = context.getSystemService() + if (connectivityManager == null) { + trySend(false) + close() + return@callbackFlow + } + + val callback = object : NetworkCallback() { + + private val networks = mutableSetOf() + + override fun onAvailable(network: Network) { + networks += network + trySend(true) + + Log.d("NetworkMonitor", "Network available") + } + + override fun onLost(network: Network) { + networks -= network + trySend(networks.isNotEmpty()) + + Log.d("NetworkMonitor", "Network lost") + } + } + + val request = NetworkRequest.Builder() + .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) + .build() + connectivityManager.registerNetworkCallback(request, callback) + + trySend(connectivityManager.isCurrentlyConnected()) + + awaitClose { + connectivityManager.unregisterNetworkCallback(callback) + } + } + .flowOn(ioDispatcher) + .conflate() + + private fun ConnectivityManager.isCurrentlyConnected(): Boolean { + return try { + activeNetwork + ?.let(::getNetworkCapabilities) + ?.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) + ?: false + } catch (e: Exception) { + e.printStackTrace() + false + } + } +} \ No newline at end of file diff --git a/core/common/src/main/java/com/teamwiney/core/common/WineyAppState.kt b/core/common/src/main/java/com/teamwiney/core/common/WineyAppState.kt index 65465525..2ffa38dc 100644 --- a/core/common/src/main/java/com/teamwiney/core/common/WineyAppState.kt +++ b/core/common/src/main/java/com/teamwiney/core/common/WineyAppState.kt @@ -21,10 +21,14 @@ import com.naver.maps.map.compose.CameraPositionState import com.naver.maps.map.compose.rememberCameraPositionState import com.teamwiney.core.common.navigation.TopLevelDestination import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch @Composable fun rememberWineyAppState( + networkMonitor: NetworkMonitor, isMapDetail: MutableState = mutableStateOf(false), navController: NavHostController = rememberNavController(), scaffoldState: ScaffoldState = rememberScaffoldState(), @@ -33,6 +37,7 @@ fun rememberWineyAppState( ): WineyAppState { return remember(Unit) { WineyAppState( + networkMonitor, isMapDetail, navController, scaffoldState, @@ -44,6 +49,7 @@ fun rememberWineyAppState( @Stable class WineyAppState( + networkMonitor: NetworkMonitor, val isMapDetail: MutableState, val navController: NavHostController, val scaffoldState: ScaffoldState, @@ -56,6 +62,14 @@ class WineyAppState( val topLevelDestination = TopLevelDestination.values().toList() + val isOffline = networkMonitor.isOnline + .map(Boolean::not) + .stateIn( + scope = scope, + started = SharingStarted.WhileSubscribed(5_000), + initialValue = false + ) + val shouldShowBottomBar: Boolean @Composable get() = !isMapDetail.value && currentDestination?.route == topLevelDestination.find { it.route == currentDestination?.route }?.route diff --git a/data/src/main/java/com/teamwiney/data/di/DispatcherModule.kt b/core/common/src/main/java/com/teamwiney/core/common/di/DispatcherModule.kt similarity index 96% rename from data/src/main/java/com/teamwiney/data/di/DispatcherModule.kt rename to core/common/src/main/java/com/teamwiney/core/common/di/DispatcherModule.kt index 202e44db..87c7ca35 100644 --- a/data/src/main/java/com/teamwiney/data/di/DispatcherModule.kt +++ b/core/common/src/main/java/com/teamwiney/core/common/di/DispatcherModule.kt @@ -1,4 +1,4 @@ -package com.teamwiney.data.di +package com.teamwiney.core.common.di import dagger.Module import dagger.Provides diff --git a/core/common/src/main/java/com/teamwiney/core/common/di/NetworkMonitorModule.kt b/core/common/src/main/java/com/teamwiney/core/common/di/NetworkMonitorModule.kt new file mode 100644 index 00000000..35d0ccae --- /dev/null +++ b/core/common/src/main/java/com/teamwiney/core/common/di/NetworkMonitorModule.kt @@ -0,0 +1,25 @@ +package com.teamwiney.core.common.di + +import android.content.Context +import com.teamwiney.core.common.ConnectivityManagerNetworkMonitor +import com.teamwiney.core.common.NetworkMonitor +import dagger.Binds +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import kotlinx.coroutines.CoroutineDispatcher + +@Module +@InstallIn(SingletonComponent::class) +object NetworkMonitorModule { + + @Provides + fun providesNetworkMonitor( + @ApplicationContext context: Context, + @DispatcherModule.IoDispatcher ioDispatcher: CoroutineDispatcher + ): NetworkMonitor = + ConnectivityManagerNetworkMonitor(context, ioDispatcher) + +} \ No newline at end of file diff --git a/core/design/build.gradle.kts b/core/design/build.gradle.kts index 33437d4a..34bc1054 100644 --- a/core/design/build.gradle.kts +++ b/core/design/build.gradle.kts @@ -26,11 +26,11 @@ android { } } compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } kotlinOptions { - jvmTarget = "1.8" + jvmTarget = "17" } buildFeatures { compose = true diff --git a/data/src/main/java/com/teamwiney/data/datasource/auth/AuthDataSourceImpl.kt b/data/src/main/java/com/teamwiney/data/datasource/auth/AuthDataSourceImpl.kt index 3fba61e0..b0917b66 100644 --- a/data/src/main/java/com/teamwiney/data/datasource/auth/AuthDataSourceImpl.kt +++ b/data/src/main/java/com/teamwiney/data/datasource/auth/AuthDataSourceImpl.kt @@ -1,7 +1,7 @@ package com.teamwiney.data.datasource.auth import com.teamwiney.core.common.model.SocialType -import com.teamwiney.data.di.DispatcherModule +import com.teamwiney.core.common.di.DispatcherModule import com.teamwiney.data.network.model.request.FcmTokenRequest import com.teamwiney.data.network.model.request.GoogleAccessTokenRequest import com.teamwiney.data.network.model.request.PhoneNumberRequest diff --git a/data/src/main/java/com/teamwiney/data/datasource/map/MapDataSourceImpl.kt b/data/src/main/java/com/teamwiney/data/datasource/map/MapDataSourceImpl.kt index 3b06a7fd..31822532 100644 --- a/data/src/main/java/com/teamwiney/data/datasource/map/MapDataSourceImpl.kt +++ b/data/src/main/java/com/teamwiney/data/datasource/map/MapDataSourceImpl.kt @@ -1,7 +1,7 @@ package com.teamwiney.data.datasource.map import com.teamwiney.core.common.base.CommonResponse -import com.teamwiney.data.di.DispatcherModule +import com.teamwiney.core.common.di.DispatcherModule import com.teamwiney.data.network.adapter.ApiResult import com.teamwiney.data.network.model.request.MapPosition import com.teamwiney.data.network.model.response.BookmarkResult diff --git a/data/src/main/java/com/teamwiney/data/datasource/tastingnote/TastingNoteDataSourceImpl.kt b/data/src/main/java/com/teamwiney/data/datasource/tastingnote/TastingNoteDataSourceImpl.kt index 016db8c7..7c438449 100644 --- a/data/src/main/java/com/teamwiney/data/datasource/tastingnote/TastingNoteDataSourceImpl.kt +++ b/data/src/main/java/com/teamwiney/data/datasource/tastingnote/TastingNoteDataSourceImpl.kt @@ -2,7 +2,7 @@ package com.teamwiney.data.datasource.tastingnote import com.teamwiney.core.common.base.CommonResponse import com.teamwiney.core.common.`typealias`.BaseResponse -import com.teamwiney.data.di.DispatcherModule +import com.teamwiney.core.common.di.DispatcherModule import com.teamwiney.data.network.adapter.ApiResult import com.teamwiney.data.network.model.response.PagingResponse import com.teamwiney.data.network.model.response.TasteAnalysis diff --git a/data/src/main/java/com/teamwiney/data/datasource/wine/WineDataSourceImpl.kt b/data/src/main/java/com/teamwiney/data/datasource/wine/WineDataSourceImpl.kt index 72e93e3d..6645cba1 100644 --- a/data/src/main/java/com/teamwiney/data/datasource/wine/WineDataSourceImpl.kt +++ b/data/src/main/java/com/teamwiney/data/datasource/wine/WineDataSourceImpl.kt @@ -1,7 +1,7 @@ package com.teamwiney.data.datasource.wine import com.teamwiney.core.common.base.CommonResponse -import com.teamwiney.data.di.DispatcherModule +import com.teamwiney.core.common.di.DispatcherModule import com.teamwiney.data.network.adapter.ApiResult import com.teamwiney.data.network.model.response.PagingResponse import com.teamwiney.data.network.model.response.SearchWine diff --git a/data/src/main/java/com/teamwiney/data/datasource/winebadge/WineBadgeDataSourceImpl.kt b/data/src/main/java/com/teamwiney/data/datasource/winebadge/WineBadgeDataSourceImpl.kt index de5a122b..f83790a0 100644 --- a/data/src/main/java/com/teamwiney/data/datasource/winebadge/WineBadgeDataSourceImpl.kt +++ b/data/src/main/java/com/teamwiney/data/datasource/winebadge/WineBadgeDataSourceImpl.kt @@ -1,6 +1,6 @@ package com.teamwiney.data.datasource.winebadge -import com.teamwiney.data.di.DispatcherModule +import com.teamwiney.core.common.di.DispatcherModule import com.teamwiney.data.network.service.WineBadgeService import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.flow diff --git a/data/src/main/java/com/teamwiney/data/datasource/winegrade/WineGradeDataSourceImpl.kt b/data/src/main/java/com/teamwiney/data/datasource/winegrade/WineGradeDataSourceImpl.kt index 259814c2..ac82fd94 100644 --- a/data/src/main/java/com/teamwiney/data/datasource/winegrade/WineGradeDataSourceImpl.kt +++ b/data/src/main/java/com/teamwiney/data/datasource/winegrade/WineGradeDataSourceImpl.kt @@ -1,6 +1,6 @@ package com.teamwiney.data.datasource.winegrade -import com.teamwiney.data.di.DispatcherModule +import com.teamwiney.core.common.di.DispatcherModule import com.teamwiney.data.network.service.WineGradeService import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.flow diff --git a/data/src/main/java/com/teamwiney/data/di/DataModule.kt b/data/src/main/java/com/teamwiney/data/di/DataModule.kt index 303e84af..8d0c882d 100644 --- a/data/src/main/java/com/teamwiney/data/di/DataModule.kt +++ b/data/src/main/java/com/teamwiney/data/di/DataModule.kt @@ -3,6 +3,7 @@ package com.teamwiney.data.di import android.content.Context import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.Preferences +import com.teamwiney.core.common.di.DispatcherModule import com.teamwiney.data.datasource.auth.AuthDataSource import com.teamwiney.data.datasource.auth.AuthDataSourceImpl import com.teamwiney.data.datasource.map.MapDataSource diff --git a/feature/home/src/main/java/com/teamwiney/home/HomeNavigation.kt b/feature/home/src/main/java/com/teamwiney/home/HomeNavigation.kt index 58393ec0..4841b8f7 100644 --- a/feature/home/src/main/java/com/teamwiney/home/HomeNavigation.kt +++ b/feature/home/src/main/java/com/teamwiney/home/HomeNavigation.kt @@ -45,8 +45,6 @@ fun NavGraphBuilder.homeGraph( ) } - - composable( route = "${HomeDestinations.WINE_DETAIL}?id={wineId}", arguments = listOf(