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

Fix: Show error dialog when there is no internet connection #112

Merged
merged 6 commits into from
Dec 7, 2023
Merged
17 changes: 0 additions & 17 deletions android/.idea/deploymentTargetDropDown.xml

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
}
Original file line number Diff line number Diff line change
@@ -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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,28 @@ import javax.inject.Inject
import javax.inject.Singleton

interface X3dRepository {
val DEFAULT_EMOJI_LIST: List<CreatedEmoji>
suspend fun createEmoji(videoUri: Uri, topK: Int): List<CreatedEmoji>
}

@Singleton
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<CreatedEmoji> {
val x3dModule = x3dDataSource.loadModule(moduleName)
?: return emptyList()
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -1,29 +1,22 @@
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
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

Expand All @@ -32,7 +25,7 @@ interface EmojiRepository {
suspend fun fetchMyCreatedEmojiList(): Flow<PagingData<EmojiDto>>
suspend fun fetchMySavedEmojiList(): Flow<PagingData<EmojiDto>>
suspend fun getEmojiWithId(id: String): EmojiDto?
suspend fun uploadEmoji(videoFile: File, emojiDto: UploadEmojiDto): Boolean
suspend fun uploadEmoji(videoFile: File, emojiDto: UploadEmojiDto): Response<Unit>
suspend fun saveEmoji(id: String): Response<Unit>
suspend fun unSaveEmoji(id: String): Response<Unit>
suspend fun deleteEmoji(id: String): Response<Unit>
Expand All @@ -41,7 +34,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<PagingData<EmojiDto>> {
return Pager(
Expand All @@ -68,35 +61,20 @@ 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<Unit> {
val emojiDtoJson = Gson().toJson(emojiDto)
val emojiDtoRequestBody = emojiDtoJson.toRequestBody("application/json".toMediaTypeOrNull())

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)
}

override suspend fun saveEmoji(id: String): Response<Unit> {
Expand All @@ -110,26 +88,4 @@ class EmojiRepositoryImpl @Inject constructor(
override suspend fun deleteEmoji(id: String): Response<Unit> {
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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 java.io.File
import java.io.IOException
import java.net.ConnectException
import javax.inject.Inject
import javax.inject.Singleton

Expand Down Expand Up @@ -66,57 +70,92 @@ class EmojiUseCaseImpl @Inject constructor(
}

override suspend fun fetchEmojiList(sortByDate: Int): Flow<PagingData<Emoji>> {
return emojiRepository.fetchEmojiList(sortByDate).map { it.map { dto -> Emoji(dto) } }
return try {
emojiRepository.fetchEmojiList(sortByDate).map { it.map { dto -> Emoji(dto) } }
} catch (e: ConnectException) {
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<PagingData<Emoji>> {
return emojiRepository.fetchMyCreatedEmojiList().map { it.map { dto -> Emoji(dto) } }
return try {
emojiRepository.fetchMyCreatedEmojiList().map { it.map { dto -> Emoji(dto) } }
} catch (e: ConnectException) {
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<PagingData<Emoji>> {
return emojiRepository.fetchMySavedEmojiList().map { it.map { dto -> Emoji(dto) } }
return try {
emojiRepository.fetchMySavedEmojiList().map { it.map { dto -> Emoji(dto) } }
} catch (e: ConnectException) {
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<CreatedEmoji> {
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 emojiRepository.uploadEmoji(videoFile, dto)
}

override suspend fun saveEmoji(id: String): Boolean {
return try {
val response = emojiRepository.saveEmoji(id)
Log.d("EmojiUseCase", "SaveEmoji Api response : ${response.code()}")

val response = emojiRepository.uploadEmoji(videoFile, dto)
if (response.isSuccessful) {
Log.d("EmojiUseCase", "Successfully saved Emoji (Id: $id)")
true
} else {
Log.e("EmojiUseCase", "Failed to save Emoji (Id: $id), ${response.code()}")
errorController.setErrorState(response.code())
false
}
} catch (e: IOException) {
Log.e("EmojiUseCase", "IOException")
false
} catch (e: ConnectException) {
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): Boolean {
return try {
emojiRepository.saveEmoji(id).isSuccessful
} catch (e: ConnectException) {
errorController.setErrorState(CustomError.INTERNAL_SERVER_ERROR.statusCode)
false
} catch (e: Exception) {
Log.e("EmojiUseCase", "Unknown Exception at Save emoji: ${e.message}")
Log.e("EmojiUseCase", "Unknown Exception on saveEmoji: ${e.message}")
false
}
}

override suspend fun unSaveEmoji(id: String): Boolean {
return try {
val response = emojiRepository.unSaveEmoji(id)
Log.d("EmojiUseCase", "UnSaveEmoji Api response : ${response.code()}")
if (response.isSuccessful) {
Log.d("EmojiUseCase", "Successfully unsaved Emoji (Id: $id)")
true
} else {
Log.e("EmojiUseCase", "Failed to unsave Emoji (Id: $id), ${response.code()}")
false
}
emojiRepository.unSaveEmoji(id).isSuccessful
} catch (e: ConnectException) {
errorController.setErrorState(CustomError.INTERNAL_SERVER_ERROR.statusCode)
false
} catch (e: Exception) {
Log.e("EmojiUseCase", "Unknown Exception at UnSave emoji: ${e.message}")
Log.e("EmojiUseCase", "Unknown Exception on unSaveEmoji: ${e.message}")
false
}
}
Expand Down
Loading