diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index a7a42e15..ebff60e7 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -22,6 +22,7 @@ dependencies {
implementation(projects.data.video)
implementation(projects.data.user)
implementation(projects.data.keyword)
+ implementation(projects.data.exhibition)
implementation(projects.local.auth)
implementation(projects.local.user)
implementation(projects.local.video)
@@ -29,6 +30,7 @@ dependencies {
implementation(projects.remote.user)
implementation(projects.remote.video)
implementation(projects.remote.keyword)
+ implementation(projects.remote.exhibition)
implementation(projects.feature.navigator)
implementation(libs.kakao.login)
implementation(libs.hilt.androidx.common)
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 0164e11e..69c4d0fb 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -12,6 +12,8 @@
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28"
tools:ignore="ScopedStorage" />
+
+
Unit = {},
onBookmarkClick: () -> Unit = {},
onDeleteClick: () -> Unit = {},
+ onMoreClick: () -> Unit = {},
) {
var boxSize by remember { mutableStateOf(IntSize.Zero) }
var expanded by remember { mutableStateOf(false) }
@@ -128,6 +129,14 @@ fun RecordyVideoText(
style = RecordyTheme.typography.body2M,
color = RecordyTheme.colors.gray01,
)
+ Spacer(modifier = Modifier.height(8.dp))
+ Icon(
+ modifier = Modifier
+ .customClickable { onMoreClick() },
+ painter = painterResource(id = R.drawable.ic_seemore),
+ contentDescription = "see more",
+ tint = RecordyTheme.colors.gray01,
+ )
Spacer(modifier = Modifier.height(if (isMyVideo) 16.dp else 20.dp))
if (isMyVideo) {
Icon(
diff --git a/core/designsystem/src/main/res/drawable/ic_seemore.xml b/core/designsystem/src/main/res/drawable/ic_seemore.xml
new file mode 100644
index 00000000..6261dcb5
--- /dev/null
+++ b/core/designsystem/src/main/res/drawable/ic_seemore.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
diff --git a/core/designsystem/src/main/res/drawable/ic_viskit_logo.xml b/core/designsystem/src/main/res/drawable/ic_viskit_logo.xml
new file mode 100644
index 00000000..08e2a383
--- /dev/null
+++ b/core/designsystem/src/main/res/drawable/ic_viskit_logo.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/core/model/src/main/java/com/record/model/VideoData.kt b/core/model/src/main/java/com/record/model/VideoData.kt
index 5592e4b0..108ef3fb 100644
--- a/core/model/src/main/java/com/record/model/VideoData.kt
+++ b/core/model/src/main/java/com/record/model/VideoData.kt
@@ -1,12 +1,15 @@
package com.record.model
data class VideoData(
- val id: String,
- val videoUri: String,
- val previewUri: String,
- val location: String,
- val userName: String,
- val content: String,
- val bookmarkCount: Int,
+ val bookmarkId: Long,
+ val id: Long,
val isBookmark: Boolean,
+ val bookmarkCount: Int,
+ val content: String,
+ val videoUrl: String,
+ val previewUrl: String,
+ val location: String,
+ val uploaderId: Long,
+ val nickname: String,
+ val isMine: Boolean,
)
diff --git a/data/exhibition/.gitignore b/data/exhibition/.gitignore
new file mode 100644
index 00000000..42afabfd
--- /dev/null
+++ b/data/exhibition/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/data/exhibition/build.gradle.kts b/data/exhibition/build.gradle.kts
new file mode 100644
index 00000000..a2b44962
--- /dev/null
+++ b/data/exhibition/build.gradle.kts
@@ -0,0 +1,13 @@
+plugins {
+ alias(libs.plugins.recordy.data)
+ alias(libs.plugins.kotlin.serialization)
+}
+
+android {
+ namespace = "com.record.exhibition"
+}
+
+dependencies {
+ implementation(projects.domain.exhibition)
+ implementation(projects.domain.video)
+}
diff --git a/data/exhibition/src/main/AndroidManifest.xml b/data/exhibition/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..8bdb7e14
--- /dev/null
+++ b/data/exhibition/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/data/exhibition/src/main/java/com/example/exhibition/di/RepositoryModule.kt b/data/exhibition/src/main/java/com/example/exhibition/di/RepositoryModule.kt
new file mode 100644
index 00000000..9e36ff32
--- /dev/null
+++ b/data/exhibition/src/main/java/com/example/exhibition/di/RepositoryModule.kt
@@ -0,0 +1,23 @@
+package com.example.exhibition.di
+
+import com.example.exhibition.repository.ExhibitionRepositoryImpl
+import com.example.exhibition.repository.SearchRepositoryImpl
+import com.record.exhibition.repository.ExhibitionRepository
+import com.record.exhibition.repository.SearchRepository
+import dagger.Binds
+import dagger.Module
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import javax.inject.Singleton
+
+@Module
+@InstallIn(SingletonComponent::class)
+abstract class RepositoryModule {
+ @Binds
+ @Singleton
+ abstract fun bindsExhibitionRepository(exhibitionRepositoryImpl: ExhibitionRepositoryImpl): ExhibitionRepository
+
+ @Binds
+ @Singleton
+ abstract fun bindsSearchRepository(searchRepositoryImpl: SearchRepositoryImpl): SearchRepository
+}
diff --git a/data/exhibition/src/main/java/com/example/exhibition/model/remote/request/RequestPatchExhibitionDto.kt b/data/exhibition/src/main/java/com/example/exhibition/model/remote/request/RequestPatchExhibitionDto.kt
new file mode 100644
index 00000000..49a2ff5e
--- /dev/null
+++ b/data/exhibition/src/main/java/com/example/exhibition/model/remote/request/RequestPatchExhibitionDto.kt
@@ -0,0 +1,18 @@
+package com.example.exhibition.model.remote.request
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class RequestPatchExhibitionDto(
+ @SerialName("endDate")
+ val endDate: String,
+ @SerialName("id")
+ val id: Int,
+ @SerialName("isFree")
+ val isFree: Boolean,
+ @SerialName("name")
+ val name: String,
+ @SerialName("startDate")
+ val startDate: String,
+)
diff --git a/data/exhibition/src/main/java/com/example/exhibition/model/remote/request/RequestPostExhibitionDto.kt b/data/exhibition/src/main/java/com/example/exhibition/model/remote/request/RequestPostExhibitionDto.kt
new file mode 100644
index 00000000..e4e86f69
--- /dev/null
+++ b/data/exhibition/src/main/java/com/example/exhibition/model/remote/request/RequestPostExhibitionDto.kt
@@ -0,0 +1,18 @@
+package com.example.exhibition.model.remote.request
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class RequestPostExhibitionDto(
+ @SerialName("endDate")
+ val endDate: String,
+ @SerialName("isFree")
+ val isFree: Boolean,
+ @SerialName("name")
+ val name: String,
+ @SerialName("placeId")
+ val placeId: Int,
+ @SerialName("startDate")
+ val startDate: String,
+)
diff --git a/data/exhibition/src/main/java/com/example/exhibition/model/remote/request/RequestPostPlaceDto.kt b/data/exhibition/src/main/java/com/example/exhibition/model/remote/request/RequestPostPlaceDto.kt
new file mode 100644
index 00000000..04bcc883
--- /dev/null
+++ b/data/exhibition/src/main/java/com/example/exhibition/model/remote/request/RequestPostPlaceDto.kt
@@ -0,0 +1,18 @@
+package com.example.exhibition.model.remote.request
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class RequestPostPlaceDto(
+ @SerialName("address")
+ val address: String,
+ @SerialName("id")
+ val id: String,
+ @SerialName("latitude")
+ val latitude: Int,
+ @SerialName("longitude")
+ val longitude: Int,
+ @SerialName("name")
+ val name: String,
+)
diff --git a/data/exhibition/src/main/java/com/example/exhibition/model/remote/response/Location.kt b/data/exhibition/src/main/java/com/example/exhibition/model/remote/response/Location.kt
new file mode 100644
index 00000000..6d140ca2
--- /dev/null
+++ b/data/exhibition/src/main/java/com/example/exhibition/model/remote/response/Location.kt
@@ -0,0 +1,14 @@
+package com.example.exhibition.model.remote.response
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class Location(
+ @SerialName("id")
+ val id: Int,
+ @SerialName("latitude")
+ val latitude: Double,
+ @SerialName("longitude")
+ val longitude: Double,
+)
diff --git a/data/exhibition/src/main/java/com/example/exhibition/model/remote/response/ResponseGetExhibitionSearchDto.kt b/data/exhibition/src/main/java/com/example/exhibition/model/remote/response/ResponseGetExhibitionSearchDto.kt
new file mode 100644
index 00000000..fe09f28e
--- /dev/null
+++ b/data/exhibition/src/main/java/com/example/exhibition/model/remote/response/ResponseGetExhibitionSearchDto.kt
@@ -0,0 +1,29 @@
+package com.example.exhibition.model.remote.response
+
+import com.record.exhibition.model.ResultType
+import com.record.exhibition.model.SearchResult
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class ResponseGetExhibitionSearchDto(
+ @SerialName("id")
+ val id: Long,
+ @SerialName("type")
+ val type: String,
+ @SerialName("address")
+ val address: String,
+ @SerialName("name")
+ val name: String,
+)
+
+fun ResponseGetExhibitionSearchDto.toDomain() = SearchResult(
+ id = this.id,
+ type = when (this.type) {
+ "PLACE" -> ResultType.PLACE
+ "EXHIBITION" -> ResultType.EXHIBITION
+ else -> ResultType.UNKNOWN
+ },
+ address = this.address,
+ name = this.name,
+)
diff --git a/data/exhibition/src/main/java/com/example/exhibition/model/remote/response/ResponseGetExhibitionsDto.kt b/data/exhibition/src/main/java/com/example/exhibition/model/remote/response/ResponseGetExhibitionsDto.kt
new file mode 100644
index 00000000..28c4fc82
--- /dev/null
+++ b/data/exhibition/src/main/java/com/example/exhibition/model/remote/response/ResponseGetExhibitionsDto.kt
@@ -0,0 +1,27 @@
+package com.example.exhibition.model.remote.response
+
+import com.record.exhibition.model.Exhibition
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class ResponseGetExhibitionsDto(
+ @SerialName("id")
+ val id: Int,
+ @SerialName("isFree")
+ val isFree: Boolean,
+ @SerialName("name")
+ val name: String,
+ @SerialName("startDate")
+ val startDate: String,
+ @SerialName("endDate")
+ val endDate: String,
+)
+
+fun ResponseGetExhibitionsDto.toDomain() = Exhibition(
+ id = this.id,
+ isFree = this.isFree,
+ name = this.name,
+ startDate = this.startDate,
+ endDate = this.endDate,
+)
diff --git a/data/exhibition/src/main/java/com/example/exhibition/model/remote/response/ResponseGetPagingPlaceDto.kt b/data/exhibition/src/main/java/com/example/exhibition/model/remote/response/ResponseGetPagingPlaceDto.kt
new file mode 100644
index 00000000..10a9c874
--- /dev/null
+++ b/data/exhibition/src/main/java/com/example/exhibition/model/remote/response/ResponseGetPagingPlaceDto.kt
@@ -0,0 +1,14 @@
+package com.example.exhibition.model.remote.response
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class ResponseGetPagingPlaceDto(
+ @SerialName("content")
+ val content: List,
+ @SerialName("hasNext")
+ val hasNext: Boolean,
+ @SerialName("pageNumber")
+ val pageNumber: Int,
+)
diff --git a/data/exhibition/src/main/java/com/example/exhibition/model/remote/response/ResponseGetPlaceDto.kt b/data/exhibition/src/main/java/com/example/exhibition/model/remote/response/ResponseGetPlaceDto.kt
new file mode 100644
index 00000000..fad8868a
--- /dev/null
+++ b/data/exhibition/src/main/java/com/example/exhibition/model/remote/response/ResponseGetPlaceDto.kt
@@ -0,0 +1,22 @@
+package com.example.exhibition.model.remote.response
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class ResponseGetPlaceDto(
+ @SerialName("address")
+ val address: String?,
+ @SerialName("exhibitionSize")
+ val exhibitionSize: Int,
+ @SerialName("id")
+ val id: Int,
+ @SerialName("location")
+ val location: Location,
+ @SerialName("name")
+ val name: String,
+ @SerialName("platformId")
+ val platformId: String?,
+ @SerialName("recordSize")
+ val recordSize: Int,
+)
diff --git a/data/exhibition/src/main/java/com/example/exhibition/model/remote/response/ResponseGetReviewsDto.kt b/data/exhibition/src/main/java/com/example/exhibition/model/remote/response/ResponseGetReviewsDto.kt
new file mode 100644
index 00000000..36c7ee95
--- /dev/null
+++ b/data/exhibition/src/main/java/com/example/exhibition/model/remote/response/ResponseGetReviewsDto.kt
@@ -0,0 +1,18 @@
+package com.example.exhibition.model.remote.response
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class ResponseGetReviewsDto(
+ @SerialName("authorName")
+ val authorName: String,
+ @SerialName("content")
+ val content: String,
+ @SerialName("createdAt")
+ val createdAt: String,
+ @SerialName("id")
+ val id: Int,
+ @SerialName("rating")
+ val rating: Int,
+)
diff --git a/data/exhibition/src/main/java/com/example/exhibition/repository/ExhibitionRepositoryImpl.kt b/data/exhibition/src/main/java/com/example/exhibition/repository/ExhibitionRepositoryImpl.kt
new file mode 100644
index 00000000..dc4f221c
--- /dev/null
+++ b/data/exhibition/src/main/java/com/example/exhibition/repository/ExhibitionRepositoryImpl.kt
@@ -0,0 +1,96 @@
+package com.example.exhibition.repository
+
+import com.example.exhibition.model.remote.response.toDomain
+import com.example.exhibition.source.remote.RemoteExhibitionDataSource
+import com.example.exhibition.source.remote.RemotePlaceDataSource
+import com.record.exhibition.model.Exhibition
+import com.record.exhibition.model.ExhibitionFilter
+import com.record.exhibition.model.Place
+import com.record.exhibition.repository.ExhibitionRepository
+import com.record.model.Page
+import com.record.model.exception.ApiError
+import com.record.video.model.toCore
+import com.record.video.repository.VideoRepository
+import retrofit2.HttpException
+import javax.inject.Inject
+
+class ExhibitionRepositoryImpl @Inject constructor(
+ private val remoteExhibitionDataSource: RemoteExhibitionDataSource,
+ private val remotePlaceDataSource: RemotePlaceDataSource,
+ private val videoRepository: VideoRepository,
+) : ExhibitionRepository {
+ override suspend fun getNearPlaceData(number: Int, size: Int, latitude: Double, longitude: Double) =
+ runCatching {
+ remotePlaceDataSource.getNearPlace(number = number, size = size, latitude = latitude, longitude = -longitude, distance = 3000000.0)
+ }.mapCatching { it ->
+ Page(
+ hasNext = it.hasNext,
+ page = it.pageNumber,
+ data = it.content.map {
+ val result = videoRepository.getPlaceVideos(it.id, 0, it.recordSize).getOrNull()
+ Place(
+ placeId = it.id,
+ address = it.address ?: "",
+ name = it.name,
+ exhibitionCount = it.exhibitionSize,
+ recordCount = it.recordSize,
+ exhibitionRecord = result?.data?.map { it.toCore() },
+ )
+ },
+ )
+ }.recoverCatching { exception ->
+ when (exception) {
+ is HttpException -> {
+ throw ApiError(exception.message())
+ }
+
+ else -> {
+ throw exception
+ }
+ }
+ }
+
+ override suspend fun getPlaceById(placeId: Long): Result = runCatching {
+ remotePlaceDataSource.getPlaceById(placeId.toInt())
+ }.mapCatching { it ->
+ val result = videoRepository.getPlaceVideos(it.id, 0, it.recordSize).getOrNull()
+ Place(
+ placeId = it.id,
+ address = it.address ?: "",
+ name = it.name,
+ exhibitionCount = it.exhibitionSize,
+ recordCount = it.recordSize,
+ exhibitionRecord = result?.data?.map { it.toCore() },
+ )
+ }.recoverCatching { exception ->
+ when (exception) {
+ is HttpException -> {
+ throw ApiError(exception.message())
+ }
+
+ else -> {
+ throw exception
+ }
+ }
+ }
+
+ override suspend fun getExhibitions(placeId: Long, filter: ExhibitionFilter): Result> = runCatching {
+ when (filter) {
+ ExhibitionFilter.DEFAULT -> remoteExhibitionDataSource.getExhibitionById(placeId.toInt())
+ ExhibitionFilter.FREE -> remoteExhibitionDataSource.getFreeExhibition(placeId.toInt())
+ ExhibitionFilter.CLOSING -> remoteExhibitionDataSource.getClosingExhibition(placeId.toInt())
+ }
+ }.mapCatching {
+ it.map { it.toDomain() }
+ }.recoverCatching { exception ->
+ when (exception) {
+ is HttpException -> {
+ throw ApiError(exception.message())
+ }
+
+ else -> {
+ throw exception
+ }
+ }
+ }
+}
diff --git a/data/exhibition/src/main/java/com/example/exhibition/repository/SearchRepositoryImpl.kt b/data/exhibition/src/main/java/com/example/exhibition/repository/SearchRepositoryImpl.kt
new file mode 100644
index 00000000..fce9965a
--- /dev/null
+++ b/data/exhibition/src/main/java/com/example/exhibition/repository/SearchRepositoryImpl.kt
@@ -0,0 +1,29 @@
+package com.example.exhibition.repository
+
+import com.example.exhibition.model.remote.response.toDomain
+import com.example.exhibition.source.remote.RemoteSearchDataSource
+import com.record.exhibition.model.SearchResult
+import com.record.exhibition.repository.SearchRepository
+import com.record.model.exception.ApiError
+import retrofit2.HttpException
+import javax.inject.Inject
+
+class SearchRepositoryImpl @Inject constructor(
+ private val searchDataSource: RemoteSearchDataSource,
+) : SearchRepository {
+ override suspend fun searchExhibition(query: String): Result> = runCatching {
+ searchDataSource.searchExhibition(query)
+ }.mapCatching {
+ it.map { it.toDomain() }
+ }.recoverCatching { exception ->
+ when (exception) {
+ is HttpException -> {
+ throw ApiError(exception.message())
+ }
+
+ else -> {
+ throw exception
+ }
+ }
+ }
+}
diff --git a/data/exhibition/src/main/java/com/example/exhibition/source/remote/RemoteExhibitionDataSource.kt b/data/exhibition/src/main/java/com/example/exhibition/source/remote/RemoteExhibitionDataSource.kt
new file mode 100644
index 00000000..53c656f1
--- /dev/null
+++ b/data/exhibition/src/main/java/com/example/exhibition/source/remote/RemoteExhibitionDataSource.kt
@@ -0,0 +1,31 @@
+package com.example.exhibition.source.remote
+
+import com.example.exhibition.model.remote.request.RequestPatchExhibitionDto
+import com.example.exhibition.model.remote.request.RequestPostExhibitionDto
+import com.example.exhibition.model.remote.response.ResponseGetExhibitionsDto
+
+interface RemoteExhibitionDataSource {
+ suspend fun postExhibition(
+ requestPostExhibitionDto: RequestPostExhibitionDto,
+ )
+
+ suspend fun getExhibitionById(
+ placeId: Int,
+ ): List
+
+ suspend fun getFreeExhibition(
+ placeId: Int,
+ ): List
+
+ suspend fun getClosingExhibition(
+ placeId: Int,
+ ): List
+
+ suspend fun patchExhibition(
+ requestPatchExhibitionDto: RequestPatchExhibitionDto,
+ )
+
+ suspend fun deleteExhibition(
+ exhibitionId: Int,
+ )
+}
diff --git a/data/exhibition/src/main/java/com/example/exhibition/source/remote/RemotePlaceDataSource.kt b/data/exhibition/src/main/java/com/example/exhibition/source/remote/RemotePlaceDataSource.kt
new file mode 100644
index 00000000..0e6e17ce
--- /dev/null
+++ b/data/exhibition/src/main/java/com/example/exhibition/source/remote/RemotePlaceDataSource.kt
@@ -0,0 +1,33 @@
+package com.example.exhibition.source.remote
+
+import com.example.exhibition.model.remote.request.RequestPostPlaceDto
+import com.example.exhibition.model.remote.response.ResponseGetPagingPlaceDto
+import com.example.exhibition.model.remote.response.ResponseGetPlaceDto
+import com.example.exhibition.model.remote.response.ResponseGetReviewsDto
+
+interface RemotePlaceDataSource {
+ suspend fun postPlace(
+ requestPostPlaceDto: RequestPostPlaceDto,
+ )
+
+ suspend fun getPlaceById(
+ id: Int,
+ ): ResponseGetPlaceDto
+
+ suspend fun getReviewsById(
+ id: Int,
+ ): List
+
+ suspend fun getNearPlace(
+ number: Int,
+ size: Int,
+ latitude: Double,
+ longitude: Double,
+ distance: Double,
+ ): ResponseGetPagingPlaceDto
+
+ suspend fun getHasInProgressExhibitionPlaces(
+ number: Int,
+ size: Int,
+ ): List
+}
diff --git a/data/exhibition/src/main/java/com/example/exhibition/source/remote/RemoteSearchDataSource.kt b/data/exhibition/src/main/java/com/example/exhibition/source/remote/RemoteSearchDataSource.kt
new file mode 100644
index 00000000..0c4ab239
--- /dev/null
+++ b/data/exhibition/src/main/java/com/example/exhibition/source/remote/RemoteSearchDataSource.kt
@@ -0,0 +1,7 @@
+package com.example.exhibition.source.remote
+
+import com.example.exhibition.model.remote.response.ResponseGetExhibitionSearchDto
+
+interface RemoteSearchDataSource {
+ suspend fun searchExhibition(query: String): List
+}
diff --git a/data/video/src/main/java/com/record/video/repository/VideoRepositoryImpl.kt b/data/video/src/main/java/com/record/video/repository/VideoRepositoryImpl.kt
index 5147a3e4..903d1c75 100644
--- a/data/video/src/main/java/com/record/video/repository/VideoRepositoryImpl.kt
+++ b/data/video/src/main/java/com/record/video/repository/VideoRepositoryImpl.kt
@@ -66,6 +66,22 @@ class VideoRepositoryImpl @Inject constructor(
}
}
+ override suspend fun getPlaceVideos(placeId: Int, cursor: Long, pageSize: Int): Result> = runCatching {
+ remoteVideoDataSource.getPlaceVideos(placeId, cursor, pageSize)
+ }.mapCatching {
+ it.toCore()
+ }.recoverCatching { exception ->
+ when (exception) {
+ is HttpException -> {
+ throw ApiError(exception.message())
+ }
+
+ else -> {
+ throw exception
+ }
+ }
+ }
+
override suspend fun getUserVideos(otherUserId: Long, cursorId: Long, size: Int): Result> = runCatching {
remoteVideoDataSource.getUserVideos(otherUserId, cursorId, size)
}.mapCatching {
diff --git a/data/video/src/main/java/com/record/video/source/remote/RemoteVideoDataSource.kt b/data/video/src/main/java/com/record/video/source/remote/RemoteVideoDataSource.kt
index 073e7ca3..758adb72 100644
--- a/data/video/src/main/java/com/record/video/source/remote/RemoteVideoDataSource.kt
+++ b/data/video/src/main/java/com/record/video/source/remote/RemoteVideoDataSource.kt
@@ -9,6 +9,7 @@ interface RemoteVideoDataSource {
suspend fun getAllVideos(cursorId: Long, size: Int): List
suspend fun getRecentVideos(keywords: List?, cursor: Long, pageSize: Int): ResponseGetSliceVideoDto
suspend fun getPopularVideos(keywords: List?, pageNumber: Int, pageSize: Int): ResponseGetPagingVideoDto
+ suspend fun getPlaceVideos(placeId: Int, cursor: Long, pageSize: Int): ResponseGetSliceVideoDto
suspend fun getUserVideos(otherUserId: Long, cursorId: Long, size: Int): ResponseGetSliceVideoDto
suspend fun getFollowingVideos(cursorId: Long, size: Int): ResponseGetSliceVideoDto
suspend fun getBookmarkVideos(cursorId: Long, size: Int): ResponseGetBookmarkSliceVideoDto
diff --git a/domain/exhibition/.gitignore b/domain/exhibition/.gitignore
new file mode 100644
index 00000000..42afabfd
--- /dev/null
+++ b/domain/exhibition/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/domain/exhibition/build.gradle.kts b/domain/exhibition/build.gradle.kts
new file mode 100644
index 00000000..8c84885d
--- /dev/null
+++ b/domain/exhibition/build.gradle.kts
@@ -0,0 +1,8 @@
+plugins {
+ alias(libs.plugins.recordy.java.library)
+}
+
+dependencies {
+ implementation(projects.core.model)
+ implementation(libs.kotlinx.coroutines.core)
+}
diff --git a/domain/exhibition/src/main/java/com/record/exhibition/model/Exhibition.kt b/domain/exhibition/src/main/java/com/record/exhibition/model/Exhibition.kt
new file mode 100644
index 00000000..bdfb981e
--- /dev/null
+++ b/domain/exhibition/src/main/java/com/record/exhibition/model/Exhibition.kt
@@ -0,0 +1,9 @@
+package com.record.exhibition.model
+
+data class Exhibition(
+ val id: Int,
+ val isFree: Boolean,
+ val name: String,
+ val startDate: String,
+ val endDate: String,
+)
diff --git a/domain/exhibition/src/main/java/com/record/exhibition/model/ExhibitionFilter.kt b/domain/exhibition/src/main/java/com/record/exhibition/model/ExhibitionFilter.kt
new file mode 100644
index 00000000..6b5b2deb
--- /dev/null
+++ b/domain/exhibition/src/main/java/com/record/exhibition/model/ExhibitionFilter.kt
@@ -0,0 +1,7 @@
+package com.record.exhibition.model
+
+enum class ExhibitionFilter {
+ DEFAULT,
+ FREE,
+ CLOSING,
+}
diff --git a/domain/exhibition/src/main/java/com/record/exhibition/model/Place.kt b/domain/exhibition/src/main/java/com/record/exhibition/model/Place.kt
new file mode 100644
index 00000000..4759bc95
--- /dev/null
+++ b/domain/exhibition/src/main/java/com/record/exhibition/model/Place.kt
@@ -0,0 +1,12 @@
+package com.record.exhibition.model
+
+import com.record.model.VideoData
+
+data class Place(
+ val placeId: Int,
+ val address: String,
+ val name: String,
+ val exhibitionCount: Int,
+ val recordCount: Int,
+ val exhibitionRecord: List?,
+)
diff --git a/domain/exhibition/src/main/java/com/record/exhibition/model/ResultType.kt b/domain/exhibition/src/main/java/com/record/exhibition/model/ResultType.kt
new file mode 100644
index 00000000..a83a2608
--- /dev/null
+++ b/domain/exhibition/src/main/java/com/record/exhibition/model/ResultType.kt
@@ -0,0 +1,7 @@
+package com.record.exhibition.model
+
+enum class ResultType {
+ PLACE,
+ EXHIBITION,
+ UNKNOWN,
+}
diff --git a/domain/exhibition/src/main/java/com/record/exhibition/model/SearchResult.kt b/domain/exhibition/src/main/java/com/record/exhibition/model/SearchResult.kt
new file mode 100644
index 00000000..c371d47d
--- /dev/null
+++ b/domain/exhibition/src/main/java/com/record/exhibition/model/SearchResult.kt
@@ -0,0 +1,8 @@
+package com.record.exhibition.model
+
+data class SearchResult(
+ val id: Long,
+ val type: ResultType,
+ val address: String,
+ val name: String,
+)
diff --git a/domain/exhibition/src/main/java/com/record/exhibition/repository/ExhibitionRepository.kt b/domain/exhibition/src/main/java/com/record/exhibition/repository/ExhibitionRepository.kt
new file mode 100644
index 00000000..0acecdf0
--- /dev/null
+++ b/domain/exhibition/src/main/java/com/record/exhibition/repository/ExhibitionRepository.kt
@@ -0,0 +1,12 @@
+package com.record.exhibition.repository
+
+import com.record.exhibition.model.Exhibition
+import com.record.exhibition.model.ExhibitionFilter
+import com.record.exhibition.model.Place
+import com.record.model.Page
+
+interface ExhibitionRepository {
+ suspend fun getNearPlaceData(number: Int, size: Int, latitude: Double, longitude: Double): Result>
+ suspend fun getPlaceById(placeId: Long): Result
+ suspend fun getExhibitions(placeId: Long, filter: ExhibitionFilter): Result>
+}
diff --git a/domain/exhibition/src/main/java/com/record/exhibition/repository/SearchRepository.kt b/domain/exhibition/src/main/java/com/record/exhibition/repository/SearchRepository.kt
new file mode 100644
index 00000000..1138c604
--- /dev/null
+++ b/domain/exhibition/src/main/java/com/record/exhibition/repository/SearchRepository.kt
@@ -0,0 +1,7 @@
+package com.record.exhibition.repository
+
+import com.record.exhibition.model.SearchResult
+
+interface SearchRepository {
+ suspend fun searchExhibition(query: String): Result>
+}
diff --git a/domain/video/src/main/java/com/record/video/model/VideoData.kt b/domain/video/src/main/java/com/record/video/model/VideoData.kt
index be87793e..6060c954 100644
--- a/domain/video/src/main/java/com/record/video/model/VideoData.kt
+++ b/domain/video/src/main/java/com/record/video/model/VideoData.kt
@@ -13,3 +13,17 @@ data class VideoData(
val nickname: String,
val isMine: Boolean,
)
+
+fun VideoData.toCore() = com.record.model.VideoData(
+ bookmarkCount = this.bookmarkCount,
+ id = this.id,
+ isBookmark = this.isBookmark,
+ bookmarkId = this.bookmarkId,
+ content = this.content,
+ videoUrl = this.videoUrl,
+ previewUrl = this.previewUrl,
+ location = this.location,
+ uploaderId = this.uploaderId,
+ nickname = this.nickname,
+ isMine = this.isMine,
+)
diff --git a/domain/video/src/main/java/com/record/video/repository/VideoRepository.kt b/domain/video/src/main/java/com/record/video/repository/VideoRepository.kt
index 4fe1c8ca..c597fcb4 100644
--- a/domain/video/src/main/java/com/record/video/repository/VideoRepository.kt
+++ b/domain/video/src/main/java/com/record/video/repository/VideoRepository.kt
@@ -8,6 +8,7 @@ interface VideoRepository {
suspend fun getAllVideos(cursorId: Long, pageSize: Int): Result>
suspend fun getRecentVideos(keywords: List?, cursor: Long, pageSize: Int): Result>
suspend fun getPopularVideos(keywords: List?, pageNumber: Int, pageSize: Int): Result>
+ suspend fun getPlaceVideos(placeId: Int, cursor: Long, pageSize: Int): Result>
suspend fun getUserVideos(otherUserId: Long, cursorId: Long, size: Int): Result>
suspend fun getMyVideos(cursorId: Long, size: Int): Result>
suspend fun getFollowingVideos(cursorId: Long, size: Int): Result>
diff --git a/feature/detail/build.gradle.kts b/feature/detail/build.gradle.kts
index 7c41f5eb..bf9b56c1 100644
--- a/feature/detail/build.gradle.kts
+++ b/feature/detail/build.gradle.kts
@@ -7,4 +7,5 @@ android {
}
dependencies {
implementation(projects.domain.video)
+ implementation(projects.domain.exhibition)
}
diff --git a/feature/detail/src/main/java/com/record/detail/DetailScreen.kt b/feature/detail/src/main/java/com/record/detail/DetailScreen.kt
index e6909a40..43b7c0ad 100644
--- a/feature/detail/src/main/java/com/record/detail/DetailScreen.kt
+++ b/feature/detail/src/main/java/com/record/detail/DetailScreen.kt
@@ -53,7 +53,7 @@ fun DetailRoute(
padding: PaddingValues,
modifier: Modifier = Modifier,
viewModel: DetailpageViewModel = hiltViewModel(),
- navigateToUplaod: () -> Unit,
+ navigateToUpload: () -> Unit,
navigateToVideo: (VideoType, Long) -> Unit,
) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
@@ -82,7 +82,8 @@ fun DetailRoute(
navigateToVideo = viewModel::navigateToVideoDetail,
onLoadMoreReviews = viewModel::loadMoreReviewVideos,
onBookmarkClick = viewModel::bookmark,
- navigateToUpload = navigateToUplaod,
+ navigateToUpload = navigateToUpload,
+ onChipSelected = viewModel::selectChip,
)
}
}
@@ -96,6 +97,7 @@ fun DetailpageScreen(
navigateToUpload: () -> Unit,
onLoadMoreReviews: () -> Unit,
onBookmarkClick: (Long) -> Unit,
+ onChipSelected: (ChipTab) -> Unit,
) {
val pagerState = rememberPagerState(
initialPage = state.detailpageTab.ordinal,
@@ -178,10 +180,10 @@ fun DetailpageScreen(
ListScreen(
exhibitionItems = state.exhibitionList,
exhibitionCount = state.exhibitionCount,
- selectedChip = selectedChipState.value,
+ selectedChip = state.selectedChip,
onItemClick = {},
onChipSelected = { selectedChip ->
- selectedChipState.value = selectedChip
+ onChipSelected(selectedChip)
},
)
}
@@ -220,7 +222,7 @@ fun CustomTabRow(
val animatedIndicatorWidth by animateDpAsState(
targetValue = tabWidth - 12.dp,
- animationSpec = tween(200),
+ animationSpec = tween(0),
)
val density = LocalDensity.current
diff --git a/feature/detail/src/main/java/com/record/detail/DetailpageContract.kt b/feature/detail/src/main/java/com/record/detail/DetailpageContract.kt
index 0a80d45d..bb2ec294 100644
--- a/feature/detail/src/main/java/com/record/detail/DetailpageContract.kt
+++ b/feature/detail/src/main/java/com/record/detail/DetailpageContract.kt
@@ -1,21 +1,25 @@
package com.record.detail
+import com.record.detail.screen.ChipTab
+import com.record.exhibition.model.Exhibition
+import com.record.model.VideoData
import com.record.model.VideoType
import com.record.ui.base.SideEffect
import com.record.ui.base.UiState
-import com.record.video.model.VideoData
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
data class DetailpageState(
- val placeName: String = "국립현대미술관",
- val placeAddress: String = "서울특별시 종로구 삼청로 30",
+ val placeId: Long = 0,
+ val placeName: String = "",
+ val placeAddress: String = "",
val exhibitionCount: Int = 0,
val reviewVideoCount: Int = 0,
val reviewCursor: Long = 0,
val reviewIsEnd: Boolean = false,
val detailpageTab: DetailpageTab = DetailpageTab.LIST,
- val exhibitionList: ImmutableList> = emptyList>().toImmutableList(),
+ val selectedChip: ChipTab = ChipTab.ALL,
+ val exhibitionList: ImmutableList = emptyList().toImmutableList(),
val reviewList: ImmutableList = emptyList().toImmutableList(),
) : UiState
diff --git a/feature/detail/src/main/java/com/record/detail/DetailpageViewModel.kt b/feature/detail/src/main/java/com/record/detail/DetailpageViewModel.kt
index c016d53c..c874bc72 100644
--- a/feature/detail/src/main/java/com/record/detail/DetailpageViewModel.kt
+++ b/feature/detail/src/main/java/com/record/detail/DetailpageViewModel.kt
@@ -1,8 +1,16 @@
package com.record.detail
+import android.util.Log
+import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope
+import com.record.detail.navigation.DetailRoute
+import com.record.detail.screen.ChipTab
+import com.record.exhibition.model.ExhibitionFilter
+import com.record.exhibition.repository.ExhibitionRepository
+import com.record.model.VideoData
import com.record.model.VideoType
import com.record.ui.base.BaseViewModel
+import com.record.video.model.toCore
import com.record.video.repository.VideoRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.collections.immutable.toImmutableList
@@ -13,19 +21,64 @@ import javax.inject.Inject
@HiltViewModel
class DetailpageViewModel @Inject constructor(
private val videoRepository: VideoRepository,
+ private val exhibitionRepository: ExhibitionRepository,
+ savedStateHandle: SavedStateHandle,
) : BaseViewModel(DetailpageState()) {
+ private val placeIdString = savedStateHandle.get(DetailRoute.PLACE_ID)
+ init {
+ intent {
+ copy(placeId = placeIdString?.toLong() ?: 0)
+ }
+ selectChip(uiState.value.selectedChip)
+ }
fun selectTab(tab: DetailpageTab) {
intent {
copy(detailpageTab = tab)
}
}
+ fun selectChip(chip: ChipTab) {
+ intent {
+ copy(selectedChip = chip)
+ }
+ viewModelScope.launch {
+ exhibitionRepository.getExhibitions(
+ placeId = uiState.value.placeId,
+ filter = when (uiState.value.selectedChip) {
+ ChipTab.ALL -> ExhibitionFilter.DEFAULT
+ ChipTab.FREE -> ExhibitionFilter.FREE
+ ChipTab.ENDING_SOON -> ExhibitionFilter.CLOSING
+ },
+ ).onSuccess {
+ intent {
+ copy(
+ exhibitionList = it.toImmutableList(),
+ )
+ }
+ }.onFailure {
+ Log.e("이잉", it.message.toString())
+ }
+ }
+ }
+
fun navigateToVideoDetail(type: VideoType, videoId: Long) {
postSideEffect(DetailpageSideEffect.NavigateToVideoDetail(type, videoId))
}
- fun fetchPlaceInfo() {
+ fun fetchPlaceInfo() = viewModelScope.launch {
+ exhibitionRepository.getPlaceById(uiState.value.placeId).onSuccess {
+ intent {
+ copy(
+ placeAddress = it.address,
+ placeName = it.name,
+ exhibitionCount = it.exhibitionCount,
+ reviewVideoCount = it.recordCount,
+ reviewList = it.exhibitionRecord?.toImmutableList() ?: emptyList().toImmutableList(),
+ )
+ }
+ }.onFailure {
+ }
}
fun initialData() = viewModelScope.launch {
@@ -39,7 +92,7 @@ class DetailpageViewModel @Inject constructor(
val reviewVideo = reviewRes.getOrThrow()
intent {
copy(
- reviewList = reviewVideo.data.toImmutableList(),
+ reviewList = reviewVideo.data.map { it.toCore() }.toImmutableList(),
reviewCursor = reviewVideo.nextCursor?.toLong() ?: 0,
reviewIsEnd = false,
)
@@ -54,7 +107,7 @@ class DetailpageViewModel @Inject constructor(
intent {
copy(
reviewCursor = it.nextCursor?.toLong() ?: 0,
- reviewList = (list + it.data).toImmutableList(),
+ reviewList = (list + it.data.map { it.toCore() }).toImmutableList(),
)
}
if (!it.hasNext) {
diff --git a/feature/detail/src/main/java/com/record/detail/navigation/DetailNavigation.kt b/feature/detail/src/main/java/com/record/detail/navigation/DetailNavigation.kt
index ed2fc4e5..52c44b33 100644
--- a/feature/detail/src/main/java/com/record/detail/navigation/DetailNavigation.kt
+++ b/feature/detail/src/main/java/com/record/detail/navigation/DetailNavigation.kt
@@ -7,10 +7,11 @@ import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavOptions
import androidx.navigation.compose.composable
import com.record.detail.DetailRoute
+import com.record.detail.navigation.DetailRoute.PLACE_ID
import com.record.model.VideoType
-fun NavController.navigateDetail(navOptions: NavOptions) {
- navigate(DetailRoute.route, navOptions)
+fun NavController.navigateDetail(placeId: Long, navOptions: NavOptions) {
+ navigate(DetailRoute.detailRoute(placeId.toString()), navOptions)
}
fun NavGraphBuilder.detailNavGraph(
@@ -19,16 +20,22 @@ fun NavGraphBuilder.detailNavGraph(
navigateToUpload: () -> Unit,
navigateToVideo: (VideoType, Long) -> Unit,
) {
- composable(route = DetailRoute.route) {
+ composable(
+ route = DetailRoute.detailRoute(
+ "{$PLACE_ID}",
+ ),
+ ) {
DetailRoute(
padding = padding,
modifier = modifier,
navigateToVideo = navigateToVideo,
- navigateToUplaod = navigateToUpload,
+ navigateToUpload = navigateToUpload,
)
}
}
object DetailRoute {
- const val route = "search"
+ const val route = "place-detail"
+ const val PLACE_ID = "place-id"
+ fun detailRoute(placeId: String) = "$route/$placeId"
}
diff --git a/feature/detail/src/main/java/com/record/detail/screen/ListScreen.kt b/feature/detail/src/main/java/com/record/detail/screen/ListScreen.kt
index e0377e89..3357022d 100644
--- a/feature/detail/src/main/java/com/record/detail/screen/ListScreen.kt
+++ b/feature/detail/src/main/java/com/record/detail/screen/ListScreen.kt
@@ -25,11 +25,12 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import com.record.designsystem.R
import com.record.designsystem.theme.RecordyTheme
+import com.record.exhibition.model.Exhibition
import kotlinx.collections.immutable.ImmutableList
@Composable
fun ListScreen(
- exhibitionItems: ImmutableList>,
+ exhibitionItems: ImmutableList,
exhibitionCount: Int,
selectedChip: ChipTab,
onChipSelected: (ChipTab) -> Unit,
@@ -74,7 +75,7 @@ fun ListScreen(
}
}
item { Spacer(modifier = Modifier.height(16.dp)) }
- if (exhibitionCount == 0) {
+ if (exhibitionItems.size == 0) {
item {
EmptyDataScreen(
message = "\n진행 중인 전시가 없어요.",
@@ -83,16 +84,13 @@ fun ListScreen(
}
} else {
items(exhibitionItems) { item ->
- val (name, startDate, endDate) = item
- Column {
- ExhibitionItem(
- name = name,
- startDate = startDate,
- endDate = endDate,
- onButtonClick = { },
- )
- Spacer(modifier = Modifier.height(16.dp))
- }
+ ExhibitionItem(
+ name = item.name,
+ startDate = item.startDate,
+ endDate = item.endDate,
+ onButtonClick = { },
+ )
+ Spacer(modifier = Modifier.height(16.dp))
}
}
}
diff --git a/feature/detail/src/main/java/com/record/detail/screen/ReviewScreen.kt b/feature/detail/src/main/java/com/record/detail/screen/ReviewScreen.kt
index 1fe067df..37fa6541 100644
--- a/feature/detail/src/main/java/com/record/detail/screen/ReviewScreen.kt
+++ b/feature/detail/src/main/java/com/record/detail/screen/ReviewScreen.kt
@@ -22,9 +22,9 @@ import androidx.compose.ui.text.withStyle
import androidx.compose.ui.unit.dp
import com.record.designsystem.component.RecordyVideoThumbnail
import com.record.designsystem.theme.RecordyTheme
+import com.record.model.VideoData
import com.record.model.VideoType
import com.record.ui.scroll.OnBottomReached
-import com.record.video.model.VideoData
import kotlinx.collections.immutable.ImmutableList
@Composable
diff --git a/feature/home/build.gradle.kts b/feature/home/build.gradle.kts
index 141c606f..94ded6cb 100644
--- a/feature/home/build.gradle.kts
+++ b/feature/home/build.gradle.kts
@@ -9,6 +9,8 @@ android {
dependencies {
implementation(projects.domain.video)
implementation(projects.domain.keyword)
+ implementation(projects.domain.exhibition)
implementation(libs.lottie.compose)
implementation(libs.collapsing.toolbar)
+ implementation(libs.google.location)
}
diff --git a/feature/home/src/main/AndroidManifest.xml b/feature/home/src/main/AndroidManifest.xml
index a5918e68..769b825c 100644
--- a/feature/home/src/main/AndroidManifest.xml
+++ b/feature/home/src/main/AndroidManifest.xml
@@ -1,4 +1,5 @@
-
\ No newline at end of file
+
+
diff --git a/feature/home/src/main/java/com/record/home/Exhibition.kt b/feature/home/src/main/java/com/record/home/Exhibition.kt
new file mode 100644
index 00000000..c1378fa2
--- /dev/null
+++ b/feature/home/src/main/java/com/record/home/Exhibition.kt
@@ -0,0 +1,12 @@
+package com.record.home
+
+import com.record.video.model.VideoData
+import kotlinx.collections.immutable.ImmutableList
+import kotlinx.collections.immutable.toImmutableList
+
+data class Exhibition(
+ val location: String,
+ val name: String,
+ val exhibitionCount: Int,
+ val userVideo: ImmutableList = emptyList().toImmutableList(),
+)
diff --git a/feature/home/src/main/java/com/record/home/HomeContract.kt b/feature/home/src/main/java/com/record/home/HomeContract.kt
index 5da09f9c..f6731c58 100644
--- a/feature/home/src/main/java/com/record/home/HomeContract.kt
+++ b/feature/home/src/main/java/com/record/home/HomeContract.kt
@@ -1,22 +1,23 @@
package com.record.home
-import com.record.model.VideoType
+import com.record.exhibition.model.Place
import com.record.ui.base.SideEffect
import com.record.ui.base.UiState
-import com.record.video.model.VideoData
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
data class HomeState(
- val chipList: ImmutableList = listOf("전체").toImmutableList(),
- val popularList: ImmutableList = emptyList().toImmutableList(),
- val recentList: ImmutableList = emptyList().toImmutableList(),
- val selectedChipIndex: Int? = 0,
+ val exhibitionList: ImmutableList = emptyList().toImmutableList(),
val isLoading: Boolean = false,
+ val location: Location = Location(0.0, 0.0),
+ val page: Int = 0,
+ val isEnd: Boolean = false,
+ val showLocationPermissionDialog: Boolean = true,
) : UiState
sealed interface HomeSideEffect : SideEffect {
data object navigateToUpload : HomeSideEffect
- data class navigateToVideo(val id: Long, val type: VideoType, val keyword: String?) : HomeSideEffect
- data object collapseToolbar : HomeSideEffect
+ data class navigateToVideo(val id: Long, val location: String) : HomeSideEffect
+ data class navigateToDetail(val id: Long) : HomeSideEffect
+ data object launchSettingIntent : HomeSideEffect
}
diff --git a/feature/home/src/main/java/com/record/home/HomeScreen.kt b/feature/home/src/main/java/com/record/home/HomeScreen.kt
index c4e509b9..723a1478 100644
--- a/feature/home/src/main/java/com/record/home/HomeScreen.kt
+++ b/feature/home/src/main/java/com/record/home/HomeScreen.kt
@@ -1,91 +1,78 @@
package com.record.home
-import androidx.compose.foundation.Image
+import android.Manifest
+import android.content.pm.PackageManager
+import android.location.Location
+import android.util.Log
+import androidx.activity.compose.rememberLauncherForActivityResult
+import androidx.activity.result.contract.ActivityResultContracts
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.BoxScope
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
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.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.itemsIndexed
+import androidx.compose.foundation.lazy.rememberLazyListState
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.rememberCoroutineScope
-import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.alpha
-import androidx.compose.ui.graphics.Brush
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalConfiguration
+import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
+import androidx.core.app.ActivityCompat
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import com.airbnb.lottie.compose.LottieAnimation
-import com.airbnb.lottie.compose.LottieCompositionSpec
-import com.airbnb.lottie.compose.LottieConstants
-import com.airbnb.lottie.compose.animateLottieCompositionAsState
-import com.airbnb.lottie.compose.rememberLottieComposition
+import com.google.android.gms.location.FusedLocationProviderClient
+import com.google.android.gms.location.LocationServices
import com.record.designsystem.R
import com.record.designsystem.component.RecordyVideoThumbnail
-import com.record.designsystem.component.button.RecordyChipButton
+import com.record.designsystem.component.dialog.RecordyDialog
import com.record.designsystem.theme.RecordyTheme
-import com.record.home.component.UploadFloatingButton
+import com.record.exhibition.model.Place
import com.record.model.VideoType
-import com.record.ui.extension.customClickable
import com.record.ui.lifecycle.LaunchedEffectWithLifecycle
-import com.record.video.model.VideoData
+import com.record.ui.scroll.OnBottomReached
+import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.collectLatest
-import kotlinx.coroutines.launch
-import me.onebone.toolbar.CollapsingToolbarScaffold
-import me.onebone.toolbar.CollapsingToolbarScaffoldState
-import me.onebone.toolbar.CollapsingToolbarScope
-import me.onebone.toolbar.ExperimentalToolbarApi
-import me.onebone.toolbar.ScrollStrategy
-import me.onebone.toolbar.rememberCollapsingToolbarScaffoldState
-@OptIn(ExperimentalToolbarApi::class)
@Composable
fun HomeRoute(
padding: PaddingValues,
modifier: Modifier = Modifier,
viewModel: HomeViewModel = hiltViewModel(),
navigateToVideoDetail: (VideoType, Long, String?, Long) -> Unit,
+ navigateToPlaceDetail: (Long) -> Unit,
navigateToUpload: () -> Unit = {},
) {
val state by viewModel.uiState.collectAsStateWithLifecycle()
- val toolbarScaffoldState = rememberCollapsingToolbarScaffoldState()
- val coroutineScope = rememberCoroutineScope()
+
LaunchedEffectWithLifecycle {
- viewModel.getVideos()
viewModel.sideEffect.collectLatest { sideEffect ->
when (sideEffect) {
HomeSideEffect.navigateToUpload -> navigateToUpload()
is HomeSideEffect.navigateToVideo -> {
- navigateToVideoDetail(sideEffect.type, sideEffect.id, sideEffect.keyword, 0)
+ // navigateToVideoDetail(sideEffect.type, sideEffect.id, sideEffect.keyword, 0)
}
- HomeSideEffect.collapseToolbar -> {
- coroutineScope.launch {
- toolbarScaffoldState.toolbarState.collapse(500)
- }
+ HomeSideEffect.launchSettingIntent -> TODO()
+ is HomeSideEffect.navigateToDetail -> {
+ navigateToPlaceDetail(sideEffect.id)
}
}
}
@@ -93,271 +80,181 @@ fun HomeRoute(
HomeScreen(
modifier = modifier.padding(bottom = padding.calculateBottomPadding()),
state = state,
- toolbarState = toolbarScaffoldState,
- onUploadButtonClick = viewModel::navigateToUpload,
- onChipButtonClick = viewModel::selectCategory,
- onVideoClick = viewModel::navigateToVideo,
- onBookmarkClick = viewModel::bookmark,
+ showLocationPermissionDialog = viewModel::showLocationPermissionDialog,
+ updateLocation = viewModel::updateLocation,
+ getData = viewModel::getPlaces,
+ navigateToDetail = viewModel::navigateToDetail,
)
}
@Composable
fun HomeScreen(
modifier: Modifier = Modifier,
- toolbarState: CollapsingToolbarScaffoldState,
state: HomeState,
- onUploadButtonClick: () -> Unit,
- onChipButtonClick: (Int) -> Unit,
- onVideoClick: (Long, VideoType) -> Unit,
- onBookmarkClick: (Long) -> Unit,
+ showLocationPermissionDialog: (Boolean) -> Unit,
+ updateLocation: (Double, Double) -> Unit,
+ getData: () -> Unit,
+ navigateToDetail: (Long) -> Unit,
) {
- var boxSize by remember {
- mutableStateOf(IntSize.Zero)
- }
- Box(
- modifier = modifier
- .fillMaxSize()
- .onGloballyPositioned { layoutCoordinates ->
- boxSize = layoutCoordinates.size
+ val configuration = LocalConfiguration.current
+ val screenWidth = configuration.screenWidthDp.dp
+ val context = LocalContext.current
+ val launcher = rememberLauncherForActivityResult(
+ contract = ActivityResultContracts.RequestPermission(),
+ ) { isGranted ->
+ if (isGranted) {
+ val fusedLocationClient: FusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(context)
+
+ // 위치 정보 요청
+ if (ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
+ return@rememberLauncherForActivityResult
}
- .background(
- brush = Brush.verticalGradient(
- listOf(Color(0x339babfb), Color(0x00000000)),
- startY = boxSize.height.toFloat() * 0.0f,
- endY = boxSize.height.toFloat() * 0.3f,
- ),
- ),
- ) {
- BackgroundAnimation()
- CollapsingToolbar(
- toolbarState = toolbarState,
- state = state,
- onChipButtonClick = onChipButtonClick,
- onVideoClick = onVideoClick,
- onBookmarkClick = onBookmarkClick,
- )
- UploadFloatingButton(
- modifier = Modifier
- .align(Alignment.BottomEnd)
- .padding(bottom = 15.dp, end = 16.dp),
- onClick = onUploadButtonClick,
- )
- if (state.isLoading) {
- LoadingLottie()
+ fusedLocationClient.lastLocation.addOnSuccessListener { location: Location? ->
+ location?.let {
+ updateLocation(it.latitude, it.longitude)
+ Log.e("위치", "${it.latitude} ${it.longitude}")
+ getData()
+ }
+ }
+ showLocationPermissionDialog(false)
+ } else {
+ showLocationPermissionDialog(true)
}
}
-}
-
-@Composable
-fun BoxScope.BackgroundAnimation() {
- val composition by rememberLottieComposition(spec = LottieCompositionSpec.RawRes(R.raw.bubble))
- val progress by animateLottieCompositionAsState(
- composition,
- iterations = LottieConstants.IterateForever,
- speed = 1.0f,
- )
- LottieAnimation(
- composition,
- { progress },
- modifier = Modifier
- .align(Alignment.TopCenter),
- )
-}
-
-@Composable
-fun LoadingLottie() {
- val composition by rememberLottieComposition(spec = LottieCompositionSpec.RawRes(R.raw.loading_lotties))
- val progress by animateLottieCompositionAsState(
- composition,
- iterations = LottieConstants.IterateForever,
- speed = 4.0f,
- )
- Box(
- modifier = Modifier
- .fillMaxSize()
- .customClickable(rippleEnabled = false) {}
- .background(color = RecordyTheme.colors.black50),
- ) {
- LottieAnimation(
- composition,
- { progress },
- modifier = Modifier
- .align(Alignment.Center),
- )
+ val lazyColumnState = rememberLazyListState()
+ lazyColumnState.OnBottomReached(buffer = 2) {
+ getData()
}
-}
-@Composable
-fun CollapsingToolbar(
- toolbarState: CollapsingToolbarScaffoldState,
- state: HomeState,
- onChipButtonClick: (Int) -> Unit,
- onVideoClick: (Long, VideoType) -> Unit,
- onBookmarkClick: (Long) -> Unit,
-) {
- CollapsingToolbarScaffold(
- modifier = Modifier
- .fillMaxSize(),
- state = toolbarState,
- scrollStrategy = ScrollStrategy.ExitUntilCollapsed,
- enabled = true,
- toolbar = {
- ToolbarContent(toolbarState)
- },
- ) {
- ChipRow(toolbarState, state.chipList, state.selectedChipIndex, onChipButtonClick)
- Content(
- state = state,
- onVideoClick = onVideoClick,
- onBookmarkClick = onBookmarkClick,
+ LaunchedEffectWithLifecycle {
+ launcher.launch(
+ Manifest.permission.ACCESS_FINE_LOCATION,
)
}
-}
-@Composable
-fun CollapsingToolbarScope.ToolbarContent(toolbarState: CollapsingToolbarScaffoldState) {
- val topPadding = (32 + 12 * toolbarState.toolbarState.progress).dp
- val alpha = toolbarState.toolbarState.progress * 2 - 0.5f
- Image(
- painter = painterResource(R.drawable.ic_recordy_logo),
- contentDescription = "logo",
- modifier = Modifier
- .padding(start = 16.dp, top = topPadding, bottom = 12.dp)
- .pin(),
- )
Box(
- modifier = Modifier
- .fillMaxWidth()
- .height(246.dp)
- .road(Alignment.CenterEnd, Alignment.BottomStart)
- .alpha(alpha)
- .padding(start = 16.dp),
+ modifier = modifier
+ .fillMaxSize(),
) {
- Row(
+ LazyColumn(
+ state = lazyColumnState,
modifier = Modifier
- .fillMaxWidth()
- .align(Alignment.BottomCenter),
- horizontalArrangement = Arrangement.SpaceBetween,
- verticalAlignment = Alignment.Bottom,
+ .fillMaxSize(),
) {
- Text(
- text = "오늘은 어떤 키워드로\n공간을 둘러볼까요?",
- modifier = Modifier
- .weight(192f)
- .padding(bottom = 28.dp),
- style = RecordyTheme.typography.title1,
- color = RecordyTheme.colors.white,
- )
-
- Image(
- modifier = Modifier
- .weight(140f)
- .padding(end = 12.dp),
- painter = painterResource(R.drawable.img_home_graphic),
- contentDescription = "home",
- )
+ item {
+ Box {
+ Icon(
+ modifier = Modifier
+ .align(Alignment.CenterStart)
+ .padding(start = 16.dp)
+ .padding(top = 66.dp, bottom = 32.dp),
+ painter = painterResource(id = R.drawable.ic_viskit_logo),
+ tint = RecordyTheme.colors.viskitYellow500,
+ contentDescription = "logo",
+ )
+ }
+ }
+ itemsIndexed(state.exhibitionList) { i, exhibition ->
+ ExhibitionContatiner(
+ modifier = Modifier.clickable {
+ navigateToDetail(exhibition.placeId.toLong())
+ },
+ exhibition,
+ screenWidth,
+ )
+ }
}
- }
-}
-@OptIn(ExperimentalToolbarApi::class)
-@Composable
-fun ChipRow(
- state: CollapsingToolbarScaffoldState,
- chipList: List,
- selectedChip: Int?,
- onChipButtonClick: (Int) -> Unit,
-) {
- LazyRow(
- modifier = Modifier
- .padding(bottom = 12.dp),
- horizontalArrangement = Arrangement.spacedBy(8.dp),
- ) {
- item { Spacer(modifier = Modifier.width(8.dp)) }
- itemsIndexed(chipList) { i, item ->
- RecordyChipButton(
- text = item,
- isActive = selectedChip == i,
- onClick = {
- onChipButtonClick(i)
- },
+ if (state.showLocationPermissionDialog) {
+ RecordyDialog(
+ graphicAsset = R.drawable.img_trashcan,
+ title = "필수 권한 허용해 주세요",
+ subTitle = "내 위치 기반 공간 추천을 위해\n사용자의 위치에 접근하도록 허용해 주세요.",
+ negativeButtonLabel = "취소",
+ positiveButtonLabel = "삭제",
+ onDismissRequest = { },
+ onPositiveButtonClick = { },
)
}
- item { Spacer(modifier = Modifier.width(8.dp)) }
}
}
@Composable
-fun Content(
- state: HomeState,
- onVideoClick: (Long, VideoType) -> Unit,
- onBookmarkClick: (Long) -> Unit,
-) {
- val configuration = LocalConfiguration.current
- val screenWidth = configuration.screenWidthDp.dp
- LazyColumn(
- modifier = Modifier
- .fillMaxWidth()
- .padding(top = 44.dp),
- ) {
- item {
- Section(
- title = "이번 주 인기 기록",
- videoList = state.popularList,
- screenWidth = screenWidth,
- onVideoClick = onVideoClick,
- onBookmarkClick = onBookmarkClick,
- videoType = VideoType.POPULAR,
- )
- Section(
- title = "방금 막 올라왔어요",
- videoList = state.recentList,
- screenWidth = screenWidth,
- onVideoClick = onVideoClick,
- onBookmarkClick = onBookmarkClick,
- videoType = VideoType.RECENT,
- )
- Spacer(modifier = Modifier.height(56.dp))
- }
- }
-}
-
-@Composable
-fun Section(
- title: String,
- videoList: List,
+private fun ExhibitionContatiner(
+ modifier: Modifier,
+ place: Place,
screenWidth: Dp,
- onVideoClick: (Long, VideoType) -> Unit,
- onBookmarkClick: (Long) -> Unit,
- videoType: VideoType,
+ onVideoClick: (Long, VideoType) -> Unit = { i, j -> },
+ onBookmarkClick: (Long) -> Unit = {},
+ videoType: VideoType = VideoType.RECENT,
) {
Column(
- modifier = Modifier
- .fillMaxWidth(),
+ modifier = modifier,
) {
- Text(
- text = title,
- style = RecordyTheme.typography.subtitle,
- color = RecordyTheme.colors.white,
+ Row(
modifier = Modifier
- .padding(horizontal = 16.dp)
- .padding(top = 16.dp, bottom = 12.dp),
- )
+ .fillMaxWidth()
+ .padding(horizontal = 16.dp, vertical = 16.dp)
+ .background(color = RecordyTheme.colors.gray10, shape = RoundedCornerShape(8.dp)),
+ horizontalArrangement = Arrangement.SpaceBetween,
+ ) {
+ Column(
+ modifier = Modifier.padding(16.dp),
+ verticalArrangement = Arrangement.spacedBy(6.dp),
+ ) {
+ if (place.address.isNotBlank()) {
+ Text(
+ text = place.address,
+ style = RecordyTheme.typography.caption1M,
+ color = RecordyTheme.colors.gray05,
+ )
+ }
+ Text(
+ text = place.name,
+ style = RecordyTheme.typography.title3,
+ color = RecordyTheme.colors.gray01,
+ )
+ Row {
+ Text(
+ text = place.exhibitionCount.toString() + "개",
+ style = RecordyTheme.typography.body2SB,
+ color = RecordyTheme.colors.viskitYellow500,
+ )
+ Text(
+ text = "의 전시가 진행 중이에요.",
+ style = RecordyTheme.typography.body2SB,
+ color = RecordyTheme.colors.gray02,
+ )
+ }
+ }
+ Icon(
+ modifier = Modifier
+ .align(Alignment.CenterVertically)
+ .padding(16.dp),
+ tint = RecordyTheme.colors.gray01,
+ painter = painterResource(id = R.drawable.ic_angle_right_24),
+ contentDescription = "next",
+ )
+ }
+
LazyRow(
horizontalArrangement = Arrangement.spacedBy(12.dp),
) {
item { Spacer(modifier = Modifier.width(4.dp)) }
- itemsIndexed(videoList) { index, videoData ->
- RecordyVideoThumbnail(
- modifier = Modifier.width(screenWidth / 8 * 3),
- imageUri = videoData.previewUrl,
- location = videoData.location,
- isBookmarkable = true,
- isBookmark = videoData.isBookmark,
- onClick = { onVideoClick(videoData.id, videoType) },
- onBookmarkClick = { onBookmarkClick(videoData.id) },
- )
+ if (place.exhibitionRecord != null) {
+ itemsIndexed(place.exhibitionRecord!!) { index, videoData ->
+ RecordyVideoThumbnail(
+ modifier = Modifier.width(screenWidth / 8 * 3),
+ imageUri = videoData.previewUrl,
+ location = videoData.location,
+ isBookmarkable = true,
+ isBookmark = videoData.isBookmark,
+ onClick = { onVideoClick(videoData.id, videoType) },
+ onBookmarkClick = { onBookmarkClick(videoData.id) },
+ )
+ }
}
+
item { Spacer(modifier = Modifier.width(4.dp)) }
}
}
@@ -367,5 +264,14 @@ fun Section(
@Composable
fun PreviewHome() {
RecordyTheme {
+ HomeScreen(
+ state = HomeState(
+ exhibitionList = emptyList().toImmutableList(),
+ ),
+ showLocationPermissionDialog = {},
+ updateLocation = { i, j -> },
+ getData = {},
+ navigateToDetail = {},
+ )
}
}
diff --git a/feature/home/src/main/java/com/record/home/HomeViewModel.kt b/feature/home/src/main/java/com/record/home/HomeViewModel.kt
index 965cb915..26b042f2 100644
--- a/feature/home/src/main/java/com/record/home/HomeViewModel.kt
+++ b/feature/home/src/main/java/com/record/home/HomeViewModel.kt
@@ -2,8 +2,8 @@ package com.record.home
import android.util.Log
import androidx.lifecycle.viewModelScope
-import com.record.keyword.repository.KeywordRepository
-import com.record.model.VideoType
+import com.record.exhibition.model.Place
+import com.record.exhibition.repository.ExhibitionRepository
import com.record.model.exception.ApiError
import com.record.ui.base.BaseViewModel
import com.record.video.repository.VideoRepository
@@ -15,139 +15,89 @@ import javax.inject.Inject
@HiltViewModel
class HomeViewModel @Inject constructor(
private val videoRepository: VideoRepository,
- private val keywordRepository: KeywordRepository,
+ private val exhibitionRepository: ExhibitionRepository,
) : BaseViewModel(HomeState()) {
- fun navigateToUpload() {
- postSideEffect(HomeSideEffect.navigateToUpload)
+ fun navigateToVideo(videoId: Long, location: String) {
+ postSideEffect(HomeSideEffect.navigateToVideo(videoId, location))
}
- fun selectCategory(categoryIndex: Int) {
- intent {
- copy(selectedChipIndex = categoryIndex)
- }
- postSideEffect(HomeSideEffect.collapseToolbar)
- getPopularVideos()
- getRecentVideos()
+ fun navigateToDetail(placeId: Long) {
+ postSideEffect(HomeSideEffect.navigateToDetail(placeId))
}
- fun getVideos() {
- getPreferenceKeywords()
- getPopularVideos()
- getRecentVideos()
- }
-
- private fun getPreferenceKeywords() {
- viewModelScope.launch {
- val newList = listOf("전체")
- keywordRepository.getKeywords().onSuccess {
+ fun getPlaces() = viewModelScope.launch {
+ if (uiState.value.isEnd) return@launch
+ val list = uiState.value.exhibitionList
+ exhibitionRepository.getNearPlaceData(uiState.value.page, 10, uiState.value.location.latitude, uiState.value.location.longitude)
+ .onSuccess {
intent {
- copy(chipList = (newList + it.keywords).toImmutableList())
+ copy(exhibitionList = (list + it.data).toImmutableList())
}
- }.onFailure {
- when (it) {
- is ApiError -> {
- Log.e("error", it.message)
- }
- }
- }
- }
- }
-
- private fun getRecentVideos() {
- viewModelScope.launch {
- val keyIndex = uiState.value.selectedChipIndex
- val keyword = if (keyIndex != null) listOf(uiState.value.chipList[keyIndex]) else null
- videoRepository.getRecentVideos(
- keywords = keyword,
- cursor = 0,
- pageSize = 10,
- ).onSuccess {
intent {
- copy(recentList = it.data.toImmutableList())
+ copy(isEnd = !it.hasNext, page = uiState.value.page + 1)
}
- }.onFailure {
- when (it) {
- is ApiError -> {
- Log.e("error", it.message)
- }
+ }
+ .onFailure { throwable ->
+ if (throwable is ApiError) {
+ Log.e("asdfasdf", throwable.message)
+ } else {
+ Log.e("asdfasdf", throwable.message.toString())
}
}
- }
}
- private fun getPopularVideos() {
- viewModelScope.launch {
- val keyIndex = uiState.value.selectedChipIndex
- val keyword = if (keyIndex != null) listOf(uiState.value.chipList[keyIndex]) else null
- videoRepository.getPopularVideos(
- keywords = keyword,
- pageNumber = 0,
- pageSize = 10,
- ).onSuccess {
- intent {
- copy(popularList = it.data.toImmutableList())
- }
- }.onFailure {
- when (it) {
- is ApiError -> {
- Log.e("error", it.message)
- }
- }
- }
- }
+ fun showLocationPermissionDialog(isShow: Boolean) = intent {
+ copy(showLocationPermissionDialog = isShow)
}
- fun navigateToVideo(videoId: Long, type: VideoType) {
- val selectedIndex = uiState.value.selectedChipIndex
- val selectedKeyword = if (selectedIndex != null) uiState.value.chipList[selectedIndex] else null
- postSideEffect(HomeSideEffect.navigateToVideo(videoId, type, selectedKeyword))
+ fun updateLocation(latitude: Double, longitude: Double) = intent {
+ copy(location = Location(latitude, longitude))
}
fun bookmark(id: Long) {
intent {
- val updatedRecentList = uiState.value.recentList.map { video ->
- if (video.id == id) {
- video.copy(isBookmark = !video.isBookmark)
- } else {
- video
- }
- }
-
- val updatedPopularList = uiState.value.popularList.map { video ->
- if (video.id == id) {
- video.copy(isBookmark = !video.isBookmark)
- } else {
- video
- }
+ val updatedList = uiState.value.exhibitionList.map { exhibition ->
+ Place(
+ placeId = exhibition.placeId,
+ address = exhibition.address,
+ name = exhibition.name,
+ exhibitionCount = exhibition.exhibitionCount,
+ recordCount = exhibition.recordCount,
+ exhibitionRecord = exhibition.exhibitionRecord?.map { video ->
+ if (video.id == id) {
+ video.copy(isBookmark = !video.isBookmark)
+ } else {
+ video
+ }
+ }?.toImmutableList(),
+ )
}
copy(
- recentList = updatedRecentList.toImmutableList(),
- popularList = updatedPopularList.toImmutableList(),
+ exhibitionList = updatedList.toImmutableList(),
)
}
viewModelScope.launch {
videoRepository.bookmark(id).onSuccess {
- val updatedRecentList1 = uiState.value.recentList.map { video ->
- if (video.id == id) {
- video.copy(isBookmark = it)
- } else {
- video
- }
- }
-
- val updatedPopularList1 = uiState.value.popularList.map { video ->
- if (video.id == id) {
- video.copy(isBookmark = it)
- } else {
- video
- }
+ val updatedList = uiState.value.exhibitionList.map { exhibition ->
+ Place(
+ placeId = exhibition.placeId,
+ address = exhibition.address,
+ name = exhibition.name,
+ exhibitionCount = exhibition.exhibitionCount,
+ recordCount = exhibition.recordCount,
+ exhibitionRecord = exhibition.exhibitionRecord?.map { video ->
+ if (video.id == id) {
+ video.copy(isBookmark = !video.isBookmark)
+ } else {
+ video
+ }
+ }?.toImmutableList(),
+ )
}
-
intent {
copy(
- recentList = updatedRecentList1.toImmutableList(),
- popularList = updatedPopularList1.toImmutableList(),
+ exhibitionList = updatedList.toImmutableList(),
)
}
}.onFailure {
diff --git a/feature/home/src/main/java/com/record/home/Location.kt b/feature/home/src/main/java/com/record/home/Location.kt
new file mode 100644
index 00000000..02e15874
--- /dev/null
+++ b/feature/home/src/main/java/com/record/home/Location.kt
@@ -0,0 +1,6 @@
+package com.record.home
+
+data class Location(
+ val latitude: Double,
+ val longitude: Double,
+)
diff --git a/feature/home/src/main/java/com/record/home/navigation/HomeNavigation.kt b/feature/home/src/main/java/com/record/home/navigation/HomeNavigation.kt
index 7be0353e..778b0d64 100644
--- a/feature/home/src/main/java/com/record/home/navigation/HomeNavigation.kt
+++ b/feature/home/src/main/java/com/record/home/navigation/HomeNavigation.kt
@@ -18,6 +18,7 @@ fun NavGraphBuilder.homeNavGraph(
modifier: Modifier = Modifier,
navigateToVideoDetail: (VideoType, Long, String?, Long) -> Unit,
navigateToUpload: () -> Unit = {},
+ navigateToPlaceDetail: (Long) -> Unit = {},
) {
composable(route = HomeRoute.route) {
HomeRoute(
@@ -25,6 +26,7 @@ fun NavGraphBuilder.homeNavGraph(
modifier = modifier,
navigateToVideoDetail = navigateToVideoDetail,
navigateToUpload = navigateToUpload,
+ navigateToPlaceDetail = navigateToPlaceDetail,
)
}
}
diff --git a/feature/mypage/src/main/java/com/record/mypage/MypageScreen.kt b/feature/mypage/src/main/java/com/record/mypage/MypageScreen.kt
index eb87468b..35408be6 100644
--- a/feature/mypage/src/main/java/com/record/mypage/MypageScreen.kt
+++ b/feature/mypage/src/main/java/com/record/mypage/MypageScreen.kt
@@ -256,7 +256,7 @@ fun CustomTabRow(
val animatedIndicatorWidth by animateDpAsState(
targetValue = tabWidth - 12.dp,
- animationSpec = tween(200),
+ animationSpec = tween(0),
)
val density = LocalDensity.current
diff --git a/feature/navigator/src/main/java/com/record/navigator/MainNavigator.kt b/feature/navigator/src/main/java/com/record/navigator/MainNavigator.kt
index 410dc9e5..ede29fa1 100644
--- a/feature/navigator/src/main/java/com/record/navigator/MainNavigator.kt
+++ b/feature/navigator/src/main/java/com/record/navigator/MainNavigator.kt
@@ -128,8 +128,8 @@ internal class MainNavigator(
navController.navigateSetting(navOptions { })
}
- fun navigateDetail() {
- navController.navigateDetail(navOptions { })
+ fun navigateDetail(id: Long) {
+ navController.navigateDetail(placeId = id, navOptions { })
}
fun navigateSearch() {
diff --git a/feature/navigator/src/main/java/com/record/navigator/MainScreen.kt b/feature/navigator/src/main/java/com/record/navigator/MainScreen.kt
index 3d0a0728..c8fadf6e 100644
--- a/feature/navigator/src/main/java/com/record/navigator/MainScreen.kt
+++ b/feature/navigator/src/main/java/com/record/navigator/MainScreen.kt
@@ -94,6 +94,7 @@ internal fun MainScreen(
padding = innerPadding,
navigateToVideoDetail = navigator::navigateVideoDetail,
navigateToUpload = navigator::navigateToUpload,
+ navigateToPlaceDetail = navigator::navigateDetail,
)
profileNavGraph(
diff --git a/feature/search/build.gradle.kts b/feature/search/build.gradle.kts
index c5b7cce5..5cc378ef 100644
--- a/feature/search/build.gradle.kts
+++ b/feature/search/build.gradle.kts
@@ -5,3 +5,7 @@ plugins {
android {
namespace = "com.record.search"
}
+
+dependencies {
+ implementation(projects.domain.exhibition)
+}
diff --git a/feature/search/src/main/java/com/record/search/SearchScreen.kt b/feature/search/src/main/java/com/record/search/SearchScreen.kt
index eae541cf..0662700b 100644
--- a/feature/search/src/main/java/com/record/search/SearchScreen.kt
+++ b/feature/search/src/main/java/com/record/search/SearchScreen.kt
@@ -32,6 +32,7 @@ import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import com.record.designsystem.R
import com.record.designsystem.theme.RecordyTheme
+import com.record.exhibition.model.SearchResult
import com.record.search.component.SearchBox
import com.record.search.component.SearchedContainerBtn
import com.record.search.component.SearchingContainerBtn
@@ -56,7 +57,7 @@ fun SearchScreen(
modifier: Modifier,
query: String,
onQueryChange: (String) -> Unit,
- items: List,
+ items: List,
) {
val keyboardController = LocalSoftwareKeyboardController.current
@@ -92,10 +93,10 @@ fun SearchScreen(
Column {
SearchedContainerBtn(
modifier = modifier.fillMaxWidth(),
- exhibitionName = item.exhibitionName,
- location = item.location,
- venue = item.venue,
- type = item.listOf,
+ exhibitionName = item.name,
+ location = item.address,
+ venue = item.name,
+ type = item.type,
)
HorizontalDivider(
modifier = modifier
@@ -118,9 +119,9 @@ fun SearchScreen(
items(items) { item ->
SearchingContainerBtn(
modifier = modifier.fillMaxWidth(),
- exhibitionName = item.exhibitionName,
- location = item.location,
- venue = item.venue,
+ exhibitionName = item.name,
+ location = item.address,
+ venue = item.name,
)
}
}
@@ -191,6 +192,7 @@ fun DefaultSearchUI() {
modifier = Modifier.weight(1f),
) {
Text(
+ modifier = Modifier.padding(bottom = 2.dp),
text = "공간뿐만 아니라 원하는 전시회를 찾고 싶다면?",
style = RecordyTheme.typography.caption1M,
color = RecordyTheme.colors.gray05,
@@ -199,7 +201,7 @@ fun DefaultSearchUI() {
Row(
modifier = Modifier
.fillMaxWidth()
- .padding(bottom = 2.dp),
+ .padding(top = 4.dp),
) {
Text(
text = "\'전시회명\'",
diff --git a/feature/search/src/main/java/com/record/search/SearchState.kt b/feature/search/src/main/java/com/record/search/SearchState.kt
index 56df46a4..7e08fc0d 100644
--- a/feature/search/src/main/java/com/record/search/SearchState.kt
+++ b/feature/search/src/main/java/com/record/search/SearchState.kt
@@ -1,5 +1,6 @@
package com.record.search
+import com.record.exhibition.model.SearchResult
import com.record.ui.base.SideEffect
import com.record.ui.base.UiState
import kotlinx.collections.immutable.ImmutableList
@@ -7,7 +8,7 @@ import kotlinx.collections.immutable.toImmutableList
data class SearchState(
val query: String = "",
- val filteredItems: ImmutableList = emptyList().toImmutableList(),
+ val filteredItems: ImmutableList = emptyList().toImmutableList(),
) : UiState
sealed interface SearchSideEffect : SideEffect
diff --git a/feature/search/src/main/java/com/record/search/SearchViewModel.kt b/feature/search/src/main/java/com/record/search/SearchViewModel.kt
index ac661dfc..578ee178 100644
--- a/feature/search/src/main/java/com/record/search/SearchViewModel.kt
+++ b/feature/search/src/main/java/com/record/search/SearchViewModel.kt
@@ -1,41 +1,36 @@
package com.record.search
import androidx.lifecycle.viewModelScope
+import com.record.exhibition.repository.SearchRepository
import com.record.ui.base.BaseViewModel
+import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.collections.immutable.toImmutableList
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.launch
+import javax.inject.Inject
-class SearchViewModel : BaseViewModel(
+@HiltViewModel
+class SearchViewModel @Inject constructor(
+ private val searchRepository: SearchRepository,
+) : BaseViewModel(
initialState = SearchState(),
) {
- private val items = listOf(
- ExhibitionData("국립현대미술관", "서울 종로구", "전시회장", listOf("미술전시회1", "전시회2", "전시회3")),
- ExhibitionData("국으로 시작하는 단어", "서울 종로구", "전시회장", listOf("미술", "현대미술")),
- ExhibitionData("서울 예술의 전당", "서울 서초구", "전시회장", listOf("음악")),
- ExhibitionData("D Museum", "서울 용산구", "미술관", listOf("사진", "디자인")),
- )
+ init {
+ viewModelScope.launch {
+ uiState.debounce(200).collectLatest {
+ searchRepository.searchExhibition(it.query).onSuccess {
+ intent {
+ copy(filteredItems = it.toImmutableList())
+ }
+ }
+ }
+ }
+ }
fun onQueryChanged(newQuery: String) {
intent {
copy(query = newQuery)
}
- filterItems(newQuery)
- }
-
- private fun filterItems(query: String) {
- viewModelScope.launch {
- val result = if (query.isEmpty()) {
- items
- } else {
- items.filter {
- it.exhibitionName.contains(query, ignoreCase = true) ||
- it.location.contains(query, ignoreCase = true) ||
- it.venue.contains(query, ignoreCase = true)
- }
- }
- intent {
- copy(filteredItems = result.toImmutableList())
- }
- }
}
}
diff --git a/feature/search/src/main/java/com/record/search/component/SearchBox.kt b/feature/search/src/main/java/com/record/search/component/SearchBox.kt
index 5b8384ee..832c0b4b 100644
--- a/feature/search/src/main/java/com/record/search/component/SearchBox.kt
+++ b/feature/search/src/main/java/com/record/search/component/SearchBox.kt
@@ -18,6 +18,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
+import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
@@ -84,6 +85,9 @@ fun SearchBox(
}
innerTextField()
},
+ cursorBrush = SolidColor(RecordyTheme.colors.gray02),
+ maxLines = 1,
+ singleLine = true,
)
}
}
diff --git a/feature/search/src/main/java/com/record/search/component/SearchedContainerBtn.kt b/feature/search/src/main/java/com/record/search/component/SearchedContainerBtn.kt
index 290336bd..ea99382f 100644
--- a/feature/search/src/main/java/com/record/search/component/SearchedContainerBtn.kt
+++ b/feature/search/src/main/java/com/record/search/component/SearchedContainerBtn.kt
@@ -20,6 +20,7 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.record.designsystem.R
import com.record.designsystem.theme.RecordyTheme
+import com.record.exhibition.model.ResultType
@Composable
fun SearchedContainerBtn(
@@ -27,7 +28,7 @@ fun SearchedContainerBtn(
exhibitionName: String,
location: String,
venue: String,
- type: List,
+ type: ResultType,
) {
Box(
modifier = modifier
@@ -77,10 +78,10 @@ fun SearchedContainerBtn(
Column(
modifier = modifier.padding(horizontal = 8.dp),
) {
- type.take(3).forEachIndexed { index, title ->
+ /*type.take(3).forEachIndexed { index, title ->
if (index > 0) Spacer(modifier = modifier.height(8.dp))
ExhibitionTitle(type = title)
- }
+ }*/
}
Spacer(modifier = modifier.height(24.dp))
@@ -96,7 +97,7 @@ fun SearchedContainerBtnPreview() {
exhibitionName = "전시회명",
location = "위치",
venue = "장소",
- type = listOf("전시회", "공간", "장소"),
+ type = ResultType.PLACE,
)
}
}
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 71518883..1b3c8753 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -61,6 +61,7 @@ google-service = "4.4.2"
material = "1.12.0"
firebase-bom = "33.1.1"
crashlytics = "3.0.2"
+google-location = "21.3.0"
# Compose Versions
compose-compiler = "1.5.1"
@@ -219,6 +220,7 @@ firebase-database = { group = "com.google.firebase", name = "firebase-database-k
accompanist-insets = { module = "com.google.accompanist:accompanist-insets", version.ref = "accompanistInsets" }
accompanist-permissions = { module = "com.google.accompanist:accompanist-permissions", version.ref = "accompanistPermissions" }
accompanist-systemuicontroller = { module = "com.google.accompanist:accompanist-systemuicontroller", version.ref = "accompanistPermissions" }
+google-location = { group = "com.google.android.gms", name = "play-services-location", version.ref = "google-location"}
# AWS
aws-android-sdk-cognito = { module = "com.amazonaws:aws-android-sdk-cognito", version.ref = "awsAndroidSdkMobileClient" }
diff --git a/remote/exhibition/.gitignore b/remote/exhibition/.gitignore
new file mode 100644
index 00000000..42afabfd
--- /dev/null
+++ b/remote/exhibition/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/remote/exhibition/build.gradle.kts b/remote/exhibition/build.gradle.kts
new file mode 100644
index 00000000..897070d3
--- /dev/null
+++ b/remote/exhibition/build.gradle.kts
@@ -0,0 +1,11 @@
+plugins {
+ alias(libs.plugins.recordy.remote)
+}
+
+android {
+ namespace = "com.record.exhibition"
+}
+
+dependencies {
+ implementation(projects.data.exhibition)
+}
diff --git a/remote/exhibition/src/main/AndroidManifest.xml b/remote/exhibition/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..8bdb7e14
--- /dev/null
+++ b/remote/exhibition/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/remote/exhibition/src/main/java/com/record/exhibition/api/ExhibitionApi.kt b/remote/exhibition/src/main/java/com/record/exhibition/api/ExhibitionApi.kt
new file mode 100644
index 00000000..784275c5
--- /dev/null
+++ b/remote/exhibition/src/main/java/com/record/exhibition/api/ExhibitionApi.kt
@@ -0,0 +1,44 @@
+package com.record.exhibition.api
+
+import com.example.exhibition.model.remote.request.RequestPatchExhibitionDto
+import com.example.exhibition.model.remote.request.RequestPostExhibitionDto
+import com.example.exhibition.model.remote.response.ResponseGetExhibitionsDto
+import retrofit2.http.Body
+import retrofit2.http.DELETE
+import retrofit2.http.GET
+import retrofit2.http.PATCH
+import retrofit2.http.POST
+import retrofit2.http.Path
+import retrofit2.http.Query
+
+interface ExhibitionApi {
+ @GET("/api/v1/exhibitions")
+ suspend fun getExhibitionByPlaceId(
+ @Query("placeId") placeId: Int,
+ ): List
+
+ @POST("/api/v1/exhibitions")
+ suspend fun postExhibition(
+ @Body requestPostExhibitionDto: RequestPostExhibitionDto,
+ )
+
+ @PATCH("/api/v1/exhibitions")
+ suspend fun patchExhibition(
+ @Body requestPatchExhibitionDto: RequestPatchExhibitionDto,
+ )
+
+ @GET("/api/v1/exhibitions/free")
+ suspend fun getFreeExhibitions(
+ @Query("placeId") placeId: Int,
+ ): List
+
+ @GET("/api/v1/exhibitions/closing")
+ suspend fun getClosingExhibitions(
+ @Query("placeId") placeId: Int,
+ ): List
+
+ @DELETE("/api/v1/exhibitions/{exhibitionId}")
+ suspend fun deleteExhibitionById(
+ @Path("exhibitionId") exhibitionId: Int,
+ )
+}
diff --git a/remote/exhibition/src/main/java/com/record/exhibition/api/PlaceApi.kt b/remote/exhibition/src/main/java/com/record/exhibition/api/PlaceApi.kt
new file mode 100644
index 00000000..66b6eeb4
--- /dev/null
+++ b/remote/exhibition/src/main/java/com/record/exhibition/api/PlaceApi.kt
@@ -0,0 +1,43 @@
+package com.record.exhibition.api
+
+import com.example.exhibition.model.remote.request.RequestPostPlaceDto
+import com.example.exhibition.model.remote.response.ResponseGetPagingPlaceDto
+import com.example.exhibition.model.remote.response.ResponseGetPlaceDto
+import com.example.exhibition.model.remote.response.ResponseGetReviewsDto
+import retrofit2.http.Body
+import retrofit2.http.GET
+import retrofit2.http.POST
+import retrofit2.http.Path
+import retrofit2.http.Query
+
+interface PlaceApi {
+ @POST("/api/v1/places")
+ suspend fun postPlace(
+ @Body requestPostPlaceDto: RequestPostPlaceDto,
+ )
+
+ @GET("/api/v1/places/{id}")
+ suspend fun getPlaceById(
+ @Path("id") id: Int,
+ ): ResponseGetPlaceDto
+
+ @GET("/api/v1/places/{id}/reviews")
+ suspend fun getReviewsById(
+ @Path("id") id: Int,
+ ): List
+
+ @GET("/api/v1/places/exhibitions/geography")
+ suspend fun getNearPlaces(
+ @Query("number") number: Int,
+ @Query("size") size: Int,
+ @Query("latitude") latitude: Double,
+ @Query("longitude") longitude: Double,
+ @Query("distance") distance: Double,
+ ): ResponseGetPagingPlaceDto
+
+ @GET("/api/v1/places/exhibitions/date")
+ suspend fun getHasInProgressExhibitionPlaces(
+ @Query("number") number: Int,
+ @Query("size") size: Int,
+ ): List
+}
diff --git a/remote/exhibition/src/main/java/com/record/exhibition/api/SearchApi.kt b/remote/exhibition/src/main/java/com/record/exhibition/api/SearchApi.kt
new file mode 100644
index 00000000..f9d753a8
--- /dev/null
+++ b/remote/exhibition/src/main/java/com/record/exhibition/api/SearchApi.kt
@@ -0,0 +1,12 @@
+package com.record.exhibition.api
+
+import com.example.exhibition.model.remote.response.ResponseGetExhibitionSearchDto
+import retrofit2.http.GET
+import retrofit2.http.Query
+
+interface SearchApi {
+ @GET("/api/v1/search")
+ suspend fun getExhibitionSearch(
+ @Query("query") query: String,
+ ): List
+}
diff --git a/remote/exhibition/src/main/java/com/record/exhibition/datasource/RemoteExhibitionDataSourceImpl.kt b/remote/exhibition/src/main/java/com/record/exhibition/datasource/RemoteExhibitionDataSourceImpl.kt
new file mode 100644
index 00000000..dc7abd9e
--- /dev/null
+++ b/remote/exhibition/src/main/java/com/record/exhibition/datasource/RemoteExhibitionDataSourceImpl.kt
@@ -0,0 +1,35 @@
+package com.record.exhibition.datasource
+
+import com.example.exhibition.model.remote.request.RequestPatchExhibitionDto
+import com.example.exhibition.model.remote.request.RequestPostExhibitionDto
+import com.example.exhibition.source.remote.RemoteExhibitionDataSource
+import com.record.exhibition.api.ExhibitionApi
+import javax.inject.Inject
+
+class RemoteExhibitionDataSourceImpl @Inject constructor(
+ private val exhibitionApi: ExhibitionApi,
+) : RemoteExhibitionDataSource {
+ override suspend fun postExhibition(
+ requestPostExhibitionDto: RequestPostExhibitionDto,
+ ) = exhibitionApi.postExhibition(requestPostExhibitionDto)
+
+ override suspend fun getExhibitionById(
+ placeId: Int,
+ ) = exhibitionApi.getExhibitionByPlaceId(placeId)
+
+ override suspend fun getFreeExhibition(
+ placeId: Int,
+ ) = exhibitionApi.getFreeExhibitions(placeId)
+
+ override suspend fun getClosingExhibition(
+ placeId: Int,
+ ) = exhibitionApi.getClosingExhibitions(placeId)
+
+ override suspend fun patchExhibition(
+ requestPatchExhibitionDto: RequestPatchExhibitionDto,
+ ) = exhibitionApi.patchExhibition(requestPatchExhibitionDto)
+
+ override suspend fun deleteExhibition(
+ exhibitionId: Int,
+ ) = exhibitionApi.deleteExhibitionById(exhibitionId)
+}
diff --git a/remote/exhibition/src/main/java/com/record/exhibition/datasource/RemotePlaceDataSourceImpl.kt b/remote/exhibition/src/main/java/com/record/exhibition/datasource/RemotePlaceDataSourceImpl.kt
new file mode 100644
index 00000000..d6bebab9
--- /dev/null
+++ b/remote/exhibition/src/main/java/com/record/exhibition/datasource/RemotePlaceDataSourceImpl.kt
@@ -0,0 +1,28 @@
+package com.record.exhibition.datasource
+
+import com.example.exhibition.model.remote.request.RequestPostPlaceDto
+import com.example.exhibition.model.remote.response.ResponseGetPagingPlaceDto
+import com.example.exhibition.model.remote.response.ResponseGetPlaceDto
+import com.example.exhibition.model.remote.response.ResponseGetReviewsDto
+import com.example.exhibition.source.remote.RemotePlaceDataSource
+import com.record.exhibition.api.PlaceApi
+import javax.inject.Inject
+
+class RemotePlaceDataSourceImpl @Inject constructor(
+ private val placeApi: PlaceApi,
+) : RemotePlaceDataSource {
+ override suspend fun postPlace(requestPostPlaceDto: RequestPostPlaceDto) =
+ placeApi.postPlace(requestPostPlaceDto)
+
+ override suspend fun getPlaceById(id: Int): ResponseGetPlaceDto =
+ placeApi.getPlaceById(id)
+
+ override suspend fun getReviewsById(id: Int): List =
+ placeApi.getReviewsById(id)
+
+ override suspend fun getNearPlace(number: Int, size: Int, latitude: Double, longitude: Double, distance: Double): ResponseGetPagingPlaceDto =
+ placeApi.getNearPlaces(number, size, latitude, longitude, distance)
+
+ override suspend fun getHasInProgressExhibitionPlaces(number: Int, size: Int): List =
+ placeApi.getHasInProgressExhibitionPlaces(number, size)
+}
diff --git a/remote/exhibition/src/main/java/com/record/exhibition/datasource/RemoteSearchDataSourceImpl.kt b/remote/exhibition/src/main/java/com/record/exhibition/datasource/RemoteSearchDataSourceImpl.kt
new file mode 100644
index 00000000..4bc0f6fe
--- /dev/null
+++ b/remote/exhibition/src/main/java/com/record/exhibition/datasource/RemoteSearchDataSourceImpl.kt
@@ -0,0 +1,12 @@
+package com.record.exhibition.datasource
+
+import com.example.exhibition.model.remote.response.ResponseGetExhibitionSearchDto
+import com.example.exhibition.source.remote.RemoteSearchDataSource
+import com.record.exhibition.api.SearchApi
+import javax.inject.Inject
+
+class RemoteSearchDataSourceImpl @Inject constructor(
+ private val searchApi: SearchApi,
+) : RemoteSearchDataSource {
+ override suspend fun searchExhibition(query: String): List = searchApi.getExhibitionSearch(query)
+}
diff --git a/remote/exhibition/src/main/java/com/record/exhibition/di/ApiModule.kt b/remote/exhibition/src/main/java/com/record/exhibition/di/ApiModule.kt
new file mode 100644
index 00000000..fab8b8c8
--- /dev/null
+++ b/remote/exhibition/src/main/java/com/record/exhibition/di/ApiModule.kt
@@ -0,0 +1,29 @@
+package com.record.exhibition.di
+
+import com.record.exhibition.api.ExhibitionApi
+import com.record.exhibition.api.PlaceApi
+import com.record.exhibition.api.SearchApi
+import com.record.network.di.Auth
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import retrofit2.Retrofit
+import retrofit2.create
+import javax.inject.Singleton
+
+@InstallIn(SingletonComponent::class)
+@Module
+object ApiModule {
+ @Provides
+ @Singleton
+ fun providesExhibitionApi(@Auth retrofit: Retrofit): ExhibitionApi = retrofit.create()
+
+ @Provides
+ @Singleton
+ fun providesPlaceApi(@Auth retrofit: Retrofit): PlaceApi = retrofit.create()
+
+ @Provides
+ @Singleton
+ fun providesSearchApi(@Auth retrofit: Retrofit): SearchApi = retrofit.create()
+}
diff --git a/remote/exhibition/src/main/java/com/record/exhibition/di/DataModule.kt b/remote/exhibition/src/main/java/com/record/exhibition/di/DataModule.kt
new file mode 100644
index 00000000..d342f515
--- /dev/null
+++ b/remote/exhibition/src/main/java/com/record/exhibition/di/DataModule.kt
@@ -0,0 +1,29 @@
+package com.record.exhibition.di
+
+import com.example.exhibition.source.remote.RemoteExhibitionDataSource
+import com.example.exhibition.source.remote.RemotePlaceDataSource
+import com.example.exhibition.source.remote.RemoteSearchDataSource
+import com.record.exhibition.datasource.RemoteExhibitionDataSourceImpl
+import com.record.exhibition.datasource.RemotePlaceDataSourceImpl
+import com.record.exhibition.datasource.RemoteSearchDataSourceImpl
+import dagger.Binds
+import dagger.Module
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import javax.inject.Singleton
+
+@InstallIn(SingletonComponent::class)
+@Module
+abstract class DataModule {
+ @Binds
+ @Singleton
+ abstract fun bindsRemoteExhibitionDataSource(remoteExhibitionDataSourceImpl: RemoteExhibitionDataSourceImpl): RemoteExhibitionDataSource
+
+ @Binds
+ @Singleton
+ abstract fun bindsRemotePlaceDataSource(remotePlaceDataSourceImpl: RemotePlaceDataSourceImpl): RemotePlaceDataSource
+
+ @Binds
+ @Singleton
+ abstract fun bindsRemoteSearchDataSource(remoteSearchDataSourceImpl: RemoteSearchDataSourceImpl): RemoteSearchDataSource
+}
diff --git a/remote/video/src/main/java/com/record/video/api/VideoApi.kt b/remote/video/src/main/java/com/record/video/api/VideoApi.kt
index 9880957b..0f644433 100644
--- a/remote/video/src/main/java/com/record/video/api/VideoApi.kt
+++ b/remote/video/src/main/java/com/record/video/api/VideoApi.kt
@@ -30,6 +30,13 @@ interface VideoApi {
@Query("pageSize") pageSize: Int,
): ResponseGetPagingVideoDto
+ @GET("/api/v1/records/place")
+ suspend fun getPlaceVideos(
+ @Query("placeId") placeId: Int,
+ @Query("cursorId") cursor: Long,
+ @Query("size") pageSize: Int,
+ ): ResponseGetSliceVideoDto
+
@GET("/api/v1/records/user/{otherUserId}")
suspend fun getUserVideos(
@Path("otherUserId") otherUserId: Long,
diff --git a/remote/video/src/main/java/com/record/video/datasource/RemoteVideoDataSourceImpl.kt b/remote/video/src/main/java/com/record/video/datasource/RemoteVideoDataSourceImpl.kt
index 84d2bbbf..23a23a8c 100644
--- a/remote/video/src/main/java/com/record/video/datasource/RemoteVideoDataSourceImpl.kt
+++ b/remote/video/src/main/java/com/record/video/datasource/RemoteVideoDataSourceImpl.kt
@@ -23,6 +23,9 @@ class RemoteVideoDataSourceImpl @Inject constructor(
override suspend fun getPopularVideos(keywords: List?, pageNumber: Int, pageSize: Int): ResponseGetPagingVideoDto =
videoApi.getPopularVideos(keywords, pageNumber, pageSize)
+ override suspend fun getPlaceVideos(placeId: Int, cursor: Long, pageSize: Int): ResponseGetSliceVideoDto =
+ videoApi.getPlaceVideos(placeId, cursor, pageSize)
+
override suspend fun getUserVideos(otherUserId: Long, cursorId: Long, size: Int): ResponseGetSliceVideoDto =
videoApi.getUserVideos(otherUserId, cursorId, size)
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 1a21edf0..ceb5f068 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -64,3 +64,6 @@ include(":core:security")
include(":local:video")
include(":feature:search")
include(":feature:detail")
+include(":domain:exhibition")
+include(":remote:exhibition")
+include(":data:exhibition")