Skip to content

Commit

Permalink
Merge pull request #278 from mtkw0127/feature/create-search-screen
Browse files Browse the repository at this point in the history
Create search screen
  • Loading branch information
takahirom authored Sep 9, 2022
2 parents 47f0cf8 + 3425a5d commit 1c152b6
Show file tree
Hide file tree
Showing 6 changed files with 337 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ fun KaigiApp(
showNavigationIcon,
kaigiAppScaffoldState::onNavigationClick,
kaigiAppScaffoldState::onBackIconClick,
kaigiAppScaffoldState::onSearchClick,
kaigiAppScaffoldState::onTimeTableClick,
kaigiAppScaffoldState::onNavigateFloorMapClick,
)
Expand Down Expand Up @@ -183,6 +184,12 @@ class KaigiAppScaffoldState @OptIn(ExperimentalMaterial3Api::class) constructor(
)
}

fun onSearchClick() {
navController.navigate(
route = SessionsNavGraph.sessionSearchRoute()
)
}

fun onNavigateFloorMapClick() {
TODO("Floor map is not yet implemented.")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@ package io.github.droidkaigi.confsched2022.model

data class Filters(
val filterFavorite: Boolean = false,
val filterSession: Boolean = false,
val searchWord: String = ""
)
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
)

package io.github.droidkaigi.confsched2022.model

import io.github.droidkaigi.confsched2022.model.TimetableItem.Session
import kotlinx.collections.immutable.PersistentSet
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.persistentSetOf
Expand Down Expand Up @@ -45,6 +47,14 @@ data class Timetable(
favorites.contains(timetableItem.id)
}
}
if (filters.filterSession) {
timetableItems = timetableItems.filterIsInstance<Session>()
}
if (filters.searchWord.isNotBlank()) {
timetableItems = timetableItems.filter { timetableItem ->
timetableItem.title.currentLangTitle.contains(filters.searchWord)
}
}
return copy(timetableItems = TimetableItemList(timetableItems.toPersistentList()))
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,297 @@
package io.github.droidkaigi.confsched2022.feature.sessions

import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.material3.Badge
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import coil.compose.AsyncImage
import io.github.droidkaigi.confsched2022.designsystem.theme.KaigiScaffold
import io.github.droidkaigi.confsched2022.feature.sessions.SessionsUiModel.ScheduleState.Loaded
import io.github.droidkaigi.confsched2022.feature.sessions.SessionsUiModel.ScheduleState.Loading
import io.github.droidkaigi.confsched2022.model.DroidKaigi2022Day
import io.github.droidkaigi.confsched2022.model.DroidKaigiSchedule
import io.github.droidkaigi.confsched2022.model.Filters
import io.github.droidkaigi.confsched2022.model.TimetableItem.Session
import io.github.droidkaigi.confsched2022.model.TimetableItemWithFavorite
import io.github.droidkaigi.confsched2022.model.fake

@Composable
fun SearchRoot(
onItemClick: () -> Unit = {},
onBookMarkClick: () -> Unit = {}
) {
val viewModel = hiltViewModel<SessionsViewModel>()
val state: SessionsUiModel by viewModel.uiModel
SearchScreen(
uiModel = state,
onItemClick = onItemClick,
onBookMarkClick = onBookMarkClick,
)
}

@Composable
private fun SearchScreen(
uiModel: SessionsUiModel,
onItemClick: () -> Unit,
onBookMarkClick: () -> Unit,
) {
val searchWord = rememberSaveable { mutableStateOf("") }
KaigiScaffold(
topBar = {},
content = {
Column {
when (uiModel.scheduleState) {
is Loaded -> {
SearchTextField(searchWord.value) {
searchWord.value = it
}
SearchedItemListField(
schedule = uiModel.scheduleState.schedule,
searchWord = searchWord.value,
onItemClick = onItemClick,
onBookMarkClick = onBookMarkClick,
)
}
is Loading -> {
FullScreenLoading()
}
}
}
}
)
}

@OptIn(ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class)
@Composable
private fun SearchTextField(searchWord: String, onSearchWordChange: (String) -> Unit) {
val keyboardController = LocalSoftwareKeyboardController.current
Box(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 10.dp),
contentAlignment = Alignment.Center
) {
OutlinedTextField(
value = searchWord,
modifier = Modifier
.fillMaxWidth(fraction = 0.9F)
.background(color = MaterialTheme.colorScheme.surfaceVariant),
placeholder = { Text("Search Session") },
singleLine = true,
keyboardActions = KeyboardActions(onDone = { keyboardController?.hide() }),
leadingIcon = {
Icon(
painter = painterResource(id = R.drawable.ic_search),
contentDescription = "search_icon",
)
},
trailingIcon = {
Icon(
painter = painterResource(id = R.drawable.ic_delete),
contentDescription = "search_word_delete_icon",
modifier = Modifier.clickable {
onSearchWordChange("")
}
)
},
onValueChange = {
onSearchWordChange(it)
},
)
}
}

@OptIn(ExperimentalFoundationApi::class)
@Composable
private fun SearchedItemListField(
schedule: DroidKaigiSchedule,
searchWord: String,
onItemClick: () -> Unit,
onBookMarkClick: () -> Unit
) {
LazyColumn {
schedule.dayToTimetable.forEach { (dayToTimeTable, timeTable) ->
val sessions =
timeTable.filtered(Filters(filterSession = true, searchWord = searchWord)).contents
if (sessions.isEmpty()) return@forEach
stickyHeader {
SearchedHeader(day = dayToTimeTable)
}
items(sessions) {
SearchedItem(
timetableItemWithFavorite = it,
onItemClick = onItemClick,
onBookMarkClick = onBookMarkClick,
)
}
}
}
}

