Skip to content

Commit

Permalink
Merge pull request #55 from IoTeaTime/feature/54-kan-127-navigation-s…
Browse files Browse the repository at this point in the history
…etting

feat : 앱 구조 설계
  • Loading branch information
kimmatches authored Nov 5, 2024
2 parents 6c8ea96 + 5c62989 commit 29ca1a4
Show file tree
Hide file tree
Showing 7 changed files with 373 additions and 141 deletions.
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

0 comments on commit 29ca1a4

Please sign in to comment.