v0.2.7: Seamless Docked Bottom Navigation Bar Redesign
This commit is contained in:
@@ -11,11 +11,13 @@ import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.pager.HorizontalPager
|
||||
import androidx.compose.foundation.pager.rememberPagerState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.*
|
||||
import androidx.compose.material.icons.outlined.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
@@ -24,7 +26,6 @@ import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.hotdeal.alarm.presentation.components.PermissionDialog
|
||||
import com.hotdeal.alarm.presentation.components.elasticPressClickable
|
||||
import com.hotdeal.alarm.presentation.deallist.DealListScreen
|
||||
@@ -33,13 +34,12 @@ import com.hotdeal.alarm.ui.theme.CornerRadius
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
* One UI 9 Fluid Navigation Framework
|
||||
* 한 손 조작성을 극대화한 하단 캡슐 내비게이션 바 및 화면 전환
|
||||
* One UI 9 Docked Navigation Framework
|
||||
* 바닥까지 완벽하게 밀착된 일체형 네이티브 하단 바
|
||||
*/
|
||||
@Composable
|
||||
fun MainScreen(viewModel: MainViewModel) {
|
||||
val context = LocalContext.current
|
||||
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
|
||||
|
||||
var showPermissionDialog by remember { mutableStateOf(false) }
|
||||
val notificationPermissionLauncher = rememberLauncherForActivityResult(
|
||||
@@ -67,22 +67,23 @@ fun MainScreen(viewModel: MainViewModel) {
|
||||
|
||||
Scaffold(
|
||||
bottomBar = {
|
||||
// One UI 9 Fluid Bottom Navigation Bar
|
||||
Surface(
|
||||
// One UI 9 Docked Bottom Navigation Bar (바닥과 완벽 일체화)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.navigationBarsPadding(),
|
||||
color = MaterialTheme.colorScheme.surface,
|
||||
tonalElevation = 3.dp,
|
||||
border = androidx.compose.foundation.BorderStroke(
|
||||
0.8.dp,
|
||||
MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.25f)
|
||||
)
|
||||
.background(MaterialTheme.colorScheme.surface)
|
||||
) {
|
||||
NavigationBar(
|
||||
containerColor = Color.Transparent,
|
||||
tonalElevation = 0.dp,
|
||||
modifier = Modifier.height(64.dp)
|
||||
// 상단 초미세 헤어라인 구분선
|
||||
HorizontalDivider(
|
||||
thickness = 0.6.dp,
|
||||
color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.35f)
|
||||
)
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(54.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
val navItems = listOf(
|
||||
Triple(0, "핫딜 피드", Icons.Filled.LocalFireDepartment to Icons.Outlined.LocalFireDepartment),
|
||||
@@ -93,40 +94,66 @@ fun MainScreen(viewModel: MainViewModel) {
|
||||
val isSelected = pagerState.currentPage == pageIndex
|
||||
val (selectedIcon, unselectedIcon) = icons
|
||||
|
||||
NavigationBarItem(
|
||||
selected = isSelected,
|
||||
onClick = {
|
||||
val itemColor = if (isSelected) {
|
||||
MaterialTheme.colorScheme.primary
|
||||
} else {
|
||||
MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.65f)
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.fillMaxHeight()
|
||||
.elasticPressClickable {
|
||||
coroutineScope.launch {
|
||||
pagerState.animateScrollToPage(pageIndex)
|
||||
}
|
||||
},
|
||||
icon = {
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
// 선택 시 상단 미니 캡슐 인디케이터
|
||||
if (isSelected) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.width(20.dp)
|
||||
.height(3.dp)
|
||||
.clip(CornerRadius.shapePill)
|
||||
.background(MaterialTheme.colorScheme.primary)
|
||||
)
|
||||
Spacer(modifier = Modifier.height(3.dp))
|
||||
} else {
|
||||
Spacer(modifier = Modifier.height(6.dp))
|
||||
}
|
||||
|
||||
Icon(
|
||||
imageVector = if (isSelected) selectedIcon else unselectedIcon,
|
||||
contentDescription = label,
|
||||
modifier = Modifier.size(24.dp)
|
||||
tint = itemColor,
|
||||
modifier = Modifier.size(22.dp)
|
||||
)
|
||||
},
|
||||
label = {
|
||||
|
||||
Spacer(modifier = Modifier.height(2.dp))
|
||||
|
||||
Text(
|
||||
text = label,
|
||||
style = MaterialTheme.typography.labelSmall.copy(
|
||||
fontWeight = if (isSelected) FontWeight.Bold else FontWeight.Medium,
|
||||
fontSize = 11.sp
|
||||
)
|
||||
)
|
||||
},
|
||||
colors = NavigationBarItemDefaults.colors(
|
||||
selectedIconColor = MaterialTheme.colorScheme.primary,
|
||||
selectedTextColor = MaterialTheme.colorScheme.primary,
|
||||
unselectedIconColor = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.7f),
|
||||
unselectedTextColor = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.7f),
|
||||
indicatorColor = MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.6f)
|
||||
)
|
||||
fontSize = 11.sp,
|
||||
fontWeight = if (isSelected) FontWeight.Bold else FontWeight.Medium
|
||||
),
|
||||
color = itemColor
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 시스템 제스처 네비게이션 바 영역 패딩 (배경색은 일체형으로 유지)
|
||||
Spacer(modifier = Modifier.navigationBarsPadding())
|
||||
}
|
||||
},
|
||||
contentWindowInsets = WindowInsets(0, 0, 0, 0)
|
||||
) { paddingValues ->
|
||||
|
||||
Reference in New Issue
Block a user