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

feat : 앱 구조 설계 #55

Merged
merged 1 commit into from
Nov 5, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.example.mhnfe.data.signaling

import com.example.mhnfe.data.signaling.model.Event


interface Signaling {
fun onSdpOffer(event: Event)
fun onSdpAnswer(event: Event)
Expand Down
90 changes: 90 additions & 0 deletions app/src/main/java/com/example/mhnfe/data/signaling/model/Event.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package com.example.mhnfe.data.signaling.model

import android.util.Base64
import android.util.Log
import com.google.gson.JsonParser
import org.webrtc.IceCandidate
import java.nio.charset.StandardCharsets

/**
* A class representing the Event object. All response messages are asynchronously delivered
* to the recipient as events (for example, an SDP offer or SDP answer delivery).
*
* @see <a href="https://docs.aws.amazon.com/kinesisvideostreams-webrtc-dg/latest/devguide/kvswebrtc-websocket-apis-7.html">Event</a>
*/
class Event(
val senderClientId: String,
val messageType: String,
val messagePayload: String,
var statusCode: String? = null,
var body: String? = null
) {
companion object {
private const val TAG = "Event"

/**
* Attempts to convert an [ICE_CANDIDATE] [Event] into an [IceCandidate].
*
* @param event the ICE_CANDIDATE event to convert.
* @return an [IceCandidate] from the [Event]. `null` if the IceCandidate wasn't
* able to be constructed.
*/
fun parseIceCandidate(event: Event?): IceCandidate? {
if (event == null || event.messageType.equals("ICE_CANDIDATE", ignoreCase = true).not()) {
Log.e(TAG, "$event is not an ICE_CANDIDATE type!")
return null
}

val decode = Base64.decode(event.messagePayload, Base64.DEFAULT)
val candidateString = String(decode, StandardCharsets.UTF_8)

if (candidateString == "null") {
Log.w(TAG, "Received null IceCandidate!")
return null
}

val jsonObject = JsonParser.parseString(candidateString).asJsonObject

val sdpMid = jsonObject["sdpMid"]?.asString?.removeSurrounding("\"") ?: ""

val sdpMLineIndex = jsonObject["sdpMLineIndex"]?.asInt ?: -1

// Ice Candidate needs one of these two to be present
if (sdpMid.isEmpty() && sdpMLineIndex == -1) {
return null
}

val candidate = jsonObject["candidate"]?.asString?.removeSurrounding("\"") ?: ""

return IceCandidate(sdpMid, if (sdpMLineIndex == -1) 0 else sdpMLineIndex, candidate)
}

fun parseSdpEvent(answerEvent: Event): String {
val message = String(Base64.decode(answerEvent.messagePayload.toByteArray(), Base64.DEFAULT))
val jsonObject = JsonParser.parseString(message).asJsonObject
val type = jsonObject["type"].asString

if (!type.equals("answer", ignoreCase = true)) {
Log.e(TAG, "Error in answer message")
}

val sdp = jsonObject["sdp"].asString
Log.d(TAG, "SDP answer received from master: $sdp")
return sdp
}

fun parseOfferEvent(offerEvent: Event): String {
val decodedPayload = String(Base64.decode(offerEvent.messagePayload, Base64.DEFAULT))

return JsonParser.parseString(decodedPayload)
.takeIf { it.isJsonObject }
?.asJsonObject
?.get("sdp")
?.asString ?: ""
}
}

override fun toString(): String {
return "Event(senderClientId='$senderClientId', messageType='$messageType', messagePayload='$messagePayload', statusCode='$statusCode', body='$body')"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.example.mhnfe.data.signaling.model

import android.util.Base64
import org.webrtc.SessionDescription

data class Message(
var action: String?,
var recipientClientId: String?,
var senderClientId: String?,
var messagePayload: String?
) {
constructor() : this(null, null, null, null)

// 두 번째 생성자는 팩토리 메서드로 변경
companion object {
fun createMessage(
action: String,
recipientClientId: String,
senderClientId: String,
messagePayload: String
) = Message(action, recipientClientId, senderClientId, messagePayload)

fun createAnswerMessage(
sessionDescription: SessionDescription,
master: Boolean,
recipientClientId: String?
): Message {
val description = sessionDescription.description
val answerPayload = "{\"type\":\"answer\",\"sdp\":\"${description.replace("\r\n", "\\r\\n")}\"}"
val encodedString = Base64.encodeToString(answerPayload.toByteArray(), Base64.URL_SAFE or Base64.NO_WRAP)

// SenderClientId should always be "" for master creating answer case
return createMessage("SDP_ANSWER", recipientClientId ?: "", "", encodedString)
}

fun createOfferMessage(
sessionDescription: SessionDescription,
clientId: String
): Message {
val description = sessionDescription.description
val offerPayload = "{\"type\":\"offer\",\"sdp\":\"${description.replace("\r\n", "\\r\\n")}\"}"
val encodedString = Base64.encodeToString(offerPayload.toByteArray(), Base64.URL_SAFE or Base64.NO_WRAP)

return createMessage("SDP_OFFER", "", clientId, encodedString)
}
}
}
109 changes: 34 additions & 75 deletions app/src/main/java/com/example/mhnfe/ui/bottombar/BottomBarScreen.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.example.mhnfe.ui.bottombar

import android.util.Log
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
Expand Down Expand Up @@ -33,6 +34,7 @@ import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import com.example.mhnfe.R
import com.example.mhnfe.data.model.UserType
import com.example.mhnfe.ui.navigation.NavRoutes
//import com.example.mhnfe.ui.screens.master.GroupScreen
//import com.example.mhnfe.ui.screens.master.HomeScreen
//import com.example.mhnfe.ui.screens.mypage.MyPageScreen
Expand All @@ -48,55 +50,6 @@ sealed class NavigationItem(var route: String, var icon: Int, var title: String)
data object MyPage : NavigationItem("myPage", R.drawable.mypage, "마이페이지")
}


@Composable
fun BottomBarScreen(
mainNavController: NavController,
userType: UserType // 유저 타입 전달 받음
) {
val bottomNavController = rememberNavController()
val navBackStackEntry by bottomNavController.currentBackStackEntryAsState()
val currentRoute = navBackStackEntry?.destination?.route
val showBottomBar = currentRoute in listOf(
NavigationItem.Monitoring.route,
NavigationItem.Report.route,
NavigationItem.MyPage.route
)

Scaffold(
bottomBar = {
if (showBottomBar) {
BottomNavigationBar(navController = bottomNavController)
}
}
) { innerPadding ->
NavHost(
navController = bottomNavController,
startDestination = NavigationItem.Monitoring.route,
modifier = Modifier.padding(innerPadding)
) {
composable(NavigationItem.Monitoring.route) {
// GroupScreen(
// userType = userType, // 전달받은 유저 타입 전달
// navController = mainNavController
// )
}
composable(NavigationItem.Report.route) {
// ReportScreen()
}
composable(NavigationItem.MyPage.route) {
// MyPageScreen()
}
}
}
}







@Composable
fun BottomNavigationBar(
modifier: Modifier = Modifier,
Expand All @@ -111,29 +64,49 @@ fun BottomNavigationBar(
verticalAlignment = Alignment.CenterVertically
) {
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentRoute = navBackStackEntry?.destination?.route
// val currentRoute = navBackStackEntry?.destination?.route

val items = listOf(
NavigationItem.Monitoring,
NavigationItem.Report,
NavigationItem.MyPage,
)

val currentRoute = navBackStackEntry?.destination?.route
Log.d("Navigation", "Current Route: $currentRoute")

items.forEach { item ->
val isSelected = when (item) {
is NavigationItem.Monitoring -> {
currentRoute == "group" // 실제 라우트 값과 매칭
}

is NavigationItem.Report -> {
currentRoute == "report_detail"
}

is NavigationItem.MyPage -> {
currentRoute == "myPage/profile"
}
}

// isSelected 상태 디버깅
Log.d("Navigation", "Item: ${item.title}, IsSelected: $isSelected")

Box(
modifier = modifier
.weight(1f)
.fillMaxHeight()
.background(
color = if (currentRoute == item.route) mainYellow else hoverYellow
color = if (isSelected) mainYellow else hoverYellow
)
.noRippleClickable {
// 네비게이션 로직 수정
navController.navigate(item.route) {
navController.graph.startDestinationRoute?.let { route ->
popUpTo(route) {
saveState = true
}
popUpTo(NavRoutes.Monitoring.Group.route) {
saveState = true
}
launchSingleTop = true
restoreState = true
}
},
Expand All @@ -144,12 +117,12 @@ fun BottomNavigationBar(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
) {
Icon(
modifier = modifier.size(30.dp),
painter = painterResource(id = item.icon),
contentDescription = item.title,
tint = mainBlack
)
Icon(
modifier = modifier.size(30.dp),
painter = painterResource(id = item.icon),
contentDescription = item.title,
tint = mainBlack
)
Text(
text = item.title,
style = Typography.labelSmall,
Expand All @@ -173,17 +146,3 @@ fun Modifier.noRippleClickable(
}
}


@Preview(showBackground = true)
@Composable
private fun BottomBarScreenPreview() {
val context = LocalContext.current
val navController = remember {
NavController(context)
}

Column {
BottomBarScreen(mainNavController = navController, userType = UserType.MASTER)
}
}

Loading