diff --git a/app/src/main/java/com/example/mhnfe/data/network/AuthAuthenticator.kt b/app/src/main/java/com/example/mhnfe/data/network/AuthAuthenticator.kt index 9408dc6e..e3a3e4ce 100644 --- a/app/src/main/java/com/example/mhnfe/data/network/AuthAuthenticator.kt +++ b/app/src/main/java/com/example/mhnfe/data/network/AuthAuthenticator.kt @@ -33,9 +33,9 @@ class AuthAuthenticator @Inject constructor( return runBlocking { // val newTokenResult = tokenManager.refreshAccessToken() try { - Log.d("AuthAuthenticator","get newToken try") + Log.d("AuthAuthenticator", "get newToken try") val newTokenResult = tokenManager.refreshAccessToken() - Log.d("AuthAuthenticator","AuthAuthenticator get newToken!!}") + Log.d("AuthAuthenticator", "AuthAuthenticator get newToken!!}") if (newTokenResult != null) { val accessToken = newTokenResult // Update the access token in your storage. @@ -46,7 +46,10 @@ class AuthAuthenticator @Inject constructor( .header("Authorization", accessToken) .build() } else { - Log.d("AuthAuthenticator","AuthAuthenticator failed by expired refreshToken!!") + Log.d( + "AuthAuthenticator", + "AuthAuthenticator failed by expired refreshToken!!" + ) return@runBlocking null } } catch (e: Exception) { diff --git a/app/src/main/java/com/example/mhnfe/data/remote/api/ImageApi.kt b/app/src/main/java/com/example/mhnfe/data/remote/api/ImageApi.kt index e6980d7b..c7c1b75b 100644 --- a/app/src/main/java/com/example/mhnfe/data/remote/api/ImageApi.kt +++ b/app/src/main/java/com/example/mhnfe/data/remote/api/ImageApi.kt @@ -22,17 +22,19 @@ interface ImageApi { @Url presignedUrl: String, @Body image: RequestBody ): Response + @POST("api/image-device") suspend fun imageDevice( @Header("Authorization") token: String, @Body request: ImageSaveRequest - ) : saveImageResponse + ): saveImageResponse @POST("/api/image-device/presigned-url") - suspend fun urlImage( + suspend fun urlImage( @Header("Authorization") token: String, @Body request: ImageRequest - ) : ImageResponse + ): ImageResponse + @GET("api/image") suspend fun getImages( @Header("Authorization") token: String, diff --git a/app/src/main/java/com/example/mhnfe/data/remote/response/ImageResponse.kt b/app/src/main/java/com/example/mhnfe/data/remote/response/ImageResponse.kt index a870060f..e5fa116b 100644 --- a/app/src/main/java/com/example/mhnfe/data/remote/response/ImageResponse.kt +++ b/app/src/main/java/com/example/mhnfe/data/remote/response/ImageResponse.kt @@ -1,15 +1,16 @@ package com.example.mhnfe.data.remote.response - data class ImageResponse( val result: Result, val body: ImageResponseBody ) + data class saveImageResponse( val result: Result, val body: EmptyBody ) + data class ImageListResponse( val result: Result, val body: ImageListBody @@ -22,8 +23,9 @@ data class ImageResponseBody( ) data class ImageListBody( - val images : List + val images: List ) + data class ImageInfo( val imageId: Long, val imageName: String, diff --git a/app/src/main/java/com/example/mhnfe/data/remote/response/QrResponse.kt b/app/src/main/java/com/example/mhnfe/data/remote/response/QrResponse.kt index fd8e1847..6ef78be5 100644 --- a/app/src/main/java/com/example/mhnfe/data/remote/response/QrResponse.kt +++ b/app/src/main/java/com/example/mhnfe/data/remote/response/QrResponse.kt @@ -30,5 +30,5 @@ data class CctvInfoResponse( data class CctvSelfInfoResponse( val result: Result, - val body : CctvSelfInfoResponseBody + val body: CctvSelfInfoResponseBody ) \ No newline at end of file diff --git a/app/src/main/java/com/example/mhnfe/data/repository/ImageRepositoryImpl.kt b/app/src/main/java/com/example/mhnfe/data/repository/ImageRepositoryImpl.kt index 781b111a..cec2b376 100644 --- a/app/src/main/java/com/example/mhnfe/data/repository/ImageRepositoryImpl.kt +++ b/app/src/main/java/com/example/mhnfe/data/repository/ImageRepositoryImpl.kt @@ -25,7 +25,7 @@ class ImageRepositoryImpl @Inject constructor( private val imageApi: ImageApi, private val accessTokenDataStore: DataStore, private val cctvResponseDataStore: DataStore, -): ImageRepository { +) : ImageRepository { override suspend fun getPresignedUrl(imageName: String): ImageResponse { val cctvAccessToken = cctvResponseDataStore.data.map { it.accessToken }.first() @@ -39,13 +39,14 @@ class ImageRepositoryImpl @Inject constructor( } return response } + override suspend fun saveImage(imageName: String, imagePath: String): saveImageResponse { val cctvAccessToken = cctvResponseDataStore.data.map { it.accessToken }.first() val request = ImageSaveRequest( imageName = imageName, imagePath = imagePath ) - val response = withContext(Dispatchers.IO) { + val response = withContext(Dispatchers.IO) { imageApi.imageDevice( token = cctvAccessToken, request = request @@ -68,6 +69,7 @@ class ImageRepositoryImpl @Inject constructor( false } } + override suspend fun getImages(year: Int, month: String, day: String): ImageListResponse { val token = accessTokenDataStore.data.map { it.accessToken }.first() return withContext(Dispatchers.IO) { diff --git a/app/src/main/java/com/example/mhnfe/di/module/NetworkModule.kt b/app/src/main/java/com/example/mhnfe/di/module/NetworkModule.kt index b607490f..e75e4f6f 100644 --- a/app/src/main/java/com/example/mhnfe/di/module/NetworkModule.kt +++ b/app/src/main/java/com/example/mhnfe/di/module/NetworkModule.kt @@ -100,6 +100,7 @@ object NetworkModule { fun provideDeviceApi(@Named("default") retrofit: Retrofit): DeviceApi { return retrofit.create(DeviceApi::class.java) } + @Provides @Singleton fun provideImageApi(@Named("default") retrofit: Retrofit): ImageApi { diff --git a/app/src/main/java/com/example/mhnfe/di/module/RepositoryModule.kt b/app/src/main/java/com/example/mhnfe/di/module/RepositoryModule.kt index 32511ffc..b97a44ab 100644 --- a/app/src/main/java/com/example/mhnfe/di/module/RepositoryModule.kt +++ b/app/src/main/java/com/example/mhnfe/di/module/RepositoryModule.kt @@ -67,9 +67,14 @@ object RepositoryModule { fun provideTokenRepository(tokenApi: TokenApi) : TokenRepository { return TokenRepositoryImpl(tokenApi) } + @Provides @Singleton - fun provideImageRepository(imageApi: ImageApi, cctvResponseDataStore: DataStore,accessTokenDataStore: DataStore) :ImageRepository { + fun provideImageRepository( + imageApi: ImageApi, + cctvResponseDataStore: DataStore, + accessTokenDataStore: DataStore + ): ImageRepository { return ImageRepositoryImpl(imageApi, accessTokenDataStore, cctvResponseDataStore) } } \ No newline at end of file diff --git a/app/src/main/java/com/example/mhnfe/domain/ai/BoundingBoxUtils.kt b/app/src/main/java/com/example/mhnfe/domain/ai/BoundingBoxUtils.kt index b6043dc2..5da96521 100644 --- a/app/src/main/java/com/example/mhnfe/domain/ai/BoundingBoxUtils.kt +++ b/app/src/main/java/com/example/mhnfe/domain/ai/BoundingBoxUtils.kt @@ -1,61 +1,50 @@ package com.example.mhnfe.domain.ai import com.example.mhnfe.domain.ai.yolo.BoundingBox -import org.json.JSONObject - -// 좌표 데이터 클래스 -data class BoundingBoxCoordinates( - val x1: Int, val y1: Int, - val x2: Int, val y2: Int, - val x3: Int, val y3: Int, - val x4: Int, val y4: Int -) object BoundingBoxUtils { - // BoundingBox 객체의 좌표를 BoundingBoxCoordinates 객체로 변환 - private fun boundingBoxCoordinates(box: BoundingBox): BoundingBoxCoordinates { - return BoundingBoxCoordinates( - x1 = box.x1.toInt(), // 좌상단 x - y1 = box.y1.toInt(), // 좌상단 y - x2 = box.x2.toInt(), // 우상단 x - y2 = box.y1.toInt(), // 우상단 y (같은 y값) - x3 = box.x1.toInt(), // 좌하단 x (같은 x값) - y3 = box.y2.toInt(), // 좌하단 y - x4 = box.x2.toInt(), // 우하단 x (같은 x값) - y4 = box.y2.toInt() // 우하단 y - ) - } - - // 각 박스의 좌표를 JSON 형식으로 반환 - fun generateCoordinatesJson(boundingBoxes: List): String { - return JSONObject().apply { - boundingBoxes.forEachIndexed { index, box -> - val coordinates = boundingBoxCoordinates(box) - put("box_$index", JSONObject().apply { - put("x1", coordinates.x1) - put("y1", coordinates.y1) - put("x2", coordinates.x2) - put("y2", coordinates.y2) - put("x3", coordinates.x3) - put("y3", coordinates.y3) - put("x4", coordinates.x4) - put("y4", coordinates.y4) - }) - } - }.toString() - } - - fun generateObjectNameJson(boundingBoxes: List): String = - boundingBoxes.joinToString(prefix = "[", postfix = "]") { box -> - "{\"object_name\":\"${box.objectName}\"}" + // 객체 이름과 신뢰도를 각각 반환하는 메서드 + fun getTypeAndConfidence(boundingBoxes: List): Pair { + return if (boundingBoxes.isNotEmpty()) { + val box = boundingBoxes.first() + box.objectName to box.cnf + } else { + "unknown" to 0.0f } + } - fun generateConfidenceJson(boundingBoxes: List): String = - boundingBoxes.joinToString(prefix = "[", postfix = "]") { box -> - "{\"confidence\":${box.cnf}}" + // 각 박스의 좌표를 반환하는 메서드 + fun getCoordinates(boundingBoxes: List): List> { + return boundingBoxes.map { box -> + mapOf( + "x1" to box.x1, + "y1" to box.y1, + "x2" to box.x2, + "y2" to box.y2, + "x3" to box.x1, + "y3" to box.y2, + "x4" to box.x2, + "y4" to box.y1 + ) } + } + // 이벤트 발생 여부를 결정하는 메서드 fun shouldTriggerEvent(lastEventTime: Long, eventDelayMillis: Long): Boolean = (System.currentTimeMillis() - lastEventTime) >= eventDelayMillis -} \ No newline at end of file + + // 좌표를 단일 JSON 객체로 변환하는 메서드 + fun getFirstCoordinatesAsJson(boundingBoxes: List): String { + return boundingBoxes.firstOrNull()?.let { box -> + """ + { + "x1": ${box.x1}, "y1": ${box.y1}, + "x2": ${box.x2}, "y2": ${box.y2}, + "x3": ${box.x1}, "y3": ${box.y2}, + "x4": ${box.x2}, "y4": ${box.y1} + } + """.trimIndent() + } ?: "{}" + } +} diff --git a/app/src/main/java/com/example/mhnfe/domain/ai/yolo/BoundingBoxProcessor.kt b/app/src/main/java/com/example/mhnfe/domain/ai/yolo/BoundingBoxProcessor.kt index e697d903..875d9369 100644 --- a/app/src/main/java/com/example/mhnfe/domain/ai/yolo/BoundingBoxProcessor.kt +++ b/app/src/main/java/com/example/mhnfe/domain/ai/yolo/BoundingBoxProcessor.kt @@ -1,12 +1,12 @@ package com.example.mhnfe.domain.ai.yolo data class BoundingBox( - val x1: Float, - val y1: Float, - val x2: Float, - val y2: Float, - val w: Float, // 너비 - val h: Float, // 높이 + val x1: Int, + val y1: Int, + val x2: Int, + val y2: Int, + val w: Int, // 너비 + val h: Int, // 높이 val cnf: Float, // confidence val idxNum: Int, // 탐지된 번호 val objectName: String @@ -18,6 +18,8 @@ object BoundingBoxProcessor { private const val CONFIDENCE_THRESHOLD = 0.3F private const val IOU_THRESHOLD = 0.5F private const val TAG = "BoundingBoxProcessor" + private const val SCREEN_WIDTH = 1280 + private const val SCREEN_HEIGHT = 720 // 콜백 -> 탐지된 객체 정보를 처리하도록 수정 fun bestBoxes( @@ -45,23 +47,23 @@ object BoundingBoxProcessor { if (maxConf > CONFIDENCE_THRESHOLD) { val clsName = labels[maxIdx] - val cx = array[c] - val cy = array[c + numElements] - val w = array[c + numElements * 2] - val h = array[c + numElements * 3] - val x1 = cx - (w / 2F) - val y1 = cy - (h / 2F) - val x2 = cx + (w / 2F) - val y2 = cy + (h / 2F) - if (x1 < 0F || x1 > 1F) continue - if (y1 < 0F || y1 > 1F) continue - if (x2 < 0F || x2 > 1F) continue - if (y2 < 0F || y2 > 1F) continue + val cx = array[c] * SCREEN_WIDTH + val cy = array[c + numElements] * SCREEN_HEIGHT + val w = array[c + numElements * 2] * SCREEN_WIDTH + val h = array[c + numElements * 3] * SCREEN_HEIGHT + val x1 = (cx - (w / 2F)).toInt() + val y1 = (cy - (h / 2F)).toInt() + val x2 = (cx + (w / 2F)).toInt() + val y2 = (cy + (h / 2F)).toInt() + if (x1 < 0 || x1 > SCREEN_WIDTH) continue + if (y1 < 0 || y1 > SCREEN_HEIGHT) continue + if (x2 < 0 || x2 > SCREEN_WIDTH) continue + if (y2 < 0 || y2 > SCREEN_HEIGHT) continue boundingBoxes.add( BoundingBox( x1 = x1, y1 = y1, x2 = x2, y2 = y2, - w = w, h = h, + w = w.toInt(), h = h.toInt(), cnf = maxConf, idxNum = maxIdx, objectName = clsName ) ) @@ -106,9 +108,9 @@ object BoundingBoxProcessor { val y1 = maxOf(box1.y1, box2.y1) val x2 = minOf(box1.x2, box2.x2) val y2 = minOf(box1.y2, box2.y2) - val intersectionArea = maxOf(0F, x2 - x1) * maxOf(0F, y2 - y1) + val intersectionArea = maxOf(0F, (x2 - x1).toFloat()) * maxOf(0F, (y2 - y1).toFloat()) val box1Area = box1.w * box1.h val box2Area = box2.w * box2.h return intersectionArea / (box1Area + box2Area - intersectionArea) } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/example/mhnfe/domain/mqtt/MqttViewModel.kt b/app/src/main/java/com/example/mhnfe/domain/mqtt/MqttViewModel.kt index 90e47d70..e95e0598 100644 --- a/app/src/main/java/com/example/mhnfe/domain/mqtt/MqttViewModel.kt +++ b/app/src/main/java/com/example/mhnfe/domain/mqtt/MqttViewModel.kt @@ -322,32 +322,32 @@ class MqttViewModel @Inject constructor( fun eventTopic( trackingId: Int, objectName: String, - coordinates: String, - confidence: String + confidence: Float, + coordinates: List> ) { - val payload = + val payload = coordinates.firstOrNull().let { coord -> """ - { - "trackingId": $trackingId, - "timestamp": ${System.currentTimeMillis() / 1000}, - "objectType": "$objectName", - "confidence": "$confidence", - "coordinates": $coordinates - } - """.trimIndent() + { + "trackingId": $trackingId, + "timestamp": ${System.currentTimeMillis() / 1000}, + "objectType": "$objectName", + "confidence": $confidence, + "coordinates": { + "x1": ${coord!!["x1"]}, "y1": ${coord["y1"]}, + "x2": ${coord["x2"]}, "y2": ${coord["y2"]}, + "x3": ${coord["x3"]}, "y3": ${coord["y3"]}, + "x4": ${coord["x4"]}, "y4": ${coord["y4"]} + } + } + """.trimIndent() + } publish("/mhn/event/detect/things/$thingId", payload) } - - private fun publish(topic: String, payload: String) { - try { - awsMqttManager.publishString(payload, topic, AWSIotMqttQos.QOS0) - Log.d(tag, "Published to topic $topic: $payload") - } catch (e: Exception) { - Log.e(tag, "Failed to publish message: ${e.message}", e) - } + awsMqttManager.publishString(payload, topic, AWSIotMqttQos.QOS0) + Log.d(tag, "Published to topic $topic: \n $payload") } private fun subscribe(topic: String, onMessageReceived: (String, String) -> Unit) { diff --git a/app/src/main/java/com/example/mhnfe/domain/repository/ImageRepository.kt b/app/src/main/java/com/example/mhnfe/domain/repository/ImageRepository.kt index 60c8e62d..6c851b0e 100644 --- a/app/src/main/java/com/example/mhnfe/domain/repository/ImageRepository.kt +++ b/app/src/main/java/com/example/mhnfe/domain/repository/ImageRepository.kt @@ -7,6 +7,6 @@ import com.example.mhnfe.data.remote.response.saveImageResponse interface ImageRepository { suspend fun getPresignedUrl(imageName: String): ImageResponse suspend fun saveImage(imageName: String, imagePath: String): saveImageResponse - suspend fun uploadToPresignedUrl(presignedUrl: String, imageData: ByteArray) :Boolean + suspend fun uploadToPresignedUrl(presignedUrl: String, imageData: ByteArray): Boolean suspend fun getImages(year: Int, month: String, day: String): ImageListResponse } \ No newline at end of file diff --git a/app/src/main/java/com/example/mhnfe/ui/components/TopBar.kt b/app/src/main/java/com/example/mhnfe/ui/components/TopBar.kt index ebc156a8..ac5253ee 100644 --- a/app/src/main/java/com/example/mhnfe/ui/components/TopBar.kt +++ b/app/src/main/java/com/example/mhnfe/ui/components/TopBar.kt @@ -59,12 +59,12 @@ fun MainTopBar( .padding(end = 20.dp), horizontalArrangement = Arrangement.End, verticalAlignment = Alignment.CenterVertically - ){ + ) { Image( modifier = modifier .size(35.dp, 35.dp) - .clickable {onImageClick()}, + .clickable { onImageClick() }, painter = painterResource(id = R.drawable.logo2), contentDescription = "멍하냥 로고", colorFilter = ColorFilter.tint(Color.White) @@ -118,7 +118,7 @@ fun SubTopBar( @Preview(showBackground = true) @Composable fun MainTopBarPreview() { - Column ( + Column( modifier = Modifier .fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally, diff --git a/app/src/main/java/com/example/mhnfe/ui/navigation/AppNavigation.kt b/app/src/main/java/com/example/mhnfe/ui/navigation/AppNavigation.kt index 756ab071..32fdde66 100644 --- a/app/src/main/java/com/example/mhnfe/ui/navigation/AppNavigation.kt +++ b/app/src/main/java/com/example/mhnfe/ui/navigation/AppNavigation.kt @@ -228,7 +228,7 @@ fun MainContent( composable(NavRoutes.Monitoring.Group.route) { GroupScreen( userType = userType, - bottomNavController= bottomNavController, + bottomNavController = bottomNavController, mainNavController = mainNavController ) } diff --git a/app/src/main/java/com/example/mhnfe/ui/screens/auth/login/LoginScreen.kt b/app/src/main/java/com/example/mhnfe/ui/screens/auth/login/LoginScreen.kt index c3ca9c83..d7be2f45 100644 --- a/app/src/main/java/com/example/mhnfe/ui/screens/auth/login/LoginScreen.kt +++ b/app/src/main/java/com/example/mhnfe/ui/screens/auth/login/LoginScreen.kt @@ -183,7 +183,10 @@ fun LoginScreen( .fillMaxWidth() .wrapContentHeight(), horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(14.dp, alignment = Alignment.CenterVertically) + verticalArrangement = Arrangement.spacedBy( + 14.dp, + alignment = Alignment.CenterVertically + ) ) { if (errorMessage != null) { Text( diff --git a/app/src/main/java/com/example/mhnfe/ui/screens/auth/login/LoginViewModel.kt b/app/src/main/java/com/example/mhnfe/ui/screens/auth/login/LoginViewModel.kt index 9888b6f6..16cb7a78 100644 --- a/app/src/main/java/com/example/mhnfe/ui/screens/auth/login/LoginViewModel.kt +++ b/app/src/main/java/com/example/mhnfe/ui/screens/auth/login/LoginViewModel.kt @@ -102,6 +102,7 @@ class LoginViewModel @Inject constructor( _errorMessage.value = "잘못된 아이디 또는 비밀번호입니다." Log.e("LoginViewModel", "Error: ${_errorMessage.value}") } + else -> { Log.e("LoginViewModel", "HttpException: ${e.message}") _errorMessage.value = "서버 오류가 발생했습니다. 다시 시도해주세요." diff --git a/app/src/main/java/com/example/mhnfe/ui/screens/auth/select/SelectScreen.kt b/app/src/main/java/com/example/mhnfe/ui/screens/auth/select/SelectScreen.kt index 1d636cd6..b56af80a 100644 --- a/app/src/main/java/com/example/mhnfe/ui/screens/auth/select/SelectScreen.kt +++ b/app/src/main/java/com/example/mhnfe/ui/screens/auth/select/SelectScreen.kt @@ -51,7 +51,7 @@ fun SelectScreen( topBar = { MainTopBar( text = "선택", - onImageClick = { navController.navigate(NavRoutes.Auth.Login.route)}) + onImageClick = { navController.navigate(NavRoutes.Auth.Login.route) }) }, snackbarHost = { SnackbarHost(snackbarHostState) } ) { paddingValues -> diff --git a/app/src/main/java/com/example/mhnfe/ui/screens/monitoring/group/GroupScreen.kt b/app/src/main/java/com/example/mhnfe/ui/screens/monitoring/group/GroupScreen.kt index 314b384b..833b21b4 100644 --- a/app/src/main/java/com/example/mhnfe/ui/screens/monitoring/group/GroupScreen.kt +++ b/app/src/main/java/com/example/mhnfe/ui/screens/monitoring/group/GroupScreen.kt @@ -57,13 +57,13 @@ fun GroupScreen( MainTopBar( text = groupInfo?.groupName ?: "그룹", onImageClick = { - try{ + try { authStateViewModel.logout() - mainNavController.navigate(NavRoutes.Auth.Main.route){ + mainNavController.navigate(NavRoutes.Auth.Main.route) { popUpTo(NavRoutes.Main.route) { inclusive = true } } } catch (e: Exception) { - mainNavController.navigate(NavRoutes.Auth.Main.route){ + mainNavController.navigate(NavRoutes.Auth.Main.route) { popUpTo(NavRoutes.Main.route) { inclusive = true } } } @@ -123,17 +123,20 @@ fun GroupScreen( horizontalAlignment = Alignment.CenterHorizontally ) { items(items = cctvList, key = { it.cctvId }) { cctvItem -> - CCTVItemCard(cctv = cctvItem.toCCTV(), groupId = groupInfo!!.groupId, onClick = { - bottomNavController.currentBackStackEntry?.savedStateHandle?.set( + CCTVItemCard( + cctv = cctvItem.toCCTV(), + groupId = groupInfo!!.groupId, + onClick = { + bottomNavController.currentBackStackEntry?.savedStateHandle?.set( "role", ChannelRole.VIEWER ) - bottomNavController.navigate( + bottomNavController.navigate( NavRoutes.Monitoring.Viewer.createRoute( channelName = cctvItem.kvsChannelName ) ) }, onEdit = { - bottomNavController.navigate( + bottomNavController.navigate( NavRoutes.Monitoring.DeviceInformation.createRoute( cctvItem.cctvId ) diff --git a/app/src/main/java/com/example/mhnfe/ui/screens/monitoring/group/MonitoringComponents.kt b/app/src/main/java/com/example/mhnfe/ui/screens/monitoring/group/MonitoringComponents.kt index 33ad18d2..306d71c9 100644 --- a/app/src/main/java/com/example/mhnfe/ui/screens/monitoring/group/MonitoringComponents.kt +++ b/app/src/main/java/com/example/mhnfe/ui/screens/monitoring/group/MonitoringComponents.kt @@ -134,7 +134,9 @@ fun CCTVItemCard( modifier = modifier.size(24.dp) ) { Icon( - modifier = modifier.size(20.dp, 16.dp).align(Alignment.Center), + modifier = modifier + .size(20.dp, 16.dp) + .align(Alignment.Center), painter = painterResource(id = if(networkStatus >= 2) R.drawable.good_signal else R.drawable.bad_signal), contentDescription = "signal", tint = Color.Unspecified @@ -144,7 +146,9 @@ fun CCTVItemCard( modifier = modifier.size(24.dp) ) { Icon( - modifier = modifier.size(20.dp, 16.dp).align(Alignment.Center), + modifier = modifier + .size(20.dp, 16.dp) + .align(Alignment.Center), painter = painterResource( id = when { //배터리 양 별로 다른 아이콘 diff --git a/app/src/main/java/com/example/mhnfe/ui/screens/monitoring/kvs/AiViewModel.kt b/app/src/main/java/com/example/mhnfe/ui/screens/monitoring/kvs/AiViewModel.kt index 29e74eb0..abbf28b9 100644 --- a/app/src/main/java/com/example/mhnfe/ui/screens/monitoring/kvs/AiViewModel.kt +++ b/app/src/main/java/com/example/mhnfe/ui/screens/monitoring/kvs/AiViewModel.kt @@ -17,12 +17,11 @@ import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.launch import org.opencv.core.Mat import org.opencv.core.Rect -import javax.inject.Inject +import java.io.ByteArrayOutputStream import java.text.SimpleDateFormat import java.util.Date import java.util.Locale -import java.io.ByteArrayOutputStream - +import javax.inject.Inject @HiltViewModel @@ -34,10 +33,13 @@ class AiViewModel @Inject constructor( private val motionDetector = MotionDetector() private val yoloDetectionManager = YoloDetectionManager(context) private var lastEventTime: Long = 0 // 마지막 이벤트 발생 시간 기록 - private val eventDelayMillis = 500L // event data to iot 딜레이 시간 + private val eventDelayMillis = 6000L // event data to iot 딜레이 시간 private val handleDetection = HandleDetection() - fun processFrame(bitmap: Bitmap?, onResult: (Int, String, String, String) -> Unit) { + fun processFrame( + bitmap: Bitmap?, + onResult: (Int, String, Float, List>) -> Unit + ) { viewModelScope.launch { try { bitmap?.let { bmp -> @@ -49,7 +51,8 @@ class AiViewModel @Inject constructor( // Yolo 실행 및 콜백 처리 yoloDetectionManager.detect(bmp) { boundingBoxes, _ -> - val filteredBoxes = handleDetection.handleDetectionResults(boundingBoxes) + val filteredBoxes = + handleDetection.handleDetectionResults(boundingBoxes) if (filteredBoxes.isNotEmpty()) { val imageResult = createImage(bmp) val imageName = imageResult.first // 이미지 이름 @@ -81,30 +84,24 @@ class AiViewModel @Inject constructor( } val trackingId = DetectionManager.getNextTrackingId() - // BoundingBoxUtil을 사용하여 JSON 데이터 생성 - val coordinatesJson = - BoundingBoxUtils.generateCoordinatesJson( - boundingBoxes - ) - val objectNameJson = - BoundingBoxUtils.generateObjectNameJson( - boundingBoxes - ) - val confidenceJson = - BoundingBoxUtils.generateConfidenceJson( - boundingBoxes - ) + + // BoundingBoxUtils를 사용하여 데이터 추출 + val (objectType, confidence) = BoundingBoxUtils.getTypeAndConfidence( + boundingBoxes + ) + val coordinates = BoundingBoxUtils.getCoordinates(boundingBoxes) onResult( trackingId, - coordinatesJson, - objectNameJson, - confidenceJson + objectType, + confidence, + coordinates ) - }catch (e: Exception) { + } catch (e: Exception) { Log.e(tag, "Image upload failed", e) } } + } } } @@ -118,6 +115,7 @@ class AiViewModel @Inject constructor( } } } + private fun createImage(bitmap: Bitmap, quality: Int = 80): Pair { // 현재 시간을 기반으로 동적 이미지 이름 생성 val timestamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date()) diff --git a/app/src/main/java/com/example/mhnfe/ui/screens/monitoring/kvs/WebRTCScreen.kt b/app/src/main/java/com/example/mhnfe/ui/screens/monitoring/kvs/WebRTCScreen.kt index e9ea5bec..31e6c388 100644 --- a/app/src/main/java/com/example/mhnfe/ui/screens/monitoring/kvs/WebRTCScreen.kt +++ b/app/src/main/java/com/example/mhnfe/ui/screens/monitoring/kvs/WebRTCScreen.kt @@ -92,7 +92,7 @@ fun WebRtcScreen( LaunchedEffect(Unit) { var groupId = 0 mainViewModel.getCctvAccessToken { cctvAccessToken -> - if(cctvAccessToken != "") { + if (cctvAccessToken != "") { mainViewModel.fetchCctvId( onSuccess = { cctvInfo -> Log.d( @@ -299,12 +299,12 @@ fun WebRtcScreen( if (!isCameraSwitching) { bitmap?.let { withContext(Dispatchers.Default) { - aiViewModel.processFrame(it) { trackingId, coordinatesJson, objectName, confidence -> + aiViewModel.processFrame(it) { trackingId, objectType, confidence, coordinates -> mqttViewModel.eventTopic( trackingId, - objectName, - coordinatesJson, - confidence + objectType, + confidence, + coordinates ) } } diff --git a/app/src/main/java/com/example/mhnfe/ui/screens/mypage/ProfileScreen.kt b/app/src/main/java/com/example/mhnfe/ui/screens/mypage/ProfileScreen.kt index 5a94f585..2fb743b1 100644 --- a/app/src/main/java/com/example/mhnfe/ui/screens/mypage/ProfileScreen.kt +++ b/app/src/main/java/com/example/mhnfe/ui/screens/mypage/ProfileScreen.kt @@ -103,13 +103,13 @@ fun ProfileScreen( MainTopBar( text = "마이페이지", onImageClick = { - try{ + try { authStateViewModel.logout() - mainNavController.navigate(NavRoutes.Auth.Main.route){ + mainNavController.navigate(NavRoutes.Auth.Main.route) { popUpTo(NavRoutes.Main.route) { inclusive = true } } } catch (e: Exception) { - mainNavController.navigate(NavRoutes.Auth.Main.route){ + mainNavController.navigate(NavRoutes.Auth.Main.route) { popUpTo(NavRoutes.Main.route) { inclusive = true } } } @@ -181,7 +181,9 @@ fun ProfileScreen( verticalAlignment = Alignment.CenterVertically ) { Row( - modifier = modifier.wrapContentWidth().wrapContentHeight(), + modifier = modifier + .wrapContentWidth() + .wrapContentHeight(), horizontalArrangement = Arrangement.spacedBy(30.dp , alignment = Alignment.Start), verticalAlignment = Alignment.CenterVertically ) { diff --git a/app/src/main/java/com/example/mhnfe/ui/screens/report/ReportComponents.kt b/app/src/main/java/com/example/mhnfe/ui/screens/report/ReportComponents.kt index 7e734686..5f7f2aaf 100644 --- a/app/src/main/java/com/example/mhnfe/ui/screens/report/ReportComponents.kt +++ b/app/src/main/java/com/example/mhnfe/ui/screens/report/ReportComponents.kt @@ -54,7 +54,7 @@ fun ReportItemCard( ) { Card( modifier = modifier - .size(340.dp,200.dp) + .size(340.dp, 200.dp) .clickable(onClick = onClick), colors = CardDefaults.cardColors( containerColor = mainBlack @@ -177,7 +177,10 @@ private fun WeekDayHeader( modifier: Modifier = Modifier ) { Row( - modifier = modifier.padding(vertical = 23.dp).fillMaxWidth().wrapContentHeight(), + modifier = modifier + .padding(vertical = 23.dp) + .fillMaxWidth() + .wrapContentHeight(), horizontalArrangement = Arrangement.SpaceEvenly, verticalAlignment = Alignment.CenterVertically ) { @@ -224,7 +227,9 @@ private fun CalendarGrid( LazyVerticalGrid( columns = GridCells.Fixed(7), - modifier = modifier.fillMaxWidth().wrapContentHeight(), + modifier = modifier + .fillMaxWidth() + .wrapContentHeight(), horizontalArrangement = Arrangement.Center, verticalArrangement = Arrangement.spacedBy(5.dp, alignment = Alignment.CenterVertically), userScrollEnabled = false diff --git a/app/src/main/java/com/example/mhnfe/ui/screens/report/ReportScreen.kt b/app/src/main/java/com/example/mhnfe/ui/screens/report/ReportScreen.kt index 0979fcbd..6ec9e768 100644 --- a/app/src/main/java/com/example/mhnfe/ui/screens/report/ReportScreen.kt +++ b/app/src/main/java/com/example/mhnfe/ui/screens/report/ReportScreen.kt @@ -72,13 +72,13 @@ fun ReportDetailScreen( MainTopBar( text = "리포트", onImageClick = { - try{ + try { authStateViewModel.logout() - mainNavController.navigate(NavRoutes.Auth.Main.route){ + mainNavController.navigate(NavRoutes.Auth.Main.route) { popUpTo(NavRoutes.Main.route) { inclusive = true } } } catch (e: Exception) { - mainNavController.navigate(NavRoutes.Auth.Main.route){ + mainNavController.navigate(NavRoutes.Auth.Main.route) { popUpTo(NavRoutes.Main.route) { inclusive = true } } } @@ -103,7 +103,9 @@ fun ReportDetailScreen( } ) Column ( - modifier = modifier.fillMaxWidth().wrapContentHeight(), + modifier = modifier + .fillMaxWidth() + .wrapContentHeight(), horizontalAlignment = Alignment.Start, verticalArrangement = Arrangement.spacedBy(25.dp, alignment = Alignment.CenterVertically) ) { @@ -114,10 +116,12 @@ fun ReportDetailScreen( style = Typography.labelLarge, color = mainBlack ) - if(uiState.images.isEmpty()) { + if (uiState.images.isEmpty()) { Box( - modifier = modifier.fillMaxWidth().height(150.dp) - ){ + modifier = modifier + .fillMaxWidth() + .height(150.dp) + ) { Text( modifier = modifier.align(alignment = Alignment.Center), text = "기록이 없습니다😢", @@ -126,9 +130,11 @@ fun ReportDetailScreen( ) } - }else { + } else { LazyRow( - modifier = Modifier.fillMaxWidth().wrapContentHeight(), + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight(), horizontalArrangement = Arrangement.spacedBy( 8.dp, alignment = Alignment.Start @@ -233,6 +239,7 @@ fun ImageDialog( private fun Preview(){ val navController = rememberNavController() ReportDetailScreen( - mainNavController = navController) + mainNavController = navController + ) } \ No newline at end of file diff --git a/app/src/main/java/com/example/mhnfe/ui/screens/shared/AuthStateViewModel.kt b/app/src/main/java/com/example/mhnfe/ui/screens/shared/AuthStateViewModel.kt index b81e1be8..b31c9ee1 100644 --- a/app/src/main/java/com/example/mhnfe/ui/screens/shared/AuthStateViewModel.kt +++ b/app/src/main/java/com/example/mhnfe/ui/screens/shared/AuthStateViewModel.kt @@ -24,7 +24,7 @@ class AuthStateViewModel @Inject constructor( private val accessTokenDataStore: DataStore, private val refreshTokenDataStore: DataStore, private val sharedPreferences: SharedPreferences -): ViewModel() { +) : ViewModel() { private val _logoutResponse = MutableStateFlow(null) val logoutResponse: StateFlow = _logoutResponse