Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
@@ -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
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
Expand All @@ -30,7 +30,7 @@ internal fun PhotoResponse.toModel(): SearchResult {
if (filteredNames.isNotEmpty()) {
resultGroups.add(
PhotoGroup(
categoryName = "# $category",
categoryName = category,
photoNames = filteredNames,
),
)
Expand Down
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<PhotoDetailUiState> {

@CircuitInject(PhotoDetailScreen::class, ActivityRetainedComponent::class)
Expand Down Expand Up @@ -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))
}
}
Comment on lines +74 to +80
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Empty string fallback may cause invalid navigation.

When getFileName returns null, the code navigates to GraphDetailScreen with an empty entityName, which would result in an invalid URL (e.g., .../entityTripleGraph/dbName/). Consider showing an error or preventing navigation when the filename cannot be resolved.

🐛 Proposed fix
 is PhotoDetailUiEvent.OnGraphButtonClick -> {
     scope.launch {
         val fileName = galleryRepository.getFileName(screen.imageUriString.toUri())
-
-        navigator.goTo(GraphDetailScreen(entityName = fileName))
+        if (fileName != null) {
+            navigator.goTo(GraphDetailScreen(entityName = fileName))
+        } else {
+            toastMessage = "Unable to retrieve file information"
+        }
     }
 }
🤖 Prompt for AI Agents
In
@feature/detail/src/main/java/com/metasearch/android/feature/detail/photo/PhotoDetailPresenter.kt
around lines 74 - 80, PhotoDetailPresenter currently uses
galleryRepository.getFileName(...) and falls back to an empty string which
causes invalid navigation to GraphDetailScreen; change the scope.launch handler
for PhotoDetailUiEvent.OnGraphButtonClick to check the result of
galleryRepository.getFileName(screen.imageUriString.toUri()) and if it's null or
blank do not call navigator.goTo but instead emit an error UI event or call the
presenter/view error handler (e.g., show an error toast/state), otherwise pass
the non-empty fileName into GraphDetailScreen; update references in
PhotoDetailPresenter to use this guard around
navigator.goTo(GraphDetailScreen(...)).


is PhotoDetailUiEvent.OnFocusingSearchClick -> navigator.goTo(
FocusingSearchScreen(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ data class FocusingSearchScreen(

@Parcelize
data class GraphDetailScreen(
val imageUriString: String,
val entityName: String,
) : Screen

@Parcelize
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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))
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -177,6 +179,9 @@ private fun FocusingSearchUiContent(
onImageClick = { uri ->
state.eventSink(FocusingSearchUiEvent.OnImageClick(uri))
},
onMoreClick = { categoryName ->
state.eventSink(FocusingSearchUiEvent.OnMoreClick(categoryName))
},
)
}
}
Expand All @@ -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 = {},
),
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ sealed interface FocusingSearchUiEvent : CircuitUiEvent {
val circle: CircleModel,
) : FocusingSearchUiEvent

/**
* 이미지 검색 결과 더보기 버튼 클릭
*/
data class OnMoreClick(
val categoryName: String,
) : FocusingSearchUiEvent

/**
* 포커싱 검색 이벤트
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -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 = {},
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -27,6 +28,7 @@ internal fun SearchResultList(
modifier: Modifier = Modifier,
result: SearchResult,
onImageClick: (String) -> Unit,
onMoreClick: (String) -> Unit,
) {
LazyColumn(
modifier = modifier
Expand All @@ -35,29 +37,47 @@ 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)
},
contentScale = ContentScale.Crop,
)
}

if (hasMore) {
item {
MoreButton(
onClick = { onMoreClick(group.categoryName) },
)
}
}
}
}
}
Expand Down
1 change: 1 addition & 0 deletions feature/search/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
<string name="focusing_search_screen_toast_error_min_circles">원을 1개 이상 그려주세요.</string>
<string name="focusing_search_screen_toast_error_max_circles">원은 최대 3개까지만 그릴 수 있습니다.</string>
<string name="focusing_search_button_text">검색</string>
<string name="focusing_search_more_button">더보기+</string>
<string name="focusing_search_change_circle_color_button">색 변경</string>
<string name="focusing_search_circle_reset_button">다시 그리기</string>
</resources>
Loading