===== 목적 =====
코틀린의 Collection 기능을 이용하여 검색 기능을 만들 것이다.
===== 정렬 =====
List를 정렬하는 것은 SortedBy 함수를 이용한다. 이를 역으로 정렬하려면 뒤에 reversed를 넣으면 된다.
fun quoteList(sortMethod: QuoteSort) : List {
return when (sortMethod) {
QuoteSort.TitleASC -> QuoteData.quoteData.sortedBy { it.title }
QuoteSort.TitleDESC -> QuoteData.quoteData.sortedBy { it.title }.reversed()
QuoteSort.AuthorASC -> QuoteData.quoteData.sortedBy { it.author }
QuoteSort.AuthorDESC -> QuoteData.quoteData.sortedBy { it.author }.reversed()
}
===== 필터링 =====
==== 1. 전체 소스 코드 ====
fun quoteList(searchString : String, sortMethod: QuoteSort) : List {
if (searchString != "") {
return when (sortMethod) {
QuoteSort.TitleASC -> QuoteData.quoteData.filter { it.title.contains(searchString) || it.author.contains(searchString) || it.quote.first().contains(searchString) }.sortedBy { it.title }
QuoteSort.TitleDESC -> QuoteData.quoteData.filter { it.title.contains(searchString) || it.author.contains(searchString) || it.quote.first().contains(searchString)}.sortedBy { it.title }.reversed()
QuoteSort.AuthorASC -> QuoteData.quoteData.filter { it.title.contains(searchString) || it.author.contains(searchString) || it.quote.first().contains(searchString)}.sortedBy { it.author }
QuoteSort.AuthorDESC -> QuoteData.quoteData.filter { it.title.contains(searchString) || it.author.contains(searchString) || it.quote.first().contains(searchString)}.sortedBy { it.author }.reversed()
}
}else
{
return when (sortMethod) {
QuoteSort.TitleASC -> QuoteData.quoteData.sortedBy { it.title }
QuoteSort.TitleDESC -> QuoteData.quoteData.sortedBy { it.title }.reversed()
QuoteSort.AuthorASC -> QuoteData.quoteData.sortedBy { it.author }
QuoteSort.AuthorDESC -> QuoteData.quoteData.sortedBy { it.author }.reversed()
}
}
}
==== 2. 검색어가 있을 때에만 필터링 ====
검색어가 없으면 전체리스트를 보여주게 해야 한다.
따라서 searchString != "" 일 때에만 필터링을 하게 했따.
==== 3. 필터 ====
필터는 람다식을 이용하여 중괄호 내에 필터의 기준 값이 들어간다.
우리는 제목, 저자, 그리고 내용 중에 그 어느 곳에서든 검색어가 들어가 있으면 리스트에 포할시킬 것이다. 따라서 그 세개의 기준을 모두 or로 묶어야 한다.
it.title.contains(searchString) || it.author.contains(searchString) || it.quote.first().contains(searchString)
==== 4. 일부 포함으로 변경 ====
필터는 완전일치를 사용할 수도 있다. 이를테면 다음과 같다.
QuoteData.quoteData.filter { it.title == searchString}
그런데 위와 같이 완전 일치를 사용하면 검색의 목적이 훼손된다. 검색이란 일부 단어만으로 쉽게 내가 찾고자 하는 목록이 나와야 하기 떄문이다. 따라서 일부만 포한하더라도 필터링이 되어야 한다.
스트링을 다루는 함수 중 일부포함은 contains함수이다.
따라서 다음과 같이 일부를 포함하는 문자를 즉각 필터링 하게 코드를 짤 수 있다.
QuoteData.quoteData.filter { it.title.contains(searchString) }
==== 5. 대소문자 구분 없이 비교하기 ====
=== 가. 한쪽으로 통일해 주는 방법 ===
다음과 같이 소문자로 통일해주면 대소문자 구분 없이 비교가 가능하다.
it.title.lowercase().contains(searchString.lowercase())
=== 나. ignoreCase 옵션 ===
과거에는 contains함수에 ignoreCase 옵션이 없었다. 그런데 최신 kotlin문법은 이게 가능하다.
따라서 다음과 같이 코드가 가능하다.
it.title.contains(searchString, ignoreCase = true)
===== 컴포즈에서 불러오기 =====
==== 1. 검색 창 만들기 ====
다음과 같이 검색창을 만들고 리스를 불러오면 된다.
var searchString by remember { mutableStateOf("") }
Row(modifier = Modifier
.fillMaxWidth()
.padding(6.dp), horizontalArrangement = Arrangement.SpaceEvenly) {
OutlinedTextField(
value = searchString,
onValueChange = { searchString = it },
label = { Text(text = "검색", style = MaterialTheme.typography.titleMedium ) },
trailingIcon = {
IconButton(onClick = {searchString = "" }) {
Icon(
imageVector = Icons.Default.Search,
contentDescription = "Select Drop Down"
)
}
},
textStyle = TextStyle(fontFamily = fontkjcMyungjo, fontSize = 14.sp, fontWeight = FontWeight.SemiBold, fontStyle = FontStyle.Normal),
keyboardOptions = KeyboardOptions.Default,
)
}
viewModel.quoteList(searchString, sortMethod).forEach() { item ->
DocuItemCard(item = item)
}
==== 2. 검색결과를 하이라이트 하게 해주는 코드 만들기 ====
다음과 같이 특정 문구를 하이라이트 하게 해주는 함수를 만들었다.
// 검색 결과 하이라이트
fun highlightContainedText(inputText: String, divider : String) : AnnotatedString
{
val originalString : AnnotatedString = buildAnnotatedString { append(inputText) }
if (inputText.contains(divider, ignoreCase = true)) {
val listOfString = inputText.split(divider, ignoreCase = true)
val formattedString = buildAnnotatedString {
for (i : Int in 0 until (listOfString.size * 2) -1)
{
if (i % 2 == 0) {
append(listOfString[i/2])
}else
{
withStyle(SpanStyle(color = Color.DarkGray, background = Color.Yellow)) { append(divider)}
}
}
}
return formattedString
}
return originalString
}
==== 3. 하이라이트를 포함한 리스트 아이템 소스 ====
// 연락처 목록을 보여주는 컴퍼저블
@Composable
fun DocuItemCard(item : QuoteText, searchString : String) {
val scope = rememberCoroutineScope()
Row (modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
.background(MaterialTheme.colorScheme.primary, RoundedCornerShape(16.dp))
.padding(10.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Column (modifier = Modifier
.weight(0.8f)
.padding(horizontal = 16.dp), verticalArrangement = Arrangement.SpaceBetween){
Text(text = highlightContainedText(item.title, searchString), color = MaterialTheme.colorScheme.primaryContainer, style = MaterialTheme.typography.displayMedium, modifier = Modifier.padding(vertical = 4.dp), overflow = TextOverflow.Ellipsis)
// Text(text = item.title, color = MaterialTheme.colorScheme.primaryContainer, style = MaterialTheme.typography.displayMedium, modifier = Modifier.padding(vertical = 4.dp), overflow = TextOverflow.Ellipsis)
Text(text = "by ${highlightContainedText(item.author, searchString)}" + " ", color = MaterialTheme.colorScheme.primaryContainer, textAlign = TextAlign.End, style = MaterialTheme.typography.bodyMedium, modifier = Modifier
.fillMaxWidth()
.padding(vertical = 4.dp), overflow = TextOverflow.Ellipsis)
Text(text = highlightContainedText(item.quote.first(), searchString), style = MaterialTheme.typography.bodySmall, overflow = TextOverflow.Ellipsis)
}
IconButton(onClick = {
// 스크린 라우팅하기
scope.launch { ScreenRouter.navigateTo(Screen.SpeechToText, item) }
}) {
Icon(Icons.Default.Edit, contentDescription = "${item.title} 연습하기", tint = Color.White)
}
}
}