Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโ€™ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/#102 video detail api #107

Merged
merged 13 commits into from
Jul 18, 2024
1 change: 1 addition & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

<application
android:name=".RecordyApplication"
android:largeHeap="true"
Copy link
Member

Choose a reason for hiding this comment

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

Heap ํฌ๊ธฐ๊นŒ์ง€ ์‹ ๊ฒฝ์“ฐ๋‹ค๋‹ˆ..

android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
Expand Down
8 changes: 8 additions & 0 deletions core/cache/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
plugins {
alias(libs.plugins.recordy.android.library)
alias(libs.plugins.recordy.android.hilt)
}

android {
namespace = "com.record.cache"
}
4 changes: 4 additions & 0 deletions core/cache/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

</manifest>
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ fun RecordyLocationBadge(
maxLines = 1,
overflow = TextOverflow.Ellipsis,
style = RecordyTheme.typography.caption1,
color = RecordyTheme.colors.gray01,
color = RecordyTheme.colors.white,
textAlign = TextAlign.Center,
)
}
Expand Down
2 changes: 1 addition & 1 deletion core/model/src/main/java/com/record/model/Cursor.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ package com.record.model

data class Cursor<T>(
val hasNext: Boolean,
val nextCursor: Int,
val nextCursor: Int?,
val data: List<T>,
)
27 changes: 27 additions & 0 deletions core/ui/src/main/java/com/record/ui/scroll/OnBottomReached.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.record.ui.scroll

