Skip to content

Commit

Permalink
[Feature] #21 검색 화면 구현 (#23)
Browse files Browse the repository at this point in the history
* [BASE] #21 검색 화면 모듈 생성

* [FEATURE] #21 필터 영역 제외한 검색 화면 UI 구현 및 PokitInput에서 아이콘 클릭 이벤트를 수신할 수 있도록 변경

* [FEATURE] #21 필터 영역 및 달력 제외한 filter bottomsheet ui 구현

* [FEATURE] #21 달력 ui 구현

* [FEATURE] #21 달력 ui 수정

* [FEATURE] #21 검색 화면에 달력 UI 연결 및 viewModel 구현

* [UI] #21 PokitInput의 singleline 속성 추가 및 KeyboardAction와 focusRequester를 인자 추가 (기본값 존재)

* [CHORE] #21 세부 사항 수정
- 아래 문제점 수정
: "모아보기", "안읽음" 필터 클릭시 포킷 선택으로 bottomSheet가 호출되던 문제
: bottomSheet에서 저장하기 클릭시 필터가 전부 취소되어 있을 때 필터 버튼만 표시되던 문제
- 아래 기능 구현
: 키보드 엔터 클릭시 검색 결과 화면으로 이동
: 검색어 제거 클릭시 검색창 활성화
: 정렬 기준 변경 로직 추가

* [CHORE] #21 ktlint 적용
  • Loading branch information
l5x5l authored Jul 27, 2024
1 parent ba6b1d7 commit b86eadc
Show file tree
Hide file tree
Showing 34 changed files with 2,053 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
Expand All @@ -21,6 +24,7 @@ import pokitmons.pokit.core.ui.components.atom.input.attributes.PokitInputState
import pokitmons.pokit.core.ui.components.atom.input.subcomponents.container.PokitInputContainer
import pokitmons.pokit.core.ui.components.atom.input.subcomponents.icon.PokitInputIcon
import pokitmons.pokit.core.ui.theme.PokitTheme
import pokitmons.pokit.core.ui.utils.conditional

