diff --git a/core/common/src/main/java/com/metasearch/android/core/common/extensions/ModifierExt.kt b/core/common/src/main/java/com/metasearch/android/core/common/extensions/ModifierExt.kt index 15fac94..aeaf8aa 100644 --- a/core/common/src/main/java/com/metasearch/android/core/common/extensions/ModifierExt.kt +++ b/core/common/src/main/java/com/metasearch/android/core/common/extensions/ModifierExt.kt @@ -1,7 +1,23 @@ package com.metasearch.android.core.common.extensions +import androidx.compose.foundation.background import androidx.compose.foundation.clickable +import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Color.Companion.LightGray +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.platform.LocalInspectionMode fun Modifier.clickableIfNotNull(onClick: (() -> Unit)?): Modifier = if (onClick != null) this.clickable(onClick = onClick) else this + +@Composable +fun Modifier.previewPlaceholder( + color: Color = LightGray, + shape: Shape? = null, +): Modifier = if (LocalInspectionMode.current) { + if (shape != null) background(color, shape) else background(color) +} else { + this +} diff --git a/core/data/api/src/main/java/com/metasearch/android/core/data/api/repository/GraphRepository.kt b/core/data/api/src/main/java/com/metasearch/android/core/data/api/repository/GraphRepository.kt index ad97680..c58659e 100644 --- a/core/data/api/src/main/java/com/metasearch/android/core/data/api/repository/GraphRepository.kt +++ b/core/data/api/src/main/java/com/metasearch/android/core/data/api/repository/GraphRepository.kt @@ -4,7 +4,7 @@ import android.net.Uri interface GraphRepository { suspend fun getFullGraphWebViewUrl(): String - suspend fun getDetailGraphWebViewUrl(imageUriString: String): String + suspend fun getDetailGraphWebViewUrl(entityName: String): String suspend fun getTripleData(photoName: String) diff --git a/core/data/impl/src/main/java/com/metasearch/android/core/data/impl/mapper/ResponseToModel.kt b/core/data/impl/src/main/java/com/metasearch/android/core/data/impl/mapper/ResponseToModel.kt index 75d1580..efeb297 100644 --- a/core/data/impl/src/main/java/com/metasearch/android/core/data/impl/mapper/ResponseToModel.kt +++ b/core/data/impl/src/main/java/com/metasearch/android/core/data/impl/mapper/ResponseToModel.kt @@ -19,7 +19,7 @@ internal fun PhotoResponse.toModel(): SearchResult { if (commonList.isNotEmpty()) { val relatedCategories = individualMap.filter { entry -> entry.value.any { it in commonList } - }.keys.joinToString(" ") { "#$it" } + }.keys.joinToString(", ") resultGroups.add(PhotoGroup(categoryName = relatedCategories, photoNames = commonList)) } @@ -30,7 +30,7 @@ internal fun PhotoResponse.toModel(): SearchResult { if (filteredNames.isNotEmpty()) { resultGroups.add( PhotoGroup( - categoryName = "# $category", + categoryName = category, photoNames = filteredNames, ), ) diff --git a/core/data/impl/src/main/java/com/metasearch/android/core/data/impl/repository/GraphRepositoryImpl.kt b/core/data/impl/src/main/java/com/metasearch/android/core/data/impl/repository/GraphRepositoryImpl.kt index fd7a3a8..54ac38e 100644 --- a/core/data/impl/src/main/java/com/metasearch/android/core/data/impl/repository/GraphRepositoryImpl.kt +++ b/core/data/impl/src/main/java/com/metasearch/android/core/data/impl/repository/GraphRepositoryImpl.kt @@ -1,12 +1,12 @@ package com.metasearch.android.core.data.impl.repository import android.net.Uri -import androidx.core.net.toUri import com.metasearch.android.core.data.api.repository.DatabaseNameRepository import com.metasearch.android.core.data.api.repository.GalleryRepository import com.metasearch.android.core.data.api.repository.GraphRepository import com.metasearch.android.core.network.BuildConfig import com.metasearch.android.core.network.service.WebService +import java.net.URLEncoder import javax.inject.Inject import javax.inject.Singleton @@ -24,13 +24,11 @@ internal class GraphRepositoryImpl @Inject constructor( return "$webServerBaseUrl/graph/$dbName" } - override suspend fun getDetailGraphWebViewUrl(imageUriString: String): String { + override suspend fun getDetailGraphWebViewUrl(entityName: String): String { val dbName = databaseNameRepository.getPersistentDeviceDatabaseName() - val uri = imageUriString.toUri() + val encodedName = URLEncoder.encode(entityName, "UTF-8") - val fileName = galleryRepository.getFileName(uri) ?: "" - - return "$webServerBaseUrl/entityTripleGraph/$dbName/$fileName" + return "$webServerBaseUrl/entityTripleGraph/$dbName/$encodedName" } override suspend fun getTripleData(photoName: String) { diff --git a/feature/detail/src/main/java/com/metasearch/android/feature/detail/graph/GraphDetailPresenter.kt b/feature/detail/src/main/java/com/metasearch/android/feature/detail/graph/GraphDetailPresenter.kt index d5497c8..524d676 100644 --- a/feature/detail/src/main/java/com/metasearch/android/feature/detail/graph/GraphDetailPresenter.kt +++ b/feature/detail/src/main/java/com/metasearch/android/feature/detail/graph/GraphDetailPresenter.kt @@ -45,7 +45,7 @@ class GraphDetailPresenter @AssistedInject constructor( val maxImages = 10 LaunchedEffect(Unit) { - webViewUrl = graphRepository.getDetailGraphWebViewUrl(screen.imageUriString) + webViewUrl = graphRepository.getDetailGraphWebViewUrl(screen.entityName) } fun handleEvent(event: GraphDetailUiEvent) { diff --git a/feature/detail/src/main/java/com/metasearch/android/feature/detail/photo/PhotoDetailPresenter.kt b/feature/detail/src/main/java/com/metasearch/android/feature/detail/photo/PhotoDetailPresenter.kt index 84d3f92..5d6b2c7 100644 --- a/feature/detail/src/main/java/com/metasearch/android/feature/detail/photo/PhotoDetailPresenter.kt +++ b/feature/detail/src/main/java/com/metasearch/android/feature/detail/photo/PhotoDetailPresenter.kt @@ -6,7 +6,9 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue +import androidx.core.net.toUri import com.metasearch.android.core.common.utils.handleException +import com.metasearch.android.core.data.api.repository.GalleryRepository import com.metasearch.android.core.data.api.repository.ImageAnalysisRepository import com.metasearch.android.feature.screens.FocusingSearchScreen import com.metasearch.android.feature.screens.GraphDetailScreen @@ -24,6 +26,7 @@ class PhotoDetailPresenter @AssistedInject constructor( @Assisted private val navigator: Navigator, @Assisted private val screen: PhotoDetailScreen, private val imageAnalysisRepository: ImageAnalysisRepository, + private val galleryRepository: GalleryRepository, ) : Presenter { @CircuitInject(PhotoDetailScreen::class, ActivityRetainedComponent::class) @@ -68,11 +71,13 @@ class PhotoDetailPresenter @AssistedInject constructor( } } - is PhotoDetailUiEvent.OnGraphButtonClick -> navigator.goTo( - GraphDetailScreen( - imageUriString = screen.imageUriString, - ), - ) + is PhotoDetailUiEvent.OnGraphButtonClick -> { + scope.launch { + val fileName = galleryRepository.getFileName(screen.imageUriString.toUri()) ?: "" + + navigator.goTo(GraphDetailScreen(entityName = fileName)) + } + } is PhotoDetailUiEvent.OnFocusingSearchClick -> navigator.goTo( FocusingSearchScreen( diff --git a/feature/screens/src/main/java/com/metasearch/android/feature/screens/Screens.kt b/feature/screens/src/main/java/com/metasearch/android/feature/screens/Screens.kt index 3398ad3..7dfc7c9 100644 --- a/feature/screens/src/main/java/com/metasearch/android/feature/screens/Screens.kt +++ b/feature/screens/src/main/java/com/metasearch/android/feature/screens/Screens.kt @@ -35,7 +35,7 @@ data class FocusingSearchScreen( @Parcelize data class GraphDetailScreen( - val imageUriString: String, + val entityName: String, ) : Screen @Parcelize diff --git a/feature/search/src/main/java/com/metasearch/android/feature/search/focusing/FocusingSearchPresenter.kt b/feature/search/src/main/java/com/metasearch/android/feature/search/focusing/FocusingSearchPresenter.kt index 688a4c7..3b327f0 100644 --- a/feature/search/src/main/java/com/metasearch/android/feature/search/focusing/FocusingSearchPresenter.kt +++ b/feature/search/src/main/java/com/metasearch/android/feature/search/focusing/FocusingSearchPresenter.kt @@ -15,6 +15,7 @@ import com.metasearch.android.core.data.api.repository.SearchRepository import com.metasearch.android.core.model.CircleModel import com.metasearch.android.core.model.SearchResult import com.metasearch.android.feature.screens.FocusingSearchScreen +import com.metasearch.android.feature.screens.GraphDetailScreen import com.metasearch.android.feature.screens.PhotoDetailScreen import com.metasearch.android.feature.search.R import com.slack.circuit.codegen.annotations.CircuitInject @@ -119,6 +120,8 @@ class FocusingSearchPresenter @AssistedInject constructor( FocusingSearchUiEvent.HideToast -> toastMessage = null is FocusingSearchUiEvent.ShowToast -> toastMessage = event.message + + is FocusingSearchUiEvent.OnMoreClick -> navigator.goTo(GraphDetailScreen(event.categoryName)) } } diff --git a/feature/search/src/main/java/com/metasearch/android/feature/search/focusing/FocusingSearchUi.kt b/feature/search/src/main/java/com/metasearch/android/feature/search/focusing/FocusingSearchUi.kt index 07f0030..7f2cee9 100644 --- a/feature/search/src/main/java/com/metasearch/android/feature/search/focusing/FocusingSearchUi.kt +++ b/feature/search/src/main/java/com/metasearch/android/feature/search/focusing/FocusingSearchUi.kt @@ -29,6 +29,8 @@ import com.metasearch.android.core.designsystem.component.MetaSearchToast import com.metasearch.android.core.designsystem.theme.MetaSearchTheme import com.metasearch.android.core.designsystem.theme.White import com.metasearch.android.core.model.CircleModel +import com.metasearch.android.core.model.PhotoGroup +import com.metasearch.android.core.model.SearchResult import com.metasearch.android.core.ui.MetaSearchScaffold import com.metasearch.android.core.ui.component.MetaSearchLoadingIndicator import com.metasearch.android.feature.screens.FocusingSearchScreen @@ -177,6 +179,9 @@ private fun FocusingSearchUiContent( onImageClick = { uri -> state.eventSink(FocusingSearchUiEvent.OnImageClick(uri)) }, + onMoreClick = { categoryName -> + state.eventSink(FocusingSearchUiEvent.OnMoreClick(categoryName)) + }, ) } } @@ -185,10 +190,27 @@ private fun FocusingSearchUiContent( @DevicePreview @Composable private fun FocusingSearchUiPreview() { + val fakeGroups = listOf( + PhotoGroup( + categoryName = "# 고양이 # 노트북", + photoNames = listOf("https://picsum.photos/200", "https://picsum.photos/201", "https://picsum.photos/202", "https://picsum.photos/203"), + ), + PhotoGroup( + categoryName = "# 고양이", + photoNames = listOf("https://picsum.photos/200", "https://picsum.photos/201", "https://picsum.photos/202", "https://picsum.photos/203"), + ), + PhotoGroup( + categoryName = "# 노트북", + photoNames = listOf("https://picsum.photos/204", "https://picsum.photos/205"), + ), + ) + val fakeSearchResult = SearchResult(groups = fakeGroups) + MetaSearchTheme { FocusingSearchUi( state = FocusingSearchUiState( imageUriString = "", + searchResult = fakeSearchResult, eventSink = {}, ), ) diff --git a/feature/search/src/main/java/com/metasearch/android/feature/search/focusing/FocusingSearchUiState.kt b/feature/search/src/main/java/com/metasearch/android/feature/search/focusing/FocusingSearchUiState.kt index b59e876..5f969d7 100644 --- a/feature/search/src/main/java/com/metasearch/android/feature/search/focusing/FocusingSearchUiState.kt +++ b/feature/search/src/main/java/com/metasearch/android/feature/search/focusing/FocusingSearchUiState.kt @@ -22,6 +22,13 @@ sealed interface FocusingSearchUiEvent : CircuitUiEvent { val circle: CircleModel, ) : FocusingSearchUiEvent + /** + * 이미지 검색 결과 더보기 버튼 클릭 + */ + data class OnMoreClick( + val categoryName: String, + ) : FocusingSearchUiEvent + /** * 포커싱 검색 이벤트 */ diff --git a/feature/search/src/main/java/com/metasearch/android/feature/search/focusing/component/MoreButton.kt b/feature/search/src/main/java/com/metasearch/android/feature/search/focusing/component/MoreButton.kt new file mode 100644 index 0000000..033167e --- /dev/null +++ b/feature/search/src/main/java/com/metasearch/android/feature/search/focusing/component/MoreButton.kt @@ -0,0 +1,49 @@ +package com.metasearch.android.feature.search.focusing.component + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.metasearch.android.core.designsystem.annotation.ComponentPreview +import com.metasearch.android.core.designsystem.theme.LightPink +import com.metasearch.android.core.designsystem.theme.MetaSearchTheme +import com.metasearch.android.core.designsystem.theme.Neutral800 +import com.metasearch.android.feature.search.R + +@Composable +fun MoreButton( + onClick: () -> Unit, +) { + Box( + modifier = Modifier + .size(100.dp) + .clip(RoundedCornerShape(MetaSearchTheme.radius.sm)) + .background(Neutral800) + .clickable { onClick() }, + contentAlignment = Alignment.Center, + ) { + Text( + text = stringResource(R.string.focusing_search_more_button), + style = MetaSearchTheme.typography.labelMedium, + color = LightPink, + ) + } +} + +@ComponentPreview +@Composable +private fun MoreButtonPreview() { + MetaSearchTheme { + MoreButton( + onClick = {}, + ) + } +} diff --git a/feature/search/src/main/java/com/metasearch/android/feature/search/focusing/component/SearchResultList.kt b/feature/search/src/main/java/com/metasearch/android/feature/search/focusing/component/SearchResultList.kt index 02d3029..b6d252c 100644 --- a/feature/search/src/main/java/com/metasearch/android/feature/search/focusing/component/SearchResultList.kt +++ b/feature/search/src/main/java/com/metasearch/android/feature/search/focusing/component/SearchResultList.kt @@ -18,6 +18,7 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.unit.dp import coil3.compose.AsyncImage +import com.metasearch.android.core.common.extensions.previewPlaceholder import com.metasearch.android.core.designsystem.theme.MetaSearchTheme import com.metasearch.android.core.designsystem.theme.Neutral500 import com.metasearch.android.core.model.SearchResult @@ -27,6 +28,7 @@ internal fun SearchResultList( modifier: Modifier = Modifier, result: SearchResult, onImageClick: (String) -> Unit, + onMoreClick: (String) -> Unit, ) { LazyColumn( modifier = modifier @@ -35,22 +37,32 @@ internal fun SearchResultList( verticalArrangement = Arrangement.spacedBy(MetaSearchTheme.spacing.spacing4), contentPadding = PaddingValues(vertical = MetaSearchTheme.spacing.spacing4), ) { + val displayLimit = 3 + items(result.groups) { group -> Column { + val displayTags = group.categoryName + .split(", ") + .joinToString(" ") { "# $it" } + Text( - text = group.categoryName, + text = displayTags, color = Neutral500, modifier = Modifier.padding(bottom = MetaSearchTheme.spacing.spacing2), ) LazyRow( horizontalArrangement = Arrangement.spacedBy(MetaSearchTheme.spacing.spacing2), ) { - items(group.photoNames) { photoName -> + val itemsToShow = group.photoNames.take(displayLimit) + val hasMore = group.photoNames.size > displayLimit + + items(itemsToShow) { photoName -> AsyncImage( model = photoName, contentDescription = null, modifier = Modifier .size(100.dp) + .previewPlaceholder() .clip(RoundedCornerShape(MetaSearchTheme.radius.sm)) .clickable { onImageClick(photoName) @@ -58,6 +70,14 @@ internal fun SearchResultList( contentScale = ContentScale.Crop, ) } + + if (hasMore) { + item { + MoreButton( + onClick = { onMoreClick(group.categoryName) }, + ) + } + } } } } diff --git a/feature/search/src/main/res/values/strings.xml b/feature/search/src/main/res/values/strings.xml index c4341ef..65b48a2 100644 --- a/feature/search/src/main/res/values/strings.xml +++ b/feature/search/src/main/res/values/strings.xml @@ -11,6 +11,7 @@ 원을 1개 이상 그려주세요. 원은 최대 3개까지만 그릴 수 있습니다. 검색 + 더보기+ 색 변경 다시 그리기