import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.grid.LazyGridState
import androidx.compose.foundation.pager.PagerState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
Expand Down Expand Up @@ -42,6 +43,32 @@ fun LazyListState.OnBottomReached(

fun LazyListState.isScrolledToEnd() = layoutInfo.visibleItemsInfo.lastOrNull()?.index == layoutInfo.totalItemsCount - 1

@Composable
fun LazyGridState.OnBottomReached(
buffer: Int = 0,
onLoadMore: () -> Unit,
) {
require(buffer >= 0) { "buffer cannot be negative, but was $buffer" }

val shouldLoadMore = remember {
derivedStateOf {
val lastVisibleItem = layoutInfo.visibleItemsInfo.lastOrNull() ?: return@derivedStateOf false

lastVisibleItem.index >= layoutInfo.totalItemsCount - 1 - buffer
}
}
LaunchedEffect(shouldLoadMore) {
snapshotFlow { shouldLoadMore.value }
.collectLatest {
if (it) {
onLoadMore()
}
}
}
}

fun LazyGridState.isScrolledToEnd() = layoutInfo.visibleItemsInfo.lastOrNull()?.index == layoutInfo.totalItemsCount - 1

@OptIn(ExperimentalFoundationApi::class)
@Composable
fun PagerState.onBottomReached(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ data class ResponseGetSliceVideoDto(
@SerialName("hasNext")
val hasNext: Boolean,
@SerialName("nextCursor")
val nextCursor: Int,
val nextCursor: Int?,
)

fun ResponseGetSliceVideoDto.toCore() = Cursor(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ import com.record.model.exception.ApiError
import com.record.video.model.VideoData
import com.record.video.model.remote.response.toCore
import com.record.video.model.remote.response.toDomain
import com.record.video.source.local.LocalUserInfoDataSource
import com.record.video.source.remote.RemoteVideoDataSource
import retrofit2.HttpException
import javax.inject.Inject

class VideoRepositoryImpl @Inject constructor(
private val remoteVideoDataSource: RemoteVideoDataSource,
private val localUserInfoDataSource: LocalUserInfoDataSource,
) : VideoRepository {
override suspend fun getAllVideos(cursorId: Long, pageSize: Int): Result<List<VideoData>> = runCatching {
remoteVideoDataSource.getAllVideos(cursorId, pageSize)
Expand All @@ -30,10 +32,10 @@ class VideoRepositoryImpl @Inject constructor(
}
}

override suspend fun getRecentVideos(keywords: List<String>?, pageNumber: Int, pageSize: Int): Result<Cursor<VideoData>> = runCatching {
override suspend fun getRecentVideos(keywords: List<String>?, cursor: Long, pageSize: Int): Result<Cursor<VideoData>> = runCatching {
val encodedKeywords = keywords?.map { it.replace(" ", "_") }?.map { toUTF8(it) }

remoteVideoDataSource.getRecentVideos(encodedKeywords, pageNumber, pageSize)
remoteVideoDataSource.getRecentVideos(encodedKeywords, cursor, pageSize)
}.mapCatching {
it.toCore()
}.recoverCatching { exception ->
Expand Down Expand Up @@ -81,6 +83,26 @@ class VideoRepositoryImpl @Inject constructor(
}
}

override suspend fun getMyVideos(cursorId: Long, size: Int): Result<Cursor<VideoData>> = runCatching {
remoteVideoDataSource.getUserVideos(
localUserInfoDataSource.getMyId(),
cursorId,
size,
)
}.mapCatching {
it.toCore()
}.recoverCatching { exception ->
when (exception) {
is HttpException -> {
throw ApiError(exception.message())
}

else -> {
throw exception
}
}
Comment on lines +92 to +103
Copy link
Contributor

Choose a reason for hiding this comment

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

์—๋Ÿฌ ์ฒ˜๋ฆฌ ์‹œ mapCatching์ด๋ž‘ recoverCatching ๊ฐ™์€ ๊ฑด ๋ชป ๋ณด๋˜ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ์ธ๋ฐ .. ํฅ๋ฏธ๋กญ๋„ค์š”๐Ÿ‘๐Ÿป ์„œ์น˜ํ•ด๋ณด๋‹ˆ ์ข‹์€ ์ฒ˜๋ฆฌ ๊ฐ™์•„ ์ €๋„ ์‡ฝ์ƒฅ ํ•ด๊ฐˆ๊ฒŒ์š” ใ…‹ใ…‹

Copy link
Member

Choose a reason for hiding this comment

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

์ €๋„ mapCating๊ตฌ๋ฌธ ์ž˜ ๋ชจ๋ฅด๋Š”๋ฐ ๋•๋ถ„์— ์ฐธ๊ณ ๊ฐ€ ๋์–ด์š”

}

override suspend fun getFollowingVideos(userId: Long, cursorId: Long, size: Int): Result<Cursor<VideoData>> = runCatching {
remoteVideoDataSource.getFollowingVideos(userId, cursorId, size)
}.mapCatching {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.record.video.source.local

interface LocalUserInfoDataSource {
suspend fun getMyId(): Long
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import com.record.video.model.remote.response.ResponseGetVideoDto

interface RemoteVideoDataSource {
suspend fun getAllVideos(cursorId: Long, size: Int): List<ResponseGetVideoDto>
suspend fun getRecentVideos(keywords: List<String>?, pageNumber: Int, pageSize: Int): ResponseGetSliceVideoDto
suspend fun getRecentVideos(keywords: List<String>?, cursor: Long, pageSize: Int): ResponseGetSliceVideoDto
suspend fun getPopularVideos(keywords: List<String>?, pageNumber: Int, pageSize: Int): ResponseGetPagingVideoDto
suspend fun getUserVideos(otherUserId: Long, cursorId: Long, size: Int): ResponseGetSliceVideoDto
suspend fun getFollowingVideos(userId: Long, cursorId: Long, size: Int): ResponseGetSliceVideoDto
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ import com.record.video.model.VideoData

interface VideoRepository {
suspend fun getAllVideos(cursorId: Long, pageSize: Int): Result<List<VideoData>>
suspend fun getRecentVideos(keywords: List<String>?, pageNumber: Int, pageSize: Int): Result<Cursor<VideoData>>
suspend fun getRecentVideos(keywords: List<String>?, cursor: Long, pageSize: Int): Result<Cursor<VideoData>>
suspend fun getPopularVideos(keywords: List<String>?, pageNumber: Int, pageSize: Int): Result<Page<VideoData>>
suspend fun getUserVideos(otherUserId: Long, cursorId: Long, size: Int): Result<Cursor<VideoData>>
suspend fun getMyVideos(cursorId: Long, size: Int): Result<Cursor<VideoData>>
suspend fun getFollowingVideos(userId: Long, cursorId: Long, size: Int): Result<Cursor<VideoData>>
suspend fun getBookmarkVideos(cursorId: Long, size: Int): Result<Cursor<VideoData>>
suspend fun bookmark(videoId: Long): Result<Boolean>
Expand Down
9 changes: 5 additions & 4 deletions feature/home/src/main/java/com/record/home/HomeContract.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,18 @@ 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 HomeState(
val chipList: List<String> = emptyList<String>().toImmutableList(),
val popularList: List<VideoData> = emptyList<VideoData>().toImmutableList(),
val recentList: List<VideoData> = emptyList<VideoData>().toImmutableList(),
val chipList: ImmutableList<String> = emptyList<String>().toImmutableList(),
val popularList: ImmutableList<VideoData> = emptyList<VideoData>().toImmutableList(),
val recentList: ImmutableList<VideoData> = emptyList<VideoData>().toImmutableList(),
val selectedChipIndex: Int? = null,
val isLoading: Boolean = false,
) : UiState

sealed interface HomeSideEffect : SideEffect {
data object navigateToUpload : HomeSideEffect
data class navigateToVideo(val index: Long, val type: VideoType, val keyword: String?) : HomeSideEffect
data class navigateToVideo(val id: Long, val type: VideoType, val keyword: String?) : HomeSideEffect
}
18 changes: 15 additions & 3 deletions feature/home/src/main/java/com/record/home/HomeScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ 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
Expand Down Expand Up @@ -52,9 +53,11 @@ import com.record.ui.extension.customClickable
import com.record.ui.lifecycle.LaunchedEffectWithLifecycle
import com.record.video.model.VideoData
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

Expand All @@ -68,13 +71,14 @@ fun HomeRoute(
val state by viewModel.uiState.collectAsStateWithLifecycle()

LaunchedEffectWithLifecycle {
viewModel.getVideos()
viewModel.sideEffect.collectLatest { sideEffect ->
when (sideEffect) {
HomeSideEffect.navigateToUpload -> {
}

is HomeSideEffect.navigateToVideo -> {
navigateToVideoDetail(sideEffect.type, sideEffect.index, sideEffect.keyword, 0)
navigateToVideoDetail(sideEffect.type, sideEffect.id, sideEffect.keyword, 0)
}
}
}
Expand Down Expand Up @@ -191,7 +195,7 @@ fun CollapsingToolbar(
ToolbarContent(toolbarState)
},
) {
ChipRow(state.chipList, state.selectedChipIndex, onChipButtonClick)
ChipRow(toolbarState, state.chipList, state.selectedChipIndex, onChipButtonClick)
Content(
state = state,
onVideoClick = onVideoClick,
Expand Down Expand Up @@ -246,12 +250,15 @@ fun CollapsingToolbarScope.ToolbarContent(toolbarState: CollapsingToolbarScaffol
}
}

@OptIn(ExperimentalToolbarApi::class)
@Composable
fun ChipRow(
state: CollapsingToolbarScaffoldState,
chipList: List<String>,
selectedChip: Int?,
onChipButtonClick: (Int) -> Unit,
) {
val coroutineScope = rememberCoroutineScope()
LazyRow(
modifier = Modifier
.padding(bottom = 12.dp),
Expand All @@ -262,7 +269,12 @@ fun ChipRow(
RecordyChipButton(
text = item,
isActive = selectedChip == i,
onClick = { onChipButtonClick(i) },
onClick = {
onChipButtonClick(i)
coroutineScope.launch {
state.toolbarState.collapse(200)
}
},
)
}
item { Spacer(modifier = Modifier.width(8.dp)) }
Expand Down
26 changes: 13 additions & 13 deletions feature/home/src/main/java/com/record/home/HomeViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import com.record.model.exception.ApiError
import com.record.ui.base.BaseViewModel
import com.record.video.repository.VideoRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.launch
import okhttp3.internal.toImmutableList
import javax.inject.Inject

@HiltViewModel
Expand All @@ -18,12 +18,6 @@ class HomeViewModel @Inject constructor(
private val keywordRepository: KeywordRepository,
) : BaseViewModel<HomeState, HomeSideEffect>(HomeState()) {

init {
getPreferenceKeywords()
getPopularVideos()
getRecentVideos()
}

fun navigateToUpload() {
postSideEffect(HomeSideEffect.navigateToUpload)
}
Expand All @@ -36,11 +30,17 @@ class HomeViewModel @Inject constructor(
getRecentVideos()
}

fun getVideos() {
getPreferenceKeywords()
getPopularVideos()
getRecentVideos()
}

Comment on lines +33 to +38
Copy link
Contributor

@nagaeng nagaeng Jul 18, 2024

Choose a reason for hiding this comment

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

์›๋ž˜ init ์œผ๋กœ ๊ตฌํ˜„ํ•ด๋†“์œผ์…จ๋˜ ๊ฑด LaunchedEffect์—์„œ ์ฒ˜๋ฆฌํ•˜๋ ค๊ณ  ํ•จ์ˆ˜๋กœ ๋นผ์…จ๊ตฐ์š” !!!! ์ข‹์Šต๋‹ˆ๋‹ค ๐Ÿซก

private fun getPreferenceKeywords() {
viewModelScope.launch {
keywordRepository.getKeywords().onSuccess {
intent {
copy(chipList = it.keywords)
copy(chipList = it.keywords.toImmutableList())
}
}.onFailure {
when (it) {
Expand All @@ -58,7 +58,7 @@ class HomeViewModel @Inject constructor(
val keyword = if (keyIndex != null) listOf(uiState.value.chipList[keyIndex]) else null
videoRepository.getRecentVideos(
keywords = keyword,
pageNumber = 0,
cursor = 0,
pageSize = 10,
).onSuccess {
intent {
Expand Down Expand Up @@ -124,8 +124,8 @@ class HomeViewModel @Inject constructor(

Log.e("๋ฐ˜ํ™˜๊ฐ’", updatedPopularList.toString())
copy(
recentList = updatedRecentList,
popularList = updatedPopularList,
recentList = updatedRecentList.toImmutableList(),
popularList = updatedPopularList.toImmutableList(),
)
}
viewModelScope.launch {
Expand All @@ -151,8 +151,8 @@ class HomeViewModel @Inject constructor(
Log.e("๋ฐ˜ํ™˜๊ฐ’", updatedPopularList1.toString())
intent {
copy(
recentList = updatedRecentList1,
popularList = updatedPopularList1,
recentList = updatedRecentList1.toImmutableList(),
popularList = updatedPopularList1.toImmutableList(),
)
}
}.onFailure {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ data class MypageState(
val nickname: String = "",
val followerNum: Int = 0,
val followingNum: Int = 0,
val bookmarkCursor: Long = 0,
val bookmarkIsEnd: Boolean = false,
val recordCursor: Long = 0,
val recordIsEnd: Boolean = false,
val mypageTab: MypageTab = MypageTab.TASTE,
val preferences: ImmutableList<Pair<String, Int>> = emptyList<Pair<String, Int>>().toImmutableList(),
val myRecordList: ImmutableList<VideoData> = emptyList<VideoData>().toImmutableList(),
Expand Down
Loading