@Composable
private fun SearchedHeader(day: DroidKaigi2022Day) {
Column(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.background(MaterialTheme.colorScheme.onPrimary)
) {
Text(
text = day.name,
modifier = Modifier.padding(top = 10.dp, bottom = 10.dp, start = 10.dp)
)
}
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun SearchedItem(
timetableItemWithFavorite: TimetableItemWithFavorite,
onItemClick: () -> Unit,
onBookMarkClick: () -> Unit,
) {
var contentHeight = 100.dp
Box(
modifier = Modifier
.wrapContentHeight()
.heightIn(min = contentHeight)
.clickable { onItemClick.invoke() }
) {
Column(modifier = Modifier.padding(start = 15.dp, end = 10.dp, top = 15.dp)) {
Row {
val timeTable = timetableItemWithFavorite.timetableItem
if (timeTable is Session) {
Box(
modifier = Modifier
.width(60.dp)
.padding(top = 10.dp)
.fillMaxHeight(),
contentAlignment = Alignment.TopCenter,
) {
AsyncImage(
model = timeTable.speakers.firstOrNull()?.iconUrl,
modifier = Modifier
.size(60.dp)
.clip(CircleShape),
contentDescription = "Speaker Icon",
)
}

val bookMarkIconResource = if (timetableItemWithFavorite.isFavorited) {
R.drawable.ic_bookmark_filled
} else {
R.drawable.ic_bookmark
}

Column(
modifier = Modifier
.padding(start = 10.dp)
.fillMaxSize(),
verticalArrangement = Arrangement.SpaceBetween
) {
Text(
text = timeTable.title.currentLangTitle,
onTextLayout = {
if (it.lineCount > 2) {
contentHeight = 140.dp
}
}
)

Text("${timeTable.startsTimeString}")
Row(
modifier = Modifier
.height(40.dp)
.fillMaxWidth()
) {
Row(
modifier = Modifier
.weight(5f)
.height(40.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Badge {
Text(text = timeTable.category.title.currentLangTitle)
}
Badge(
modifier = Modifier.offset(x = 10.dp),
containerColor = MaterialTheme.colorScheme.onTertiary
) {
Text(text = timeTable.room.name.currentLangTitle)
}
}
Icon(
painter = painterResource(id = bookMarkIconResource),
contentDescription = "book_mark_icon",
modifier = Modifier
.size(30.dp)
.weight(1f)
.clickable { onBookMarkClick.invoke() }
)
}
}
}
}
}
}
}

@Composable
private fun FullScreenLoading() {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center,
) {
CircularProgressIndicator()
}
}

@Preview(showSystemUi = true)
@Composable
private fun SearchScreenPreview() {
val schedule = DroidKaigiSchedule.fake()
val scheduleState = Loaded(schedule)
val sessionUiModel = SessionsUiModel(scheduleState, true)
SearchScreen(
uiModel = sessionUiModel,
onItemClick = {},
onBookMarkClick = {},
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,15 @@ fun NavGraphBuilder.sessionsNavGraph(
showNavigationIcon: Boolean,
onNavigationIconClick: () -> Unit,
onBackIconClick: () -> Unit,
onSearchIconClick: () -> Unit,
onTimetableClick: (TimetableItemId) -> Unit,
onNavigateFloorMapClick: () -> Unit,
) {
composable(route = SessionsNavGraph.sessionRoute) {
SessionsScreenRoot(
showNavigationIcon = showNavigationIcon,
onNavigationIconClick = onNavigationIconClick,
onSearchClicked = { /*TODO: Implement later*/ },
onSearchClicked = onSearchIconClick,
onTimetableClick = onTimetableClick,
)
}
Expand All @@ -38,10 +39,19 @@ fun NavGraphBuilder.sessionsNavGraph(
onNavigateFloorMapClick = onNavigateFloorMapClick,
)
}

composable(
route = SessionsNavGraph.sessionSearchRoute(),
) {
SearchRoot()
}
}

object SessionsNavGraph {
const val sessionRoute = "sessions"
fun sessionDetailRoute(sessionId: String) =
"session/detail/$sessionId"

fun sessionSearchRoute() =
"session/search"
}
10 changes: 10 additions & 0 deletions feature-sessions/src/main/res/drawable/ic_delete.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="20"
android:viewportHeight="20">
<path
android:pathData="M10,0C4.47,0 0,4.47 0,10C0,15.53 4.47,20 10,20C15.53,20 20,15.53 20,10C20,4.47 15.53,0 10,0ZM10,18C5.59,18 2,14.41 2,10C2,5.59 5.59,2 10,2C14.41,2 18,5.59 18,10C18,14.41 14.41,18 10,18ZM10,8.59L13.59,5L15,6.41L11.41,10L15,13.59L13.59,15L10,11.41L6.41,15L5,13.59L8.59,10L5,6.41L6.41,5L10,8.59Z"
android:fillColor="#C0C9C1"
android:fillType="evenOdd"/>
</vector>

0 comments on commit 1c152b6

Please sign in to comment.