diff --git a/android/app/src/main/java/com/goliath/emojihub/data_sources/EmojiPagingSource.kt b/android/app/src/main/java/com/goliath/emojihub/data_sources/EmojiPagingSource.kt new file mode 100644 index 00000000..e83b8c84 --- /dev/null +++ b/android/app/src/main/java/com/goliath/emojihub/data_sources/EmojiPagingSource.kt @@ -0,0 +1,34 @@ +package com.goliath.emojihub.data_sources + +import androidx.paging.PagingSource +import androidx.paging.PagingState +import com.goliath.emojihub.data_sources.api.EmojiApi +import com.goliath.emojihub.models.EmojiDto +import javax.inject.Inject + +class EmojiPagingSource @Inject constructor( + private val api: EmojiApi +): PagingSource() { + override suspend fun load(params: LoadParams): LoadResult { + val cursor = params.key ?: 1 + val count = params.loadSize + return try { + val response = api.fetchEmojiList(1, cursor, count).body() + val data = response ?: listOf() + LoadResult.Page( + data = data, + prevKey = if (cursor == 1) null else cursor - 1, + nextKey = if (data.isEmpty()) null else cursor + 1 + ) + } catch (exception: Exception) { + LoadResult.Error(exception) + } + } + + override fun getRefreshKey(state: PagingState): Int? { + return state.anchorPosition?.let { anchorPosition -> + state.closestPageToPosition(anchorPosition)?.prevKey?.plus(1) + ?: state.closestPageToPosition(anchorPosition)?.nextKey?.minus(1) + } + } +} \ No newline at end of file diff --git a/android/app/src/main/java/com/goliath/emojihub/repositories/remote/EmojiRepository.kt b/android/app/src/main/java/com/goliath/emojihub/repositories/remote/EmojiRepository.kt index 647d1e9b..30277013 100644 --- a/android/app/src/main/java/com/goliath/emojihub/repositories/remote/EmojiRepository.kt +++ b/android/app/src/main/java/com/goliath/emojihub/repositories/remote/EmojiRepository.kt @@ -1,11 +1,18 @@ package com.goliath.emojihub.repositories.remote import android.util.Log +import androidx.paging.Pager +import androidx.paging.PagingConfig +import androidx.paging.PagingData +import com.goliath.emojihub.data_sources.EmojiPagingSource +import com.goliath.emojihub.data_sources.PostPagingSource import com.goliath.emojihub.data_sources.api.EmojiApi import com.goliath.emojihub.models.EmojiDto import com.goliath.emojihub.models.FetchEmojiListDto +import com.goliath.emojihub.models.PostDto import com.goliath.emojihub.models.UploadEmojiDto import com.google.gson.Gson +import kotlinx.coroutines.flow.Flow import okhttp3.MediaType import okhttp3.MultipartBody import okhttp3.RequestBody @@ -17,7 +24,7 @@ import javax.inject.Inject import javax.inject.Singleton interface EmojiRepository { - suspend fun fetchEmojiList(numLimit: Int): List + suspend fun fetchEmojiList(): Flow> suspend fun getEmojiWithId(id: String): EmojiDto? suspend fun uploadEmoji(videoFile: File, emojiDto: UploadEmojiDto): Boolean suspend fun saveEmoji(id: String): Response @@ -29,22 +36,11 @@ interface EmojiRepository { class EmojiRepositoryImpl @Inject constructor( private val emojiApi: EmojiApi ): EmojiRepository { - override suspend fun fetchEmojiList(numLimit: Int): List { -// val fetchEmojiListDto = FetchEmojiListDto(1, 0, 10) - try { - val response = emojiApi.fetchEmojiList(1, 1, 10) - - if(response.isSuccessful && response.body() != null) { - Log.d("Fetch_E_L", "Successfully fetched ${response.body()!!.size} emojis") - return response.body()!! - } else { - val errorBody = response.errorBody()?.string() ?: "Unknown error" - Log.d("Fetch_E_L", "Failed to fetch emojis: $errorBody") - } - } catch(e: Exception) { - Log.e("Fetch_E_L", "Error fetching emojis", e) - } - return listOf() + override suspend fun fetchEmojiList(): Flow> { + return Pager( + config = PagingConfig(pageSize = 10, initialLoadSize = 10, enablePlaceholders = false), + pagingSourceFactory = { EmojiPagingSource(emojiApi) } + ).flow } override suspend fun getEmojiWithId(id: String): EmojiDto? { diff --git a/android/app/src/main/java/com/goliath/emojihub/usecases/EmojiUseCase.kt b/android/app/src/main/java/com/goliath/emojihub/usecases/EmojiUseCase.kt index 1225ed50..e2925d0d 100644 --- a/android/app/src/main/java/com/goliath/emojihub/usecases/EmojiUseCase.kt +++ b/android/app/src/main/java/com/goliath/emojihub/usecases/EmojiUseCase.kt @@ -2,23 +2,30 @@ package com.goliath.emojihub.usecases import android.net.Uri import android.util.Log +import androidx.paging.PagingData +import androidx.paging.map import com.goliath.emojihub.data_sources.ApiErrorController import com.goliath.emojihub.models.CreatedEmoji +import com.goliath.emojihub.models.Emoji import com.goliath.emojihub.models.EmojiDto +import com.goliath.emojihub.models.Post import com.goliath.emojihub.models.UploadEmojiDto import com.goliath.emojihub.repositories.local.X3dRepository import com.goliath.emojihub.repositories.remote.EmojiRepository +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.map import java.io.File import javax.inject.Inject import javax.inject.Singleton interface EmojiUseCase { - val emojiListState: StateFlow> - suspend fun fetchEmojiList(numInt: Int) + val emojiList: StateFlow> + suspend fun updateEmojiList(data: PagingData) + suspend fun fetchEmojiList(): Flow> suspend fun createEmoji(videoUri: Uri, topK: Int): List suspend fun uploadEmoji(emojiUnicode: String, emojiLabel: String, videoFile: File): Boolean suspend fun saveEmoji(id: String): Boolean @@ -32,18 +39,15 @@ class EmojiUseCaseImpl @Inject constructor( private val errorController: ApiErrorController ): EmojiUseCase { - private val _emojiListState = MutableStateFlow>(emptyList()) - override val emojiListState: StateFlow> - get() = _emojiListState.asStateFlow() + private val _emojiList = MutableStateFlow>(PagingData.empty()) + override val emojiList: StateFlow> + get() = _emojiList - override suspend fun fetchEmojiList(numInt: Int) { - try{ - val emojiList = emojiRepository.fetchEmojiList(numInt) - _emojiListState.emit(emojiList) - Log.d("Fetch_E_L", "USECASE DONE: $emojiList") - } catch (e: Exception) { - errorController.setErrorState(-1) - } + override suspend fun updateEmojiList(data: PagingData) { + _emojiList.emit(data) + } + override suspend fun fetchEmojiList(): Flow> { + return emojiRepository.fetchEmojiList().map { it.map { dto -> Emoji(dto) } } } override suspend fun createEmoji(videoUri: Uri, topK: Int): List { diff --git a/android/app/src/main/java/com/goliath/emojihub/viewmodels/EmojiViewModel.kt b/android/app/src/main/java/com/goliath/emojihub/viewmodels/EmojiViewModel.kt index bf1761e6..f9010fdb 100644 --- a/android/app/src/main/java/com/goliath/emojihub/viewmodels/EmojiViewModel.kt +++ b/android/app/src/main/java/com/goliath/emojihub/viewmodels/EmojiViewModel.kt @@ -7,6 +7,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import androidx.paging.cachedIn import com.goliath.emojihub.models.CreatedEmoji import com.goliath.emojihub.models.Emoji import com.goliath.emojihub.usecases.EmojiUseCase @@ -28,19 +29,18 @@ class EmojiViewModel @Inject constructor( var currentEmoji: Emoji? = null var isBottomSheetShown by mutableStateOf(false) - private val _emojiList = MutableStateFlow>(emptyList()) - val emojiList: StateFlow> = _emojiList.asStateFlow() + val emojiList = emojiUseCase.emojiList private val _topK = 3 - fun fetchEmojiList(numInt: Int) + fun fetchEmojiList() { viewModelScope.launch { - emojiUseCase.fetchEmojiList(numInt) - - val emojis = emojiUseCase.emojiListState.value.map { dto -> Emoji(dto) } - _emojiList.emit(emojis) - Log.d("Fetch_E_L", "VIEWMODEL DONE: $emojis") + emojiUseCase.fetchEmojiList() + .cachedIn(viewModelScope) + .collect { + emojiUseCase.updateEmojiList(it) + } } } diff --git a/android/app/src/main/java/com/goliath/emojihub/views/EmojiPage.kt b/android/app/src/main/java/com/goliath/emojihub/views/EmojiPage.kt index 0c29ead0..598daa09 100644 --- a/android/app/src/main/java/com/goliath/emojihub/views/EmojiPage.kt +++ b/android/app/src/main/java/com/goliath/emojihub/views/EmojiPage.kt @@ -34,6 +34,7 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.sp import androidx.core.content.ContextCompat import androidx.hilt.navigation.compose.hiltViewModel +import androidx.paging.compose.collectAsLazyPagingItems import com.goliath.emojihub.LocalNavController import com.goliath.emojihub.NavigationDestination import com.goliath.emojihub.models.createDummyEmoji @@ -64,13 +65,13 @@ fun EmojiPage( } } + val emojiList = viewModel.emojiList.collectAsLazyPagingItems() + LaunchedEffect(Unit) { - viewModel.fetchEmojiList(10) + viewModel.fetchEmojiList() } - val emojiList = viewModel.emojiList.collectAsState().value - Column(Modifier.background(White)) { TopNavigationBar("Emoji", shouldNavigate = false) { IconButton(onClick = { @@ -107,10 +108,12 @@ fun EmojiPage( horizontalArrangement = Arrangement.spacedBy(4.dp), verticalArrangement = Arrangement.spacedBy(4.dp), ) { - items(emojiList, key = { it.id }) { emoji -> - EmojiCell(emoji = emoji) { - viewModel.currentEmoji = emoji - navController.navigate(NavigationDestination.PlayEmojiVideo) + items(emojiList.itemCount) { index -> + emojiList[index]?.let{ + EmojiCell(emoji = it) { selectedEmoji -> + viewModel.currentEmoji = selectedEmoji + navController.navigate(NavigationDestination.PlayEmojiVideo) + } } } }