From 49998c92d284d6ef2574c5e5dff3bd18e5d89fa8 Mon Sep 17 00:00:00 2001 From: Wooyeol Lee Date: Thu, 7 Dec 2023 01:19:41 +0900 Subject: [PATCH 1/5] refactor: move createVideoThumbNail method which needs application context to data_source (emojiDataSource) --- .../emojihub/data_sources/DataSourceModule.kt | 5 + .../data_sources/remote/EmojiDataSource.kt | 40 ++++++++ .../repositories/remote/EmojiRepository.kt | 95 +++++-------------- 3 files changed, 68 insertions(+), 72 deletions(-) create mode 100644 android/app/src/main/java/com/goliath/emojihub/data_sources/remote/EmojiDataSource.kt diff --git a/android/app/src/main/java/com/goliath/emojihub/data_sources/DataSourceModule.kt b/android/app/src/main/java/com/goliath/emojihub/data_sources/DataSourceModule.kt index 72ca0e40..01f122d7 100644 --- a/android/app/src/main/java/com/goliath/emojihub/data_sources/DataSourceModule.kt +++ b/android/app/src/main/java/com/goliath/emojihub/data_sources/DataSourceModule.kt @@ -2,6 +2,8 @@ package com.goliath.emojihub.data_sources import com.goliath.emojihub.data_sources.local.X3dDataSource import com.goliath.emojihub.data_sources.local.X3dDataSourceImpl +import com.goliath.emojihub.data_sources.remote.EmojiDataSource +import com.goliath.emojihub.data_sources.remote.EmojiDataSourceImpl import dagger.Binds import dagger.Module import dagger.hilt.InstallIn @@ -13,6 +15,9 @@ abstract class DataSourceModule { @Binds abstract fun bindsX3dDataSource(impl: X3dDataSourceImpl): X3dDataSource + @Binds + abstract fun bindsEmojiDataSource(impl: EmojiDataSourceImpl): EmojiDataSource + @Binds abstract fun bindsApiErrorController(impl: ApiErrorControllerImpl): ApiErrorController } \ No newline at end of file diff --git a/android/app/src/main/java/com/goliath/emojihub/data_sources/remote/EmojiDataSource.kt b/android/app/src/main/java/com/goliath/emojihub/data_sources/remote/EmojiDataSource.kt new file mode 100644 index 00000000..a7bf5e21 --- /dev/null +++ b/android/app/src/main/java/com/goliath/emojihub/data_sources/remote/EmojiDataSource.kt @@ -0,0 +1,40 @@ +package com.goliath.emojihub.data_sources.remote + +import android.content.Context +import android.graphics.Bitmap +import android.media.MediaMetadataRetriever +import android.util.Log +import dagger.hilt.android.qualifiers.ApplicationContext +import java.io.File +import java.io.FileOutputStream +import javax.inject.Inject + +interface EmojiDataSource { + fun createVideoThumbNail(videoFile: File): File? +} +class EmojiDataSourceImpl @Inject constructor( + @ApplicationContext private val context: Context +): EmojiDataSource { + + override fun createVideoThumbNail(videoFile: File): File? { + val retriever = MediaMetadataRetriever() + try { + retriever.setDataSource(videoFile.absolutePath) + val bitmap = retriever.frameAtTime + + bitmap?.let { + val thumbnailFile = File(context.cacheDir, "thumbnail_${videoFile.name}.jpg") + FileOutputStream(thumbnailFile).use { out -> + bitmap.compress(Bitmap.CompressFormat.JPEG, 75, out) + } + Log.d("EmojiDataSource", "Thumbnail created: ${thumbnailFile.absolutePath}") + return thumbnailFile + } + } catch (e: Exception) { + Log.e("EmojiDataSource", "ERROR creating thumbnail: ${e.message?:"Unknown error"}") + } finally { + retriever.release() + } + return null + } +} \ 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 6b84ba92..dc83017a 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,8 +1,5 @@ package com.goliath.emojihub.repositories.remote -import android.content.Context -import android.graphics.Bitmap -import android.media.MediaMetadataRetriever import android.util.Log import androidx.paging.Pager import androidx.paging.PagingConfig @@ -10,20 +7,17 @@ import androidx.paging.PagingData import com.goliath.emojihub.data_sources.EmojiFetchType import com.goliath.emojihub.data_sources.EmojiPagingSource import com.goliath.emojihub.data_sources.api.EmojiApi +import com.goliath.emojihub.data_sources.remote.EmojiDataSource import com.goliath.emojihub.models.EmojiDto import com.goliath.emojihub.models.UploadEmojiDto import com.google.gson.Gson -import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.flow.Flow import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.MultipartBody import okhttp3.RequestBody.Companion.asRequestBody import okhttp3.RequestBody.Companion.toRequestBody -import retrofit2.HttpException import retrofit2.Response import java.io.File -import java.io.FileOutputStream -import java.io.IOException import javax.inject.Inject import javax.inject.Singleton @@ -41,7 +35,7 @@ interface EmojiRepository { @Singleton class EmojiRepositoryImpl @Inject constructor( private val emojiApi: EmojiApi, - @ApplicationContext private val context: Context + private val emojiDataSource: EmojiDataSource ): EmojiRepository { override suspend fun fetchEmojiList(sortByDate: Int): Flow> { return Pager( @@ -75,86 +69,43 @@ class EmojiRepositoryImpl @Inject constructor( val videoFileRequestBody = videoFile.asRequestBody("video/mp4".toMediaTypeOrNull()) val videoFileMultipartBody = MultipartBody.Part.createFormData("file", videoFile.name, videoFileRequestBody) - val thumbnailFile = createVideoThumbnail(context, videoFile) + val thumbnailFile = emojiDataSource.createVideoThumbNail(videoFile) val thumbnailRequestBody = thumbnailFile!! .asRequestBody("image/jpg".toMediaTypeOrNull()) val thumbnailMultipartBody = MultipartBody.Part.createFormData("thumbnail", thumbnailFile.name, thumbnailRequestBody) - return try { - emojiApi.uploadEmoji(videoFileMultipartBody, thumbnailMultipartBody, emojiDtoRequestBody) - true - } - catch (e: IOException) { - Log.e("EmojiRepository", "IOException") - false - } - catch (e: HttpException) { - Log.e("EmojiRepository", "HttpException") - false - } - catch (e: Exception) { - Log.e("EmojiRepository", "Unknown Exception: ${e.message}") - false - } + return emojiApi.uploadEmoji(videoFileMultipartBody, thumbnailMultipartBody, emojiDtoRequestBody) + .isSuccessful } override suspend fun saveEmoji(id: String): Result { - return try { - val response = emojiApi.saveEmoji(id) - Log.d("EmojiRepository", "SaveEmoji Api response : ${response.code()}") - - if (response.isSuccessful) { - Log.d("EmojiRepository", "Successfully saved Emoji (Id: $id)") - Result.success(Unit) - } else { - Log.d("EmojiRepository", "Failed to save Emoji (Id: $id), ${response.code()}") - Result.failure(Exception("Failed to save Emoji (Id: $id), ${response.code()}")) - } - } catch (e: Exception) { - Result.failure(e) + val response = emojiApi.saveEmoji(id) + Log.d("EmojiRepository", "SaveEmoji Api response : ${response.code()}") + + return if (response.isSuccessful) { + Log.d("EmojiRepository", "Successfully saved Emoji (Id: $id)") + Result.success(Unit) + } else { + Log.d("EmojiRepository", "Failed to save Emoji (Id: $id), ${response.code()}") + Result.failure(Exception("Failed to save Emoji (Id: $id), ${response.code()}")) } } override suspend fun unSaveEmoji(id: String): Result { - return try { - val response = emojiApi.unSaveEmoji(id) - Log.d("EmojiRepository", "UnSaveEmoji Api response : ${response.code()}") - if (response.isSuccessful) { - Log.d("EmojiRepository", "Successfully unsaved Emoji (Id: $id)") - Result.success(Unit) - } else { - Log.d("EmojiRepository", "Failed to unsave Emoji (Id: $id), ${response.code()}") - Result.failure(Exception("Failed to unsave Emoji (Id: $id), ${response.code()}")) - } - } catch (e: Exception) { - Result.failure(e) + val response = emojiApi.unSaveEmoji(id) + Log.d("EmojiRepository", "UnSaveEmoji Api response : ${response.code()}") + + return if (response.isSuccessful) { + Log.d("EmojiRepository", "Successfully unsaved Emoji (Id: $id)") + Result.success(Unit) + } else { + Log.d("EmojiRepository", "Failed to unsave Emoji (Id: $id), ${response.code()}") + Result.failure(Exception("Failed to unsave Emoji (Id: $id), ${response.code()}")) } } override suspend fun deleteEmoji(id: String): Response { TODO("Not yet implemented") } - - fun createVideoThumbnail(context: Context, videoFile: File): File? { - val retriever = MediaMetadataRetriever() - try { - retriever.setDataSource(videoFile.absolutePath) - val bitmap = retriever.frameAtTime - - bitmap?.let { - val thumbnailFile = File(context.cacheDir, "thumbnail_${videoFile.name}.jpg") - FileOutputStream(thumbnailFile).use { out -> - bitmap.compress(Bitmap.CompressFormat.JPEG, 75, out) - } - Log.d("create_TN", "Thumbnail created: ${thumbnailFile.absolutePath}") - return thumbnailFile - } - } catch (e: Exception) { - Log.e("EmojiRepository_create_TN", "ERROR creating thumbnail: ${e.message?:"Unknown error"}") - } finally { - retriever.release() - } - return null - } } \ No newline at end of file From da37e04cb1c58b0871e47e59445a50a0cb213d32 Mon Sep 17 00:00:00 2001 From: Wooyeol Lee Date: Thu, 7 Dec 2023 01:21:32 +0900 Subject: [PATCH 2/5] refactor: handling exception on UseCase level, for HttpException set error state 500 --- .../data_sources/ApiErrorController.kt | 2 +- .../goliath/emojihub/usecases/EmojiUseCase.kt | 67 +++++++++++++++++-- 2 files changed, 62 insertions(+), 7 deletions(-) diff --git a/android/app/src/main/java/com/goliath/emojihub/data_sources/ApiErrorController.kt b/android/app/src/main/java/com/goliath/emojihub/data_sources/ApiErrorController.kt index 9715a26b..69f5a820 100644 --- a/android/app/src/main/java/com/goliath/emojihub/data_sources/ApiErrorController.kt +++ b/android/app/src/main/java/com/goliath/emojihub/data_sources/ApiErrorController.kt @@ -51,7 +51,7 @@ enum class CustomError( override fun body(): String = "이미 있는 계정입니다." }, INTERNAL_SERVER_ERROR(500) { - override fun body(): String = "접속 오류가 발생했습니다." + override fun body(): String = "네트워크 접속 오류가 발생했습니다." },; companion object { 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 e845e1a8..cf5c888b 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 @@ -5,6 +5,7 @@ import android.util.Log import androidx.paging.PagingData import androidx.paging.map import com.goliath.emojihub.data_sources.ApiErrorController +import com.goliath.emojihub.data_sources.CustomError import com.goliath.emojihub.models.CreatedEmoji import com.goliath.emojihub.models.Emoji import com.goliath.emojihub.models.UploadEmojiDto @@ -13,8 +14,11 @@ import com.goliath.emojihub.repositories.remote.EmojiRepository import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map +import retrofit2.HttpException import java.io.File +import java.io.IOException import javax.inject.Inject import javax.inject.Singleton @@ -66,15 +70,39 @@ class EmojiUseCaseImpl @Inject constructor( } override suspend fun fetchEmojiList(sortByDate: Int): Flow> { - return emojiRepository.fetchEmojiList(sortByDate).map { it.map { dto -> Emoji(dto) } } + return try { + emojiRepository.fetchEmojiList(sortByDate).map { it.map { dto -> Emoji(dto) } } + } catch (e: HttpException) { + errorController.setErrorState(CustomError.INTERNAL_SERVER_ERROR.statusCode) + flowOf(PagingData.empty()) + } catch (e: Exception) { + Log.e("EmojiUseCase", "Unknown Exception on fetchMyEmojiList: ${e.message}") + flowOf(PagingData.empty()) + } } override suspend fun fetchMyCreatedEmojiList(): Flow> { - return emojiRepository.fetchMyCreatedEmojiList().map { it.map { dto -> Emoji(dto) } } + return try { + emojiRepository.fetchMyCreatedEmojiList().map { it.map { dto -> Emoji(dto) } } + } catch (e: HttpException) { + errorController.setErrorState(CustomError.INTERNAL_SERVER_ERROR.statusCode) + flowOf(PagingData.empty()) + } catch (e: Exception) { + Log.e("EmojiUseCase", "Unknown Exception on fetchMyCreatedEmojiList: ${e.message}") + flowOf(PagingData.empty()) + } } override suspend fun fetchMySavedEmojiList(): Flow> { - return emojiRepository.fetchMySavedEmojiList().map { it.map { dto -> Emoji(dto) } } + return try { + emojiRepository.fetchMySavedEmojiList().map { it.map { dto -> Emoji(dto) } } + } catch (e: HttpException) { + errorController.setErrorState(CustomError.INTERNAL_SERVER_ERROR.statusCode) + flowOf(PagingData.empty()) + } catch (e: Exception) { + Log.e("EmojiUseCase", "Unknown Exception on fetchMySavedEmojiList: ${e.message}") + flowOf(PagingData.empty()) + } } override suspend fun createEmoji(videoUri: Uri, topK: Int): List { @@ -83,14 +111,41 @@ class EmojiUseCaseImpl @Inject constructor( override suspend fun uploadEmoji(emojiUnicode: String, emojiLabel: String, videoFile: File): Boolean { val dto = UploadEmojiDto(emojiUnicode, emojiLabel) - return emojiRepository.uploadEmoji(videoFile, dto) + return try { + emojiRepository.uploadEmoji(videoFile, dto) + } catch (e: IOException) { + Log.e("EmojiUseCase", "IOException") + false + } catch (e: HttpException) { + errorController.setErrorState(CustomError.INTERNAL_SERVER_ERROR.statusCode) + false + } catch (e: Exception) { + Log.e("EmojiUseCase", "Unknown Exception on uploadEmoji: ${e.message}") + false + } } override suspend fun saveEmoji(id: String): Result { - return emojiRepository.saveEmoji(id) + return try { + emojiRepository.saveEmoji(id) + } catch (e: HttpException) { + errorController.setErrorState(CustomError.INTERNAL_SERVER_ERROR.statusCode) + Result.failure(e) + } catch (e: Exception) { + Log.e("EmojiUseCase", "Unknown Exception on saveEmoji: ${e.message}") + Result.failure(e) + } } override suspend fun unSaveEmoji(id: String): Result { - return emojiRepository.unSaveEmoji(id) + return try { + emojiRepository.unSaveEmoji(id) + } catch (e: HttpException) { + errorController.setErrorState(CustomError.INTERNAL_SERVER_ERROR.statusCode) + Result.failure(e) + } catch (e: Exception) { + Log.e("EmojiUseCase", "Unknown Exception on unSaveEmoji: ${e.message}") + Result.failure(e) + } } } \ No newline at end of file From 0e10968d88e297cd06d14bddc3284a6c1a0ed2f7 Mon Sep 17 00:00:00 2001 From: Wooyeol Lee Date: Thu, 7 Dec 2023 03:48:11 +0900 Subject: [PATCH 3/5] refactor: use default emoji list, which is accessible from X3dUseCase --- .../repositories/local/X3dRepository.kt | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/android/app/src/main/java/com/goliath/emojihub/repositories/local/X3dRepository.kt b/android/app/src/main/java/com/goliath/emojihub/repositories/local/X3dRepository.kt index 469949c4..2d41f462 100644 --- a/android/app/src/main/java/com/goliath/emojihub/repositories/local/X3dRepository.kt +++ b/android/app/src/main/java/com/goliath/emojihub/repositories/local/X3dRepository.kt @@ -10,6 +10,7 @@ import javax.inject.Inject import javax.inject.Singleton interface X3dRepository { + val DEFAULT_EMOJI_LIST: List suspend fun createEmoji(videoUri: Uri, topK: Int): List } @@ -17,19 +18,20 @@ interface X3dRepository { class X3dRepositoryImpl @Inject constructor( private val x3dDataSource: X3dDataSource ): X3dRepository { + + // FIXME: Default emojis should be topK different emojis -> use just 3 emojis for now + override val DEFAULT_EMOJI_LIST = listOf( + CreatedEmoji("love it", "U+2764 U+FE0F"), + CreatedEmoji("like", "U+1F44D"), + CreatedEmoji("ok", "U+1F646") + ) companion object{ const val moduleName = "Hagrid/efficient_x3d_s_hagrid_float.pt" const val idToClassFileName = "Hagrid/hagrid_id_to_classname.json" const val classToUnicodeFileName = "Hagrid/hagrid_classname_to_unicode.json" const val SCORE_THRESHOLD = 0.4F - // FIXME: Default emojis should be topK different emojis - const val DEFAULT_EMOJI_NAME_1 = "love it" - const val DEFAULT_EMOJI_UNICODE_1 = "U+2764 U+FE0F" - const val DEFAULT_EMOJI_NAME_2 = "like" - const val DEFAULT_EMOJI_UNICODE_2 = "U+1F44D" - const val DEFAULT_EMOJI_NAME_3 = "ok" - const val DEFAULT_EMOJI_UNICODE_3 = "U+1F646" } + override suspend fun createEmoji(videoUri: Uri, topK: Int): List { val x3dModule = x3dDataSource.loadModule(moduleName) ?: return emptyList() @@ -57,9 +59,7 @@ class X3dRepositoryImpl @Inject constructor( val inferenceResults = x3dDataSource.runInference(x3dModule, videoTensor, topK) if (inferenceResults.isEmpty() || inferenceResults[0].score < SCORE_THRESHOLD) { Log.w("X3d Repository", "Score is lower than threshold, return default emoji") - return listOf(CreatedEmoji(DEFAULT_EMOJI_NAME_1, DEFAULT_EMOJI_UNICODE_1), - CreatedEmoji(DEFAULT_EMOJI_NAME_2, DEFAULT_EMOJI_UNICODE_2), - CreatedEmoji(DEFAULT_EMOJI_NAME_3, DEFAULT_EMOJI_UNICODE_3)) + return DEFAULT_EMOJI_LIST } return x3dDataSource.indexToCreatedEmojiList( inferenceResults, idToClassFileName, classToUnicodeFileName From d3cf35bf634469392b7a59ce9bbaee691fad485f Mon Sep 17 00:00:00 2001 From: Wooyeol Lee Date: Thu, 7 Dec 2023 03:53:56 +0900 Subject: [PATCH 4/5] fix: uploadEmoji return type from Boolean to Response --- .../goliath/emojihub/repositories/remote/EmojiRepository.kt | 5 ++--- .../emojihub/repositories/remote/EmojiRepositoryImplTest.kt | 4 ++-- .../com/goliath/emojihub/usecases/EmojiUseCaseImplTest.kt | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) 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 06ef7042..0b01cec6 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 @@ -25,7 +25,7 @@ interface EmojiRepository { suspend fun fetchMyCreatedEmojiList(): Flow> suspend fun fetchMySavedEmojiList(): Flow> suspend fun getEmojiWithId(id: String): EmojiDto? - suspend fun uploadEmoji(videoFile: File, emojiDto: UploadEmojiDto): Boolean + suspend fun uploadEmoji(videoFile: File, emojiDto: UploadEmojiDto): Response suspend fun saveEmoji(id: String): Response suspend fun unSaveEmoji(id: String): Response suspend fun deleteEmoji(id: String): Response @@ -61,7 +61,7 @@ class EmojiRepositoryImpl @Inject constructor( TODO("Not yet implemented") } - override suspend fun uploadEmoji(videoFile: File, emojiDto: UploadEmojiDto): Boolean { + override suspend fun uploadEmoji(videoFile: File, emojiDto: UploadEmojiDto): Response { val emojiDtoJson = Gson().toJson(emojiDto) val emojiDtoRequestBody = emojiDtoJson.toRequestBody("application/json".toMediaTypeOrNull()) @@ -75,7 +75,6 @@ class EmojiRepositoryImpl @Inject constructor( thumbnailFile.name, thumbnailRequestBody) return emojiApi.uploadEmoji(videoFileMultipartBody, thumbnailMultipartBody, emojiDtoRequestBody) - .isSuccessful } override suspend fun saveEmoji(id: String): Response { diff --git a/android/app/src/test/java/com/goliath/emojihub/repositories/remote/EmojiRepositoryImplTest.kt b/android/app/src/test/java/com/goliath/emojihub/repositories/remote/EmojiRepositoryImplTest.kt index f4689094..731a233a 100644 --- a/android/app/src/test/java/com/goliath/emojihub/repositories/remote/EmojiRepositoryImplTest.kt +++ b/android/app/src/test/java/com/goliath/emojihub/repositories/remote/EmojiRepositoryImplTest.kt @@ -130,12 +130,12 @@ class EmojiRepositoryImplTest { } returns File("sampleThumbnailFile") // when - val isUploaded = runBlocking { + val response = runBlocking { emojiRepositoryImpl.uploadEmoji(sampleVideoFile, sampleUploadEmojiDto) } // then coVerify(exactly = 1) { emojiApi.uploadEmoji(any(), any(), any()) } - assertTrue(isUploaded) + assertTrue(response.isSuccessful) } @Test diff --git a/android/app/src/test/java/com/goliath/emojihub/usecases/EmojiUseCaseImplTest.kt b/android/app/src/test/java/com/goliath/emojihub/usecases/EmojiUseCaseImplTest.kt index 9c517d2e..c5aa66b3 100644 --- a/android/app/src/test/java/com/goliath/emojihub/usecases/EmojiUseCaseImplTest.kt +++ b/android/app/src/test/java/com/goliath/emojihub/usecases/EmojiUseCaseImplTest.kt @@ -184,7 +184,7 @@ class EmojiUseCaseImplTest { val videoFile = File("sample.mp4") coEvery { emojiRepository.uploadEmoji(videoFile, any()) - } returns true + } returns Response.success(Unit) // when val isUploaded = runBlocking { emojiUseCaseImpl.uploadEmoji(emojiUnicode, emojiLabel, videoFile) From 94fa84acc318da74331373cc2f3437c601666627 Mon Sep 17 00:00:00 2001 From: Wooyeol Lee Date: Thu, 7 Dec 2023 03:55:38 +0900 Subject: [PATCH 5/5] refactor: catch ConnectionException (when network is unavailable) and display error dialog --- .../goliath/emojihub/usecases/EmojiUseCase.kt | 29 +++-- .../goliath/emojihub/usecases/PostUseCase.kt | 42 +++++-- .../goliath/emojihub/usecases/UserUseCase.kt | 113 +++++++++++------- 3 files changed, 128 insertions(+), 56 deletions(-) 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 40d248b1..4a3b1446 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 @@ -16,9 +16,9 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map -import retrofit2.HttpException import java.io.File import java.io.IOException +import java.net.ConnectException import javax.inject.Inject import javax.inject.Singleton @@ -72,7 +72,7 @@ class EmojiUseCaseImpl @Inject constructor( override suspend fun fetchEmojiList(sortByDate: Int): Flow> { return try { emojiRepository.fetchEmojiList(sortByDate).map { it.map { dto -> Emoji(dto) } } - } catch (e: HttpException) { + } catch (e: ConnectException) { errorController.setErrorState(CustomError.INTERNAL_SERVER_ERROR.statusCode) flowOf(PagingData.empty()) } catch (e: Exception) { @@ -84,7 +84,7 @@ class EmojiUseCaseImpl @Inject constructor( override suspend fun fetchMyCreatedEmojiList(): Flow> { return try { emojiRepository.fetchMyCreatedEmojiList().map { it.map { dto -> Emoji(dto) } } - } catch (e: HttpException) { + } catch (e: ConnectException) { errorController.setErrorState(CustomError.INTERNAL_SERVER_ERROR.statusCode) flowOf(PagingData.empty()) } catch (e: Exception) { @@ -96,7 +96,7 @@ class EmojiUseCaseImpl @Inject constructor( override suspend fun fetchMySavedEmojiList(): Flow> { return try { emojiRepository.fetchMySavedEmojiList().map { it.map { dto -> Emoji(dto) } } - } catch (e: HttpException) { + } catch (e: ConnectException) { errorController.setErrorState(CustomError.INTERNAL_SERVER_ERROR.statusCode) flowOf(PagingData.empty()) } catch (e: Exception) { @@ -106,17 +106,28 @@ class EmojiUseCaseImpl @Inject constructor( } override suspend fun createEmoji(videoUri: Uri, topK: Int): List { - return x3dRepository.createEmoji(videoUri, topK) + return try { + x3dRepository.createEmoji(videoUri, topK) + } catch (e: Exception) { + Log.e("EmojiUseCase", "Unknown Exception on createEmoji: ${e.message}") + x3dRepository.DEFAULT_EMOJI_LIST + } } override suspend fun uploadEmoji(emojiUnicode: String, emojiLabel: String, videoFile: File): Boolean { val dto = UploadEmojiDto(emojiUnicode, emojiLabel) return try { - emojiRepository.uploadEmoji(videoFile, dto) + val response = emojiRepository.uploadEmoji(videoFile, dto) + if (response.isSuccessful) { + true + } else { + errorController.setErrorState(response.code()) + false + } } catch (e: IOException) { Log.e("EmojiUseCase", "IOException") false - } catch (e: HttpException) { + } catch (e: ConnectException) { errorController.setErrorState(CustomError.INTERNAL_SERVER_ERROR.statusCode) false } catch (e: Exception) { @@ -128,7 +139,7 @@ class EmojiUseCaseImpl @Inject constructor( override suspend fun saveEmoji(id: String): Boolean { return try { emojiRepository.saveEmoji(id).isSuccessful - } catch (e: HttpException) { + } catch (e: ConnectException) { errorController.setErrorState(CustomError.INTERNAL_SERVER_ERROR.statusCode) false } catch (e: Exception) { @@ -140,7 +151,7 @@ class EmojiUseCaseImpl @Inject constructor( override suspend fun unSaveEmoji(id: String): Boolean { return try { emojiRepository.unSaveEmoji(id).isSuccessful - } catch (e: HttpException) { + } catch (e: ConnectException) { errorController.setErrorState(CustomError.INTERNAL_SERVER_ERROR.statusCode) false } catch (e: Exception) { diff --git a/android/app/src/main/java/com/goliath/emojihub/usecases/PostUseCase.kt b/android/app/src/main/java/com/goliath/emojihub/usecases/PostUseCase.kt index 2ca9f337..cf007c78 100644 --- a/android/app/src/main/java/com/goliath/emojihub/usecases/PostUseCase.kt +++ b/android/app/src/main/java/com/goliath/emojihub/usecases/PostUseCase.kt @@ -1,8 +1,10 @@ package com.goliath.emojihub.usecases +import android.util.Log import androidx.paging.PagingData import androidx.paging.map import com.goliath.emojihub.data_sources.ApiErrorController +import com.goliath.emojihub.data_sources.CustomError import com.goliath.emojihub.models.Post import com.goliath.emojihub.models.PostDto import com.goliath.emojihub.models.UploadPostDto @@ -10,7 +12,9 @@ import com.goliath.emojihub.repositories.remote.PostRepository import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map +import java.net.ConnectException import javax.inject.Inject import javax.inject.Singleton @@ -50,20 +54,44 @@ class PostUseCaseImpl @Inject constructor( } override suspend fun fetchPostList(): Flow> { - return repository.fetchPostList().map { it.map { dto -> Post(dto) } } + return try { + repository.fetchPostList().map { it.map { dto -> Post(dto) } } + } catch (e: ConnectException) { + errorController.setErrorState(CustomError.INTERNAL_SERVER_ERROR.statusCode) + flowOf(PagingData.empty()) + } catch (e: Exception) { + Log.e("PostUseCase", "Unknown Exception on fetchPostList: ${e.message}") + flowOf(PagingData.empty()) + } } override suspend fun fetchMyPostList(): Flow> { - return repository.fetchMyPostList().map { it.map { dto -> Post(dto) } } + return try { + repository.fetchMyPostList().map { it.map { dto -> Post(dto) } } + } catch (e: ConnectException) { + errorController.setErrorState(CustomError.INTERNAL_SERVER_ERROR.statusCode) + flowOf(PagingData.empty()) + } catch (e: Exception) { + Log.e("PostUseCase", "Unknown Exception on fetchMyPostList: ${e.message}") + flowOf(PagingData.empty()) + } } override suspend fun uploadPost(content: String): Boolean { val dto = UploadPostDto(content) - val response = repository.uploadPost(dto) - return if (response.isSuccessful) { - true - } else { - errorController.setErrorState(response.code()) + return try { + val response = repository.uploadPost(dto) + if (response.isSuccessful) { + true + } else { + errorController.setErrorState(response.code()) + false + } + } catch (e: ConnectException) { + errorController.setErrorState(CustomError.INTERNAL_SERVER_ERROR.statusCode) + false + } catch (e: Exception) { + Log.e("PostUseCase", "Unknown Exception on uploadPost: ${e.message}") false } } diff --git a/android/app/src/main/java/com/goliath/emojihub/usecases/UserUseCase.kt b/android/app/src/main/java/com/goliath/emojihub/usecases/UserUseCase.kt index 4a73498b..acc75f3e 100644 --- a/android/app/src/main/java/com/goliath/emojihub/usecases/UserUseCase.kt +++ b/android/app/src/main/java/com/goliath/emojihub/usecases/UserUseCase.kt @@ -1,7 +1,9 @@ package com.goliath.emojihub.usecases +import android.util.Log import com.goliath.emojihub.EmojiHubApplication import com.goliath.emojihub.data_sources.ApiErrorController +import com.goliath.emojihub.data_sources.CustomError import com.goliath.emojihub.models.LoginUserDto import com.goliath.emojihub.models.RegisterUserDto import com.goliath.emojihub.models.User @@ -11,6 +13,7 @@ import com.goliath.emojihub.repositories.remote.UserRepository import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.update +import java.net.ConnectException import javax.inject.Inject import javax.inject.Singleton @@ -52,69 +55,99 @@ class UserUseCaseImpl @Inject constructor( override suspend fun fetchMyInfo() { val accessToken = EmojiHubApplication.preferences.accessToken ?: return if (accessToken.isEmpty()) return - - val response = repository.fetchMyInfo(accessToken) - response.let { - if(it.isSuccessful) { - val userDetailsDto = it.body() ?: return // FIXME: may be considered as an error - _userDetailsState.update { UserDetails(userDetailsDto) } + try { + val response = repository.fetchMyInfo(accessToken) + response.let { + if(it.isSuccessful) { + val userDetailsDto = it.body() ?: return // FIXME: may be considered as an error + _userDetailsState.update { UserDetails(userDetailsDto) } + } + else errorController.setErrorState(it.code()) } - else errorController.setErrorState(it.code()) + } catch (e: ConnectException) { + errorController.setErrorState(CustomError.INTERNAL_SERVER_ERROR.statusCode) + } catch (e: Exception) { + Log.e("UserUseCase", "Unknown Exception on fetchMyEmoji: ${e.message}") } } override suspend fun registerUser(email: String, name: String, password: String): Boolean { val dto = RegisterUserDto(email, name, password) - val response = repository.registerUser(dto) - response.let { - if(it.isSuccessful) return true - else errorController.setErrorState(it.code()) + try { + val response = repository.registerUser(dto) + response.let { + if(it.isSuccessful) return true + else errorController.setErrorState(it.code()) + } + return response.isSuccessful + } catch (e: ConnectException) { + errorController.setErrorState(CustomError.INTERNAL_SERVER_ERROR.statusCode) + } catch (e: Exception) { + Log.e("UserUseCase", "Unknown Exception on registerUser: ${e.message}") } - return response.isSuccessful + return false } override suspend fun login(name: String, password: String) { val dto = LoginUserDto(name, password) - val response = repository.login(dto) - response.let { - if (it.isSuccessful) { - val accessToken = it.body()?.accessToken - _accessTokenState.update { accessToken } - _userState.update { User(UserDto(name)) } - EmojiHubApplication.preferences.accessToken = accessToken - EmojiHubApplication.preferences.currentUser = name - } else { - errorController.setErrorState(it.code()) + try { + val response = repository.login(dto) + response.let { + if (it.isSuccessful) { + val accessToken = it.body()?.accessToken + _accessTokenState.update { accessToken } + _userState.update { User(UserDto(name)) } + EmojiHubApplication.preferences.accessToken = accessToken + EmojiHubApplication.preferences.currentUser = name + } else { + errorController.setErrorState(it.code()) + } } + } catch (e: ConnectException) { + errorController.setErrorState(CustomError.INTERNAL_SERVER_ERROR.statusCode) + } catch (e: Exception) { + Log.e("UserUseCase", "Unknown Exception on login: ${e.message}") } } override suspend fun logout() { - val response = repository.logout() - response.let { - if (it.isSuccessful) { - EmojiHubApplication.preferences.accessToken = null - EmojiHubApplication.preferences.currentUser = null - _accessTokenState.update { null } - _userState.update { null } - } else { - errorController.setErrorState(it.code()) + try { + val response = repository.logout() + response.let { + if (it.isSuccessful) { + EmojiHubApplication.preferences.accessToken = null + EmojiHubApplication.preferences.currentUser = null + _accessTokenState.update { null } + _userState.update { null } + } else { + errorController.setErrorState(it.code()) + } } + } catch (e: ConnectException) { + errorController.setErrorState(CustomError.INTERNAL_SERVER_ERROR.statusCode) + } catch (e: Exception) { + Log.e("UserUseCase", "Unknown Exception on logout: ${e.message}") } } override suspend fun signOut() { val accessToken = EmojiHubApplication.preferences.accessToken ?: return - val response = repository.signOut(accessToken) - response.let { - if (it.isSuccessful) { - EmojiHubApplication.preferences.accessToken = null - EmojiHubApplication.preferences.currentUser = null - _accessTokenState.update { null } - _userState.update { null } - } else { - errorController.setErrorState(it.code()) + try { + val response = repository.signOut(accessToken) + response.let { + if (it.isSuccessful) { + EmojiHubApplication.preferences.accessToken = null + EmojiHubApplication.preferences.currentUser = null + _accessTokenState.update { null } + _userState.update { null } + } else { + errorController.setErrorState(it.code()) + } } + } catch (e: ConnectException) { + errorController.setErrorState(CustomError.INTERNAL_SERVER_ERROR.statusCode) + } catch (e: Exception) { + Log.e("UserUseCase", "Unknown Exception on signOut: ${e.message}") } } } \ No newline at end of file