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

[Feature] 버스 검색 결과 스크린 #463

Merged
merged 32 commits into from
Nov 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
9242040
add: BusSearchScreen
ThirFir Nov 6, 2024
a439239
add: Compose ContraintLayout dependency
ThirFir Nov 6, 2024
837833d
add: ConstraintLayout 적용한 버스 조회 뷰
ThirFir Nov 6, 2024
38e3498
add: 프리뷰 추가
ThirFir Nov 6, 2024
8aef5c0
add: 조회하기 버튼 뷰
ThirFir Nov 6, 2024
51af37a
add: 버튼 onClick 임시
ThirFir Nov 6, 2024
70c9369
add: 임시 검색 로직
ThirFir Nov 6, 2024
83d2672
add: Button enabled 상태
ThirFir Nov 6, 2024
e594b67
add: place 선택 바텀시트
ThirFir Nov 7, 2024
f2fa9a4
chore: 패키지 정리
ThirFir Nov 7, 2024
85370a1
chore: TODO 주석
ThirFir Nov 7, 2024
39f5845
chore: 오타
ThirFir Nov 7, 2024
f7836da
add: BusSearchResultScreen 밑바탕
ThirFir Nov 7, 2024
286de42
add: Picker 컴포넌트
ThirFir Nov 7, 2024
be023b7
refactor: 무한스크롤 아닐 때 동작 처리
ThirFir Nov 7, 2024
a336d5e
chore: Compose bom 버전 업
ThirFir Nov 7, 2024
bfab6f1
add: 상단 Row
ThirFir Nov 7, 2024
09c9dd1
refactor: LazyColumn으로 변경
ThirFir Nov 7, 2024
3d6e006
add: PickerState에 Index 추가
ThirFir Nov 7, 2024
69916d1
add: 출발 시간 선택 다이얼로그
ThirFir Nov 7, 2024
0de03cc
add: 시간 뷰모델 로직
ThirFir Nov 7, 2024
ae5edcc
add: 시간 선택 뷰 로직
ThirFir Nov 7, 2024
a9f6059
add: 메인 액티비티 버스 진입 뷰
ThirFir Nov 8, 2024
07b1b67
add: 메인에서 버스 feature로의 진입
ThirFir Nov 8, 2024
c76d284
fix: Swap 아이콘 변경
ThirFir Nov 8, 2024
1bf8c6f
Merge remote-tracking branch 'origin/develop' into feature/bus-search
ThirFir Nov 8, 2024
afaa686
add: 버스 검색 결과 아이템 뷰
ThirFir Nov 8, 2024
193fc28
chore: 임시 데이터 포맷 수정
ThirFir Nov 8, 2024
0ce706e
add: 프리뷰
ThirFir Nov 8, 2024
f1084bf
add: picker unselected textstyle
ThirFir Nov 12, 2024
12a7658
fix: 로깅
ThirFir Nov 12, 2024
1baaa6c
fix: MainActivity release/debug
ThirFir Nov 12, 2024
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
@@ -0,0 +1,122 @@
package `in`.koreatech.koin.core.designsystem.component.picker

import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.gestures.snapping.rememberSnapFlingBehavior
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import `in`.koreatech.koin.core.designsystem.theme.KoinTheme
import kotlinx.coroutines.flow.map

/**
* 아이템 피커
* @param items 아이템 리스트
* @param pickerState PickerState
* @param visibleItemsCount 보여질 아이템 수
* @param modifier Modifier
* @param infiniteScroll 무한 스크롤 여부
* @param startIndex 시작 인덱스
* @param contentPadding 아이템 내부 Padding
* @param selectedTextStyle 선택 아이템 텍스트 스타일
* @param unselectedTextStyle 선택되지 않은 아이템 텍스트 스타일
* @param selectedItemColor 선택 아이템 색상
* @param unselectedItemColor 선택되지 않은 아이템 색상
*/
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun KoinPicker(
items: List<String>,
pickerState: PickerState,
visibleItemsCount: Int,
modifier: Modifier = Modifier,
infiniteScroll: Boolean = true,
startIndex: Int = 0,
contentPadding: PaddingValues = PaddingValues(vertical = 2.dp),
selectedTextStyle: TextStyle = KoinTheme.typography.medium16,
unselectedTextStyle: TextStyle = KoinTheme.typography.medium16,
selectedItemColor: Color = KoinTheme.colors.primary500,
unselectedItemColor: Color = KoinTheme.colors.neutral500,
) {

val newItems = if (infiniteScroll) items
else List(visibleItemsCount / 2) { "" } + items + List(visibleItemsCount / 2) { "" }

val visibleItemsMiddle = visibleItemsCount / 2
val listScrollCount = if (infiniteScroll) Int.MAX_VALUE else newItems.size
val listScrollMiddle = listScrollCount / 2
val listStartIndex =
(listScrollMiddle - listScrollMiddle % newItems.size - visibleItemsMiddle + startIndex).coerceAtLeast(startIndex)

val listState = rememberLazyListState(initialFirstVisibleItemIndex = listStartIndex)
val flingBehavior = rememberSnapFlingBehavior(lazyListState = listState)

var selectedItemIndex by remember { mutableIntStateOf(startIndex) }

var itemHeight by remember { mutableStateOf(0.dp) }
val density = LocalDensity.current

LazyColumn(
state = listState,
flingBehavior = flingBehavior,
modifier = modifier.height(itemHeight * visibleItemsCount)
) {
items(listScrollCount) { index ->
Text(
text = newItems.getItem(index),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
style = if (selectedItemIndex == index) selectedTextStyle else unselectedTextStyle,
color = if (selectedItemIndex == index) selectedItemColor else unselectedItemColor,
modifier = Modifier
.fillMaxSize()
.onSizeChanged {
itemHeight = with(density) {
it.height.toDp()
}
}
.padding(contentPadding)
)
}
}

LaunchedEffect(listState) {
snapshotFlow {
listState.firstVisibleItemIndex
}.map { index ->
index + visibleItemsMiddle
}.collect { middle ->
selectedItemIndex = middle
pickerState.selectedItem = newItems.getItem(middle)
pickerState.selectedItemIndex = middle % newItems.size - if (infiniteScroll.not()) visibleItemsMiddle else 0
}
}
}

@Composable
fun rememberPickerState() = remember { PickerState() }

class PickerState internal constructor() {
var selectedItem by mutableStateOf("")
var selectedItemIndex by mutableIntStateOf(0)
}

private fun List<String>.getItem(index: Int) = this[index % this.size]
6 changes: 5 additions & 1 deletion feature/bus/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@

<application>
<activity
android:name="in.koreatech.bus.Bus2Activity"
android:name="in.koreatech.bus.BusTimetableActivity"
android:exported="false" />

<activity
android:name="in.koreatech.bus.BusSearchActivity"
android:exported="false" />
</application>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,19 @@ import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material3.MaterialTheme
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.ComposeView
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.navigation.compose.rememberNavController
import dagger.hilt.android.AndroidEntryPoint
import `in`.koreatech.bus.navigation.BusNavigation
import `in`.koreatech.bus.navigation.BusSearchNavigation
import `in`.koreatech.koin.core.designsystem.theme.KoinTheme
import `in`.koreatech.koin.feature.bus.R

