Skip to content

Commit

Permalink
Merge pull request #336 from boostcampwm2023/AOS-feat/NetworkConnect
Browse files Browse the repository at this point in the history
Network ์ƒํƒœ ๊ด€๋ฆฌํ•˜๊ธฐ
  • Loading branch information
hegleB authored Jun 1, 2024
2 parents 13a789a + eb5af49 commit 1d735b6
Show file tree
Hide file tree
Showing 24 changed files with 374 additions and 22 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package boostcamp.and07.mindsync.data.di

import android.content.Context
import android.net.ConnectivityManager
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
object ConnectivityManagerModule {
@Singleton
@Provides
fun provideConnectivityManager(
@ApplicationContext context: Context,
): ConnectivityManager {
return context.getSystemService(ConnectivityManager::class.java)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package boostcamp.and07.mindsync.data.network

import android.net.ConnectivityManager
import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
import android.net.NetworkRequest
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import javax.inject.Inject

class NetworkManager
@Inject
constructor(private val connectivityManager: ConnectivityManager) {
private val _isConnected = MutableStateFlow(false)
val isConnected: StateFlow<Boolean> = _isConnected

private val request: NetworkRequest =
NetworkRequest.Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
.addCapability(NET_CAPABILITY_INTERNET)
.build()

private val networkCallback =
object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
_isConnected.value = true
}

override fun onLost(network: Network) {
_isConnected.value = false
}
}

fun registerNetworkCallback() {
connectivityManager.registerNetworkCallback(request, networkCallback)
}

fun unRegisterNetworkCallback() {
connectivityManager.unregisterNetworkCallback(networkCallback)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,16 @@ package boostcamp.and07.mindsync.ui.base

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import boostcamp.and07.mindsync.data.network.NetworkManager
import boostcamp.and07.mindsync.data.repository.login.LogoutEventRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import javax.inject.Inject

Expand All @@ -15,9 +20,17 @@ open class BaseActivityViewModel
@Inject
constructor(
private val logoutEventRepository: LogoutEventRepository,
private val networkManager: NetworkManager,
) : ViewModel() {
private val _events = MutableSharedFlow<ViewEvent>()
val events: SharedFlow<ViewEvent> = _events.asSharedFlow()
private val _isConnected = MutableStateFlow(false)
val isConnected: StateFlow<Boolean> = _isConnected

init {
networkManager.registerNetworkCallback()
observerNetworkConnection()
}

private fun sendLogoutEvent() {
viewModelScope.launch {
Expand All @@ -29,9 +42,22 @@ open class BaseActivityViewModel
}
}

private fun observerNetworkConnection() {
viewModelScope.launch {
networkManager.isConnected.collectLatest { isConnected ->
_isConnected.update { isConnected }
}
}
}

fun logout() {
viewModelScope.launch {
logoutEventRepository.logout()
}
}

override fun onCleared() {
super.onCleared()
networkManager.unRegisterNetworkCallback()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import boostcamp.and07.mindsync.R
import boostcamp.and07.mindsync.data.model.Board
import boostcamp.and07.mindsync.ui.dialog.AddBoardDialogScreen
import boostcamp.and07.mindsync.ui.dialog.DisConnectedNetworkDialogScreen
import boostcamp.and07.mindsync.ui.theme.MindSyncTheme
import coil.compose.AsyncImage
import java.io.File
Expand All @@ -58,6 +59,7 @@ fun BoardListScreen(
updateBoardName: (CharSequence) -> Unit,
) {
val uiState by boardListViewModel.boardUiState.collectAsStateWithLifecycle()
val isConnected by boardListViewModel.isConnected.collectAsStateWithLifecycle()
Scaffold(bottomBar = {
BoardListBottomBar(
uiState = uiState,
Expand Down Expand Up @@ -86,6 +88,9 @@ fun BoardListScreen(
closeDialog = { showDialog(false) },
)
}
if (isConnected.not()) {
DisConnectedNetworkDialogScreen()
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package boostcamp.and07.mindsync.ui.boardlist

import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import boostcamp.and07.mindsync.data.network.NetworkManager
import boostcamp.and07.mindsync.data.repository.boardlist.BoardListRepository
import boostcamp.and07.mindsync.data.repository.login.LogoutEventRepository
import boostcamp.and07.mindsync.ui.base.BaseActivityViewModel
import boostcamp.and07.mindsync.ui.util.fileToMultiPart
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.CoroutineExceptionHandler
Expand All @@ -23,7 +25,9 @@ class BoardListViewModel
constructor(
private val savedStateHandle: SavedStateHandle,
private val boardListRepository: BoardListRepository,
) : ViewModel() {
logoutEventRepository: LogoutEventRepository,
networkManager: NetworkManager,
) : BaseActivityViewModel(logoutEventRepository, networkManager) {
private val _boardUiState = MutableStateFlow(BoardListUiState())
val boardUiState: StateFlow<BoardListUiState> = _boardUiState
private val _boardUiEvent = MutableSharedFlow<BoardListUiEvent>()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package boostcamp.and07.mindsync.ui.dialog

import android.app.Dialog
import android.content.DialogInterface
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import androidx.fragment.app.DialogFragment
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import boostcamp.and07.mindsync.R
import boostcamp.and07.mindsync.databinding.DialogDisconnectNetworkBinding
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch

@AndroidEntryPoint
class DisConnectedNetworkDialog(private val isConnected: StateFlow<Boolean>) :
DialogFragment() {
private var _binding: DialogDisconnectNetworkBinding? = null
private val binding get() = _binding!!

override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val dialog = super.onCreateDialog(savedInstanceState)
dialog.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
dialog.window?.attributes?.windowAnimations = R.style.AnimationDialogStyle
isCancelable = false
return dialog
}

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?,
): View? {
_binding = DialogDisconnectNetworkBinding.inflate(inflater, container, false)
observerNetworkState()
return binding.root
}

override fun onStart() {
super.onStart()
resizeDialog()
}

private fun observerNetworkState() {
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
isConnected.collectLatest { isConnected ->
if (isConnected) {
dismiss()
}
}
}
}
}

private fun resizeDialog() {
val params: ViewGroup.LayoutParams? = dialog?.window?.attributes

val displayMetrics = requireActivity().resources.displayMetrics
val deviceWidth = displayMetrics.widthPixels

params?.width = (deviceWidth * 0.8).toInt()
params?.height = WindowManager.LayoutParams.WRAP_CONTENT
dialog?.window?.attributes = params as WindowManager.LayoutParams
}

override fun onDismiss(dialog: DialogInterface) {
_binding = null
super.onDismiss(dialog)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package boostcamp.and07.mindsync.ui.dialog

import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.width
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import boostcamp.and07.mindsync.R
import boostcamp.and07.mindsync.ui.theme.MindSyncTheme

@Composable
fun DisConnectedNetworkDialogScreen() {
val screenWidth = LocalConfiguration.current.screenWidthDp.dp
val screenHeight = LocalConfiguration.current.screenHeightDp.dp
val dialogWidth = screenWidth * 0.8f

Dialog(onDismissRequest = {}) {
Column(
modifier =
Modifier
.width(dialogWidth)
.height(screenHeight),
verticalArrangement = Arrangement.Center,
) {
Row(
modifier =
Modifier
.align(Alignment.CenterHorizontally),
verticalAlignment = Alignment.CenterVertically,
) {
Image(
painter = painterResource(id = R.drawable.ic_network_off),
contentDescription = null,
)
}
Spacer(modifier = Modifier.height(30.dp))
Row(
modifier = Modifier.align(Alignment.CenterHorizontally),
) {
Text(
text = stringResource(id = R.string.disconnected_network_message),
style = MaterialTheme.typography.displaySmall,
)
}
}
}
}

@Preview(showBackground = true)
@Composable
private fun LoadingDialogPreview() {
MindSyncTheme {
DisConnectedNetworkDialogScreen()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package boostcamp.and07.mindsync.ui.login

import android.util.Log
import androidx.lifecycle.viewModelScope
import boostcamp.and07.mindsync.data.network.NetworkManager
import boostcamp.and07.mindsync.data.repository.login.LoginRepository
import boostcamp.and07.mindsync.data.repository.login.LogoutEventRepository
import boostcamp.and07.mindsync.data.repository.login.TokenRepository
Expand All @@ -24,9 +25,10 @@ class LoginViewModel
constructor(
private val loginRepository: LoginRepository,
private val tokenRepository: TokenRepository,
private val logoutEventRepository: LogoutEventRepository,
logoutEventRepository: LogoutEventRepository,
networkManager: NetworkManager,
) :
BaseActivityViewModel(logoutEventRepository) {
BaseActivityViewModel(logoutEventRepository, networkManager) {
private val _loginEvent = MutableSharedFlow<LoginEvent>()
val loginEvent = _loginEvent.asSharedFlow()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import boostcamp.and07.mindsync.databinding.ActivityMainBinding
import boostcamp.and07.mindsync.ui.base.BaseActivity
import boostcamp.and07.mindsync.ui.base.BaseActivityViewModel
import boostcamp.and07.mindsync.ui.boardlist.UsersAdapter
import boostcamp.and07.mindsync.ui.dialog.DisConnectedNetworkDialog
import boostcamp.and07.mindsync.ui.profile.ProfileActivity
import boostcamp.and07.mindsync.ui.space.list.SpaceListFragmentDirections
import boostcamp.and07.mindsync.ui.util.ThrottleDuration
Expand Down Expand Up @@ -54,6 +55,7 @@ class MainActivity :
setSideBarNavigation()
setBinding()
observeEvent()
showDisconnectedNetworkDialog()
}

override fun getViewModel(): BaseActivityViewModel {
Expand Down Expand Up @@ -83,6 +85,22 @@ class MainActivity :
}
}

private fun showDisconnectedNetworkDialog() {
val dialog = DisConnectedNetworkDialog(mainViewModel.isConnected)
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
mainViewModel.isConnected.collectLatest { isConnected ->
if (isConnected.not()) {
dialog.show(
this@MainActivity.supportFragmentManager,
"DisConnectedNetworkDialog",
)
}
}
}
}
}

private fun setNavController() {
val navHostFragment =
(supportFragmentManager.findFragmentById(R.id.fcv_main_nav_host) as NavHostFragment)
Expand Down Expand Up @@ -213,7 +231,11 @@ class MainActivity :
spaceAdapter.setSideBarClickListener(
object : SpaceClickListener {
override fun onClickSpace(space: Space) {
navController.navigate(SpaceListFragmentDirections.actionToBoardListFragment(spaceId = space.id))
navController.navigate(
SpaceListFragmentDirections.actionToBoardListFragment(
spaceId = space.id,
),
)
mainViewModel.updateCurrentSpace(space)
}
},
Expand Down
Loading

0 comments on commit 1d735b6

Please sign in to comment.