@Composable
fun PokitInput(
Expand All @@ -29,10 +33,13 @@ fun PokitInput(
onChangeText: (String) -> Unit,
icon: PokitInputIcon?,
modifier: Modifier = Modifier,
onClickIcon: (() -> Unit)? = null,
shape: PokitInputShape = PokitInputShape.RECTANGLE,
readOnly: Boolean = false,
enable: Boolean = true,
isError: Boolean = false,
keyboardActions: KeyboardActions = KeyboardActions.Default,
focusRequester: FocusRequester? = null,
) {
var focused by remember { mutableStateOf(false) }
val state = remember(focused, isError, readOnly, enable) {
Expand All @@ -52,10 +59,15 @@ fun PokitInput(
onValueChange = onChangeText,
textStyle = textStyle,
enabled = (enable && !readOnly),
maxLines = 1,
modifier = Modifier.onFocusChanged { focusState ->
focused = focusState.isFocused
},
singleLine = true,
modifier = Modifier
.onFocusChanged { focusState ->
focused = focusState.isFocused
}
.conditional(focusRequester != null) {
focusRequester(focusRequester!!)
},
keyboardActions = keyboardActions,
decorationBox = { innerTextField ->
PokitInputContainer(
iconPosition = icon?.position,
Expand All @@ -68,7 +80,7 @@ fun PokitInput(
}

if (icon?.position == PokitInputIconPosition.LEFT) {
PokitInputIcon(state = state, resourceId = icon.resourceId)
PokitInputIcon(state = state, resourceId = icon.resourceId, onClick = onClickIcon)
Box(modifier = Modifier.width(8.dp))
}

Expand All @@ -81,7 +93,7 @@ fun PokitInput(
}

if (icon?.position == PokitInputIconPosition.RIGHT) {
PokitInputIcon(state = state, resourceId = icon.resourceId)
PokitInputIcon(state = state, resourceId = icon.resourceId, onClick = onClickIcon)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package pokitmons.pokit.core.ui.components.atom.input.subcomponents.icon

import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Icon
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
Expand All @@ -14,14 +17,23 @@ import pokitmons.pokit.core.ui.theme.PokitTheme
internal fun PokitInputIcon(
state: PokitInputState,
resourceId: Int,
onClick: (() -> Unit)? = null,
) {
val iconColor = getColor(state = state)

Icon(
painter = painterResource(id = resourceId),
contentDescription = null,
tint = iconColor,
modifier = Modifier.size(24.dp)
modifier = Modifier.size(24.dp).then(
other = onClick?.let { method ->
Modifier.clickable(
indication = null,
interactionSource = remember { MutableInteractionSource() },
onClick = method
)
} ?: Modifier
)
)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package pokitmons.pokit.core.ui.utils

import androidx.compose.ui.Modifier

internal fun Modifier.conditional(condition: Boolean, modifier: Modifier.() -> Modifier): Modifier {
return if (condition) {
then(modifier(Modifier))
} else {
this
}
}
1 change: 1 addition & 0 deletions feature/search/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
64 changes: 64 additions & 0 deletions feature/search/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
plugins {
alias(libs.plugins.com.android.library)
alias(libs.plugins.org.jetbrains.kotlin.android)
}

android {
namespace = "pokitmons.pokit.search"
compileSdk = 34

defaultConfig {
minSdk = 24

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
}

buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
buildFeatures {
compose = true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.5.1"
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
}

dependencies {

implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.activity.compose)
implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.ui)
implementation(libs.androidx.ui.graphics)
implementation(libs.androidx.ui.tooling.preview)
implementation(libs.androidx.material3)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
androidTestImplementation(platform(libs.androidx.compose.bom))
androidTestImplementation(libs.androidx.ui.test.junit4)
debugImplementation(libs.androidx.ui.tooling)
debugImplementation(libs.androidx.ui.test.manifest)

implementation(libs.orbit.compose)
implementation(libs.orbit.core)
implementation(libs.orbit.viewmodel)

implementation(project(":core:ui"))
}
Empty file.
21 changes: 21 additions & 0 deletions feature/search/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package pokitmons.pokit.search

import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith

/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("pokitmons.pokit.search.test", appContext.packageName)
}
}
4 changes: 4 additions & 0 deletions feature/search/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

</manifest>
20 changes: 20 additions & 0 deletions feature/search/src/main/java/pokitmons/pokit/search/Preview.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package pokitmons.pokit.search

import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import pokitmons.pokit.core.ui.theme.PokitTheme

@Preview(showBackground = true)
@Composable
private fun Preview() {
PokitTheme {
Column(
modifier = Modifier.fillMaxSize()
) {
SearchScreen()
}
}
}
127 changes: 127 additions & 0 deletions feature/search/src/main/java/pokitmons/pokit/search/SearchScreen.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package pokitmons.pokit.search

import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material3.HorizontalDivider
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import pokitmons.pokit.core.ui.theme.PokitTheme
import pokitmons.pokit.search.components.filter.FilterArea
import pokitmons.pokit.search.components.filterbottomsheet.FilterBottomSheet
import pokitmons.pokit.search.components.recentsearchword.RecentSearchWord
import pokitmons.pokit.search.components.searchitemlist.SearchItemList
import pokitmons.pokit.search.components.toolbar.Toolbar
import pokitmons.pokit.search.model.Filter
import pokitmons.pokit.search.model.FilterType
import pokitmons.pokit.search.model.Link
import pokitmons.pokit.search.model.SearchScreenState
import pokitmons.pokit.search.model.SearchScreenStep

@Composable
fun SearchScreenContainer(
viewModel: SearchViewModel,
onBackPressed: () -> Unit,
) {
val state by viewModel.state.collectAsState()
val searchWord by viewModel.searchWord.collectAsState()
val linkList by viewModel.linkList.collectAsState()

SearchScreen(
state = state,
currentSearchWord = searchWord,
linkList = linkList,
onClickBack = onBackPressed,
inputSearchWord = viewModel::inputSearchWord,
onClickSearch = viewModel::applyCurrentSearchWord,
onClickRecentSearchWord = viewModel::applySearchWord,
onClickUseRecentSearchWord = viewModel::toggleUseRecentSearchWord,
onClickRemoveAllRecentSearchWord = viewModel::removeAllRecentSearchWord,
onClickRemoveRecentSearchWord = viewModel::removeRecentSearchWord,
onClickFilterSelect = viewModel::showFilterBottomSheet,
onClickFilterItem = viewModel::showFilterBottomSheetWithType,
hideBottomSheet = viewModel::hideFilterBottomSheet,
onClickFilterSave = viewModel::setFilter,
toggleSortOrder = viewModel::toggleSortOrder
)
}

@Composable
fun SearchScreen(
state: SearchScreenState = SearchScreenState(),
currentSearchWord: String = "",
linkList: List<Link> = emptyList(),
onClickBack: () -> Unit = {},
inputSearchWord: (String) -> Unit = {},
onClickSearch: () -> Unit = {},
onClickRecentSearchWord: (String) -> Unit = {},
onClickUseRecentSearchWord: () -> Unit = {},
onClickRemoveAllRecentSearchWord: () -> Unit = {},
onClickRemoveRecentSearchWord: (String) -> Unit = {},
onClickFilterSelect: () -> Unit = {},
onClickFilterItem: (FilterType) -> Unit = {},
hideBottomSheet: () -> Unit = {},
onClickFilterSave: (Filter) -> Unit = {},
toggleSortOrder: () -> Unit = {},
) {
Column(
modifier = Modifier.fillMaxSize()
) {
Toolbar(
onClickBack = onClickBack,
inputSearchWord = inputSearchWord,
currentSearchWord = currentSearchWord,
onClickSearch = onClickSearch,
onClickRemove = remember { { inputSearchWord("") } }
)

if (state.step == SearchScreenStep.INPUT) {
RecentSearchWord(
onClickRemoveAll = onClickRemoveAllRecentSearchWord,
onToggleAutoSave = onClickUseRecentSearchWord,
useAutoSave = state.useRecentSearchWord,
recentSearchWords = state.recentSearchWords,
onClickRemoveSearchWord = onClickRemoveRecentSearchWord,
onClickSearchWord = onClickRecentSearchWord
)
}

if (state.step == SearchScreenStep.RESULT) {
FilterArea(
filter = state.filter,
onClickFilter = onClickFilterSelect,
onClickBookmark = remember { { onClickFilterItem(FilterType.Collect) } },
onClickPokitName = remember { { onClickFilterItem(FilterType.Pokit) } },
onClickPeriod = remember { { onClickFilterItem(FilterType.Period) } }
)
}

HorizontalDivider(
thickness = 6.dp,
color = PokitTheme.colors.backgroundPrimary
)

if (state.step == SearchScreenStep.RESULT) {
SearchItemList(
modifier = Modifier
.fillMaxWidth()
.weight(1f),
onToggleSort = toggleSortOrder,
useRecentOrder = state.sortRecent,
links = linkList
)
}

FilterBottomSheet(
filter = state.filter ?: Filter(),
firstShowType = state.firstBottomSheetFilterType,
show = state.showFilterBottomSheet,
onDismissRequest = hideBottomSheet,
onSaveClilck = onClickFilterSave
)
}
}
Loading

0 comments on commit b86eadc

Please sign in to comment.