@AndroidEntryPoint
class Bus2Activity : AppCompatActivity() {
class BusSearchActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
KoinTheme {
BusNavigation(
BusSearchNavigation(
modifier = Modifier.fillMaxSize()
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package `in`.koreatech.bus

import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.ui.Modifier
import dagger.hilt.android.AndroidEntryPoint
import `in`.koreatech.bus.navigation.BusTimetableNavigation
import `in`.koreatech.koin.core.designsystem.theme.KoinTheme

@AndroidEntryPoint
class BusTimetableActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
KoinTheme {
BusTimetableNavigation(
modifier = Modifier.fillMaxSize()
)
}
}
}
}
142 changes: 142 additions & 0 deletions feature/bus/src/main/java/in/koreatech/bus/MainEntryView.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package `in`.koreatech.bus

import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.rounded.KeyboardArrowRight
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.material3.ripple
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import `in`.koreatech.koin.core.designsystem.theme.KoinTheme
import `in`.koreatech.koin.feature.bus.R

@Composable
fun MainEntryView(
onShuttleTicketClicked: () -> Unit,
onTimetableCardClicked: () -> Unit,
onSearchCardClicked: () -> Unit,
modifier: Modifier = Modifier
) {
val unibusInteractionSource = remember { MutableInteractionSource() }
val timetableInteractionSource = remember { MutableInteractionSource() }
val searchInteractionSource = remember { MutableInteractionSource() }
Column(
modifier = modifier
) {
Row(
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = stringResource(R.string.bus),
style = KoinTheme.typography.bold18,
color = KoinTheme.colors.primary500
)
Spacer(modifier = Modifier.weight(1f))
Row(
modifier = Modifier
.clip(
RoundedCornerShape(8.dp)
)
.clickable(
interactionSource = unibusInteractionSource,
indication = ripple(bounded = false)
) {
onShuttleTicketClicked()
}
.padding(8.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
painter = painterResource(R.drawable.ic_qr),
contentDescription = stringResource(R.string.unibus_shortcut_content_description),
tint = KoinTheme.colors.primary500
)
Text(
modifier = Modifier.padding(start = 4.dp),
text = stringResource(R.string.shuttle_ticket),
style = KoinTheme.typography.regular14,
color = KoinTheme.colors.primary500
)
}
}

Row {
MainEntryCard(
title = stringResource(R.string.main_title_bus_timetable),
description = stringResource(R.string.shortcut),
modifier = Modifier
.padding(top = 20.dp)
.clip(RoundedCornerShape(8.dp))
.background(color = KoinTheme.colors.neutral50)
.clickable(
interactionSource = timetableInteractionSource,
indication = ripple(bounded = false)
) {
onTimetableCardClicked()
}.padding(horizontal = 16.dp)
.weight(1f)
)
Spacer(modifier = Modifier.width(16.dp))
MainEntryCard(
title = stringResource(R.string.main_title_bus_search),
description = stringResource(R.string.see),
modifier = Modifier
.padding(top = 20.dp)
.clip(RoundedCornerShape(8.dp))
.background(color = KoinTheme.colors.neutral50)
.clickable(
interactionSource = searchInteractionSource,
indication = ripple(bounded = false)
) {
onSearchCardClicked()
}.padding(horizontal = 16.dp)
.weight(1f)
)
}
}
}

@Composable
private fun MainEntryCard(
title: String,
description: String,
modifier: Modifier = Modifier,
) {
Row(
modifier = modifier,
verticalAlignment = Alignment.CenterVertically
) {
Column(
modifier = Modifier.padding(vertical = 12.dp)
) {
Text(
text = title,
style = KoinTheme.typography.medium14
)
Text(
text = description,
style = KoinTheme.typography.regular12
)
}
Spacer(modifier = Modifier.weight(1f))
Icon(
imageVector = Icons.AutoMirrored.Rounded.KeyboardArrowRight,
contentDescription = title
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package `in`.koreatech.bus.navigation

import androidx.compose.animation.EnterTransition
import androidx.compose.animation.ExitTransition
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import `in`.koreatech.bus.screen.search.composable.BusSearchResultScreen
import `in`.koreatech.bus.screen.search.composable.BusSearchScreen

@Composable
fun BusSearchNavigation(
modifier: Modifier = Modifier,
navController: NavHostController = rememberNavController(),
) {

NavHost(
modifier = modifier,
navController = navController,
startDestination = Routes.BusSearch,
enterTransition = {
EnterTransition.None
}, exitTransition = {
ExitTransition.None
}
) {

composable<Routes.BusSearch> {
BusSearchScreen(
modifier = Modifier.fillMaxSize(),
onNavigationIconClick = { navController.popBackStack() },
onSearchSuccess = {
navController.navigate(Routes.BusSearchResult)
}
)
}

composable<Routes.BusSearchResult> {
BusSearchResultScreen(
modifier = Modifier.fillMaxSize(),
onNavigationIconClick = { navController.popBackStack() }

)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import `in`.koreatech.bus.screen.search.composable.BusSearchScreen
import `in`.koreatech.bus.screen.timetable.composable.BusTimetableScreen

@Composable
fun BusNavigation(
fun BusTimetableNavigation(
modifier: Modifier = Modifier,
navController: NavHostController = rememberNavController(),
) {
Expand Down
Loading