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
@@ -0,0 +1,22 @@
package com.flint.core.common.extension

import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.ViewModel
import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavHostController

/**
* 공용 뷰모델을 불러오기 위한 함수
* */
@Composable
inline fun <reified T : ViewModel> NavBackStackEntry.sharedViewModel(
navController: NavHostController,
): T {
val navGraphRoute = destination.parent?.route ?: return hiltViewModel()
val parentEntry = remember(this) {
navController.getBackStackEntry(navGraphRoute)
}
return hiltViewModel(parentEntry)
Comment on lines +17 to +21
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find and examine the NavBackStackEntryExt.kt file
find . -name "NavBackStackEntryExt.kt" -type f

Repository: imflint/Flint-Android

Length of output: 140


🏁 Script executed:

#!/bin/bash
# Read the full content of the extension file to understand the complete context
if [ -f "app/src/main/java/com/flint/core/common/extension/NavBackStackEntryExt.kt" ]; then
    cat -n "app/src/main/java/com/flint/core/common/extension/NavBackStackEntryExt.kt"
else
    find . -name "NavBackStackEntryExt.kt" -type f -exec cat -n {} \;
fi

Repository: imflint/Flint-Android

Length of output: 899


🏁 Script executed:

#!/bin/bash
# Search for usages of sharedViewModel extension function
rg -n --type=kt 'sharedViewModel' -C 3

Repository: imflint/Flint-Android

Length of output: 91


🏁 Script executed:

#!/bin/bash
# Search for sharedViewModel usages without type filter
rg -n 'sharedViewModel' -C 3

Repository: imflint/Flint-Android

Length of output: 7825


🏁 Script executed:

#!/bin/bash
# Search for any try-catch or error handling around getBackStackEntry in the codebase
rg -n 'getBackStackEntry' -C 5

Repository: imflint/Flint-Android

Length of output: 1088


getBackStackEntry()IllegalArgumentException을 던질 수 있습니다.

navController.getBackStackEntry(navGraphRoute)는 해당 경로가 백 스택에 없을 경우 예외를 던집니다. 네비게이션 전환 중이나 백 스택 상태가 변경될 때 런타임 크래시가 발생할 수 있습니다. 현재 코드는 부모 경로가 null인 경우만 방어하고 있어 실제 크래시 시나리오를 방지하지 못합니다.

try-catch로 감싸거나 runCatching을 사용하여 예외를 처리하는 것을 권장합니다.

🤖 Prompt for AI Agents
In `@app/src/main/java/com/flint/core/common/extension/NavBackStackEntryExt.kt`
around lines 17 - 21, Wrap the call to
navController.getBackStackEntry(navGraphRoute) in a try-catch (or runCatching)
that handles IllegalArgumentException and falls back to returning
hiltViewModel() without a parent; specifically, protect the remember block that
initializes parentEntry (and the subsequent return hiltViewModel(parentEntry))
so that if getBackStackEntry throws or cannot find navGraphRoute you return
hiltViewModel() instead, using the symbols navController.getBackStackEntry,
navGraphRoute, parentEntry, and hiltViewModel to locate and update the code.

}
5 changes: 5 additions & 0 deletions app/src/main/java/com/flint/core/navigation/Route.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ interface Route {
val tempToken: String
) : Route

@Serializable
data class OnboardingGraph(
val tempToken: String
) : Route

@Serializable
data object OnboardingContent : Route

Expand Down
6 changes: 6 additions & 0 deletions app/src/main/java/com/flint/data/api/SearchApi.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.flint.data.api

import com.flint.data.dto.base.BaseResponse
import com.flint.data.dto.search.SearchBookmarkedContentsResponseDto
import com.flint.data.dto.search.SearchContentsResponseDto
import retrofit2.http.GET
import retrofit2.http.Query

Expand All @@ -12,4 +13,9 @@ interface SearchApi {
@Query("cursor") cursor: Int,
@Query("size") size: Int
) : BaseResponse<SearchBookmarkedContentsResponseDto>

@GET("/api/v1/search/contents")
suspend fun getSearchContentList(
@Query("keyword") keyword: String?
): BaseResponse<SearchContentsResponseDto>
}
6 changes: 6 additions & 0 deletions app/src/main/java/com/flint/data/api/UserApi.kt
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package com.flint.data.api

import com.flint.data.dto.base.BaseResponse
import com.flint.data.dto.user.response.NicknameCheckResponseDto
import com.flint.data.dto.user.response.BookmarkedCollectionListResponseDto
import com.flint.data.dto.user.response.CreatedCollectionListResponseDto
import com.flint.data.dto.user.response.UserKeywordsResponseDto
import com.flint.data.dto.user.response.UserProfileResponseDto
import retrofit2.http.GET
import retrofit2.http.Path
import retrofit2.http.Query

interface UserApi {
// 사용자 프로필 조회
Expand All @@ -16,6 +18,10 @@ interface UserApi {
): BaseResponse<UserProfileResponseDto>

// 닉네임 중복 체크
@GET("/api/v1/users/nickname/check")
suspend fun checkNickname(
@Query("nickname") nickname: String
): BaseResponse<NicknameCheckResponseDto>

// 사용자 북마크 컬렉션 조회
@GET("/api/v1/users/{userId}/bookmarked-collections")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.flint.data.dto.response

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class ContentSearchResponseDto(
@SerialName("data") val contents: List<ContentItemDto>,
@SerialName("meta") val meta: PageMetaDto,
)
@Serializable
data class ContentItemDto(
@SerialName("contentId") val contentId: Long,
@SerialName("title") val title: String,
@SerialName("author") val author: String,
@SerialName("posterUrl") val posterUrl: String,
@SerialName("year") val year: Int,
)

@Serializable
data class PageMetaDto(
@SerialName("type") val type: String,
@SerialName("returned") val returned: Int,
@SerialName("currentPage") val currentPage: Int,
@SerialName("totalPages") val totalPages: Int,
@SerialName("totalElements") val totalElements: Int,
@SerialName("nextCursor") val nextCursor: String?,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.flint.data.dto.search

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class SearchContentsResponseDto(
@SerialName("contents")
val contents: List<Content>
) {
@Serializable
data class Content(
@SerialName("id")
val id: String,
@SerialName("title")
val title: String,
@SerialName("author")
val author: String,
@SerialName("posterUrl")
val posterUrl: String,
@SerialName("year")
val year: Int
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.flint.data.dto.user.response

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class NicknameCheckResponseDto(
@SerialName("available")
val available: Boolean
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.flint.domain.mapper.search

import com.flint.data.dto.search.SearchContentsResponseDto
import com.flint.domain.model.search.SearchContentItemModel
import com.flint.domain.model.search.SearchContentListModel
import kotlinx.collections.immutable.toImmutableList

fun SearchContentsResponseDto.toModel(): SearchContentListModel {
return SearchContentListModel(
contents = this.contents.map { it.toModel() }.toImmutableList()
)
}

private fun SearchContentsResponseDto.Content.toModel(): SearchContentItemModel{
return SearchContentItemModel(
id = id,
title = title,
author = author,
posterUrl = posterUrl,
year = year
)
}



10 changes: 10 additions & 0 deletions app/src/main/java/com/flint/domain/mapper/user/UserMapper.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.flint.domain.mapper.user

import com.flint.data.dto.user.response.NicknameCheckResponseDto
import com.flint.domain.model.user.NicknameCheckModel

fun NicknameCheckResponseDto.toModel(): NicknameCheckModel {
return NicknameCheckModel(
isAvailable = this.available
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package com.flint.domain.model.search

import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf

data class SearchContentItemModel(
val id: String,
val title: String,
val author: String,
val posterUrl: String,
val year: Int,
)

data class SearchContentListModel(
val contents: ImmutableList<SearchContentItemModel>
) {
companion object {
val FakeList =
persistentListOf(
SearchContentItemModel(
id = "1",
title = "은하수를 여행하는 히치하이커를 위한 안내서",
author = "가스 제닝스",
posterUrl = "https://image.tmdb.org/t/p/w500/ib6v6qUXzez1x2qIOLN7C0yJNPQ.jpg",
year = 2005,
),
SearchContentItemModel(
id = "2",
title = "인터스텔라",
author = "크리스토퍼 놀란",
posterUrl = "https://image.tmdb.org/t/p/w500/ib6v6qUXzez1x2qIOLN7C0yJNPQ.jpg",
year = 2014,
),
SearchContentItemModel(
id = "3",
title = "인셉션",
author = "크리스토퍼 놀란",
posterUrl = "https://image.tmdb.org/t/p/w500/ib6v6qUXzez1x2qIOLN7C0yJNPQ.jpg",
year = 2010,
),
SearchContentItemModel(
id = "4",
title = "기생충",
author = "봉준호",
posterUrl = "https://image.tmdb.org/t/p/w500/ib6v6qUXzez1x2qIOLN7C0yJNPQ.jpg",
year = 2019,
),
SearchContentItemModel(
id = "5",
title = "어벤져스: 엔드게임",
author = "안소니 루소",
posterUrl = "https://image.tmdb.org/t/p/w500/ib6v6qUXzez1x2qIOLN7C0yJNPQ.jpg",
year = 2019,
),
SearchContentItemModel(
id = "6",
title = "다크 나이트",
author = "크리스토퍼 놀란",
posterUrl = "https://image.tmdb.org/t/p/w500/ib6v6qUXzez1x2qIOLN7C0yJNPQ.jpg",
year = 2008,
),
SearchContentItemModel(
id = "7",
title = "오펜하이머",
author = "크리스토퍼 놀란",
posterUrl = "https://image.tmdb.org/t/p/w500/ib6v6qUXzez1x2qIOLN7C0yJNPQ.jpg",
year = 2023,
),
SearchContentItemModel(
id = "8",
title = "괴물",
author = "봉준호",
posterUrl = "https://image.tmdb.org/t/p/w500/ib6v6qUXzez1x2qIOLN7C0yJNPQ.jpg",
year = 2006,
),
SearchContentItemModel(
id = "9",
title = "설국열차",
author = "봉준호",
posterUrl = "https://image.tmdb.org/t/p/w500/ib6v6qUXzez1x2qIOLN7C0yJNPQ.jpg",
year = 2013,
),
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.flint.domain.model.user

data class NicknameCheckModel(
val isAvailable: Boolean
)
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,17 @@ package com.flint.domain.repository
import com.flint.core.common.util.suspendRunCatching
import com.flint.data.api.SearchApi
import com.flint.domain.mapper.content.toModel
import com.flint.domain.mapper.search.toModel
import com.flint.domain.model.content.ContentModel
import com.flint.domain.model.search.SearchContentListModel
import javax.inject.Inject

class SearchRepository @Inject constructor(
private val apiService: SearchApi,
) {
suspend fun getBookmarkedContentList(keyword: String, cursor: Int, size: Int): Result<List<ContentModel>> =
suspendRunCatching { apiService.getBookmarkedContentList(keyword, cursor, size).data.toModel() }

suspend fun getSearchContentList(keyword: String?): Result<SearchContentListModel> =
suspendRunCatching { apiService.getSearchContentList(keyword).data.toModel() }
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.flint.core.common.util.suspendRunCatching
import com.flint.data.api.UserApi
import com.flint.domain.mapper.collection.toModel
import com.flint.domain.mapper.user.toModel
import com.flint.domain.model.user.NicknameCheckModel
import com.flint.domain.model.collection.CollectionListModel
import com.flint.domain.model.user.KeywordListModel
import com.flint.domain.model.user.UserProfileResponseModel
Expand Down Expand Up @@ -33,4 +34,10 @@ class UserRepository @Inject constructor(
suspendRunCatching {
apiService.getUserBookmarkedCollections(userId ?: myTempUserId).data.toModel()
}

// 닉네임 중복 체크
suspend fun checkNickname(nickname: String): Result<NicknameCheckModel> =
suspendRunCatching {
apiService.checkNickname(nickname).data.toModel()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,18 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.GridItemSpan
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import com.flint.core.designsystem.component.button.FlintBasicButton
import com.flint.core.designsystem.component.button.FlintButtonState
import com.flint.core.designsystem.component.image.SelectedContentItem
Expand All @@ -36,7 +37,13 @@ fun OnboardingContentRoute(
paddingValues: PaddingValues,
navigateToOnboardingOtt: () -> Unit,
navigateUp: () -> Unit,
) {
viewModel: OnboardingViewModel = hiltViewModel(),
) {

LaunchedEffect(Unit) {
viewModel.getSearchContentList(null) // 인기 목록 받아오기
}

OnboardingContentScreen(
nickname = "User",
currentStep = 7,
Expand All @@ -60,9 +67,8 @@ fun OnboardingContentScreen(
modifier =
modifier
.fillMaxSize()
.background(color = FlintTheme.colors.background)
.statusBarsPadding(),
) {
.background(color = FlintTheme.colors.background),
) {
FlintBackTopAppbar(
onClick = onBackClick,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
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 com.flint.R
import com.flint.core.designsystem.component.button.FlintBasicButton
import com.flint.core.designsystem.component.button.FlintButtonState
Expand All @@ -28,7 +28,8 @@ fun OnboardingDoneRoute(
paddingValues: PaddingValues,
navigateToHome: () -> Unit,
navigateUp: () -> Unit,
) {
viewModel: OnboardingViewModel = hiltViewModel(),
) {
OnboardingDoneScreen(
onBackClick = navigateUp,
onNextClick = navigateToHome,
Expand All @@ -47,7 +48,6 @@ fun OnboardingDoneScreen(
modifier
.fillMaxSize()
.background(color = FlintTheme.colors.background)
.statusBarsPadding(),
) {
FlintBackTopAppbar(
onClick = onBackClick,
Expand Down
Loading