diff --git a/android/.idea/deploymentTargetDropDown.xml b/android/.idea/deploymentTargetDropDown.xml
deleted file mode 100644
index 7e0dc85b..00000000
--- a/android/.idea/deploymentTargetDropDown.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/android/app/src/main/java/com/goliath/emojihub/RootActivity.kt b/android/app/src/main/java/com/goliath/emojihub/RootActivity.kt
index 6f700095..c956dd6e 100644
--- a/android/app/src/main/java/com/goliath/emojihub/RootActivity.kt
+++ b/android/app/src/main/java/com/goliath/emojihub/RootActivity.kt
@@ -15,6 +15,7 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
@@ -32,8 +33,11 @@ import com.goliath.emojihub.views.LoginPage
import com.goliath.emojihub.views.MainPage
import com.goliath.emojihub.views.SignUpPage
import com.goliath.emojihub.views.TransformVideoPage
+import com.goliath.emojihub.views.components.CreatedEmojiListView
+import com.goliath.emojihub.views.components.CreatedPostListView
import com.goliath.emojihub.views.components.CustomDialog
import com.goliath.emojihub.views.components.PlayEmojiView
+import com.goliath.emojihub.views.components.SavedEmojiListView
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
@@ -50,7 +54,10 @@ class RootActivity : ComponentActivity() {
setContent {
EmojiHubTheme {
- Box(Modifier.fillMaxSize().background(Color.White)) {
+ Box(
+ Modifier
+ .fillMaxSize()
+ .background(Color.White)) {
val accessToken = userViewModel.accessTokenState.collectAsState().value
val error by apiErrorController.apiErrorState.collectAsState()
@@ -110,20 +117,62 @@ class RootActivity : ComponentActivity() {
}
composable(NavigationDestination.TransformVideo) {
- val emojiViewModel = hiltViewModel()
+ val parentEntry = remember(it) {
+ navController.getBackStackEntry(NavigationDestination.MainPage)
+ }
+ val emojiViewModel = hiltViewModel(parentEntry)
TransformVideoPage(emojiViewModel)
}
composable(NavigationDestination.PlayEmojiVideo) {
- val emojiViewModel = hiltViewModel()
- PlayEmojiView(emojiViewModel)
+ val parentEntry = remember(it) {
+ navController.getBackStackEntry(NavigationDestination.MainPage)
+ }
+ val emojiViewModel = hiltViewModel(parentEntry)
+ val userViewModel = hiltViewModel(parentEntry)
+ PlayEmojiView(emojiViewModel, userViewModel)
}
composable(NavigationDestination.CreatePost) {
- val postViewModel = hiltViewModel()
+ val parentEntry = remember(it) {
+ navController.getBackStackEntry(NavigationDestination.MainPage)
+ }
+ val postViewModel = hiltViewModel(parentEntry)
CreatePostPage(postViewModel)
}
+
+ composable(NavigationDestination.MyPostList) {
+ val parentEntry = remember(it) {
+ navController.getBackStackEntry(NavigationDestination.MainPage)
+ }
+ val postViewModel = hiltViewModel(parentEntry)
+ CreatedPostListView(postViewModel.myPostList)
+ }
+
+ composable(NavigationDestination.MyEmojiList) {
+ val parentEntry = remember(it) {
+ navController.getBackStackEntry(NavigationDestination.MainPage)
+ }
+ val emojiViewModel = hiltViewModel(parentEntry)
+ CreatedEmojiListView(emojiViewModel)
+ }
+
+ composable(NavigationDestination.MySavedEmojiList) {
+ val parentEntry = remember(it) {
+ navController.getBackStackEntry(NavigationDestination.MainPage)
+ }
+ val emojiViewModel = hiltViewModel(parentEntry)
+ SavedEmojiListView(emojiViewModel)
+ }
}
}
}
+}
+
+fun NavController.navigateAsOrigin(route: String) {
+ navigate(route) {
+ while (popBackStack()) { }
+ launchSingleTop = true
+ restoreState = true
+ }
}
\ No newline at end of file
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/data_sources/BottomNavigationController.kt b/android/app/src/main/java/com/goliath/emojihub/data_sources/BottomNavigationController.kt
index d8ea772f..77ec7b80 100644
--- a/android/app/src/main/java/com/goliath/emojihub/data_sources/BottomNavigationController.kt
+++ b/android/app/src/main/java/com/goliath/emojihub/data_sources/BottomNavigationController.kt
@@ -1,7 +1,7 @@
package com.goliath.emojihub.data_sources
-import androidx.compose.runtime.MutableState
import androidx.compose.runtime.Stable
+import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import com.goliath.emojihub.views.PageItem
@@ -9,7 +9,7 @@ class BottomNavigationController {
private val _bottomNavigationDestination = mutableStateOf(PageItem.Feed.screenRoute)
@Stable
- val currentDestination: MutableState = _bottomNavigationDestination
+ val currentDestination: State = _bottomNavigationDestination
fun updateDestination(destination: PageItem) {
_bottomNavigationDestination.value = destination.screenRoute
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/EmojiPagingSource.kt b/android/app/src/main/java/com/goliath/emojihub/data_sources/EmojiPagingSource.kt
index 8774e6bc..5b60c5db 100644
--- 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
@@ -12,6 +12,7 @@ enum class EmojiFetchType {
class EmojiPagingSource @Inject constructor(
private val api: EmojiApi,
+ private val sortByDate : Int,
private val type: EmojiFetchType
): PagingSource() {
override suspend fun load(params: LoadParams): LoadResult {
@@ -20,20 +21,21 @@ class EmojiPagingSource @Inject constructor(
return try {
val response: List? = when (type) {
EmojiFetchType.GENERAL -> {
- api.fetchEmojiList(1, cursor, count).body()
+ api.fetchEmojiList(sortByDate, cursor, count).body()
}
EmojiFetchType.MY_CREATED -> {
- api.fetchMyCreatedEmojiList(1, cursor, count).body()
+ api.fetchMyCreatedEmojiList(sortByDate, cursor, count).body()
}
EmojiFetchType.MY_SAVED -> {
- api.fetchMySavedEmojiList(1, cursor, count).body()
+ api.fetchMySavedEmojiList(sortByDate, cursor, count).body()
}
}
val data = response ?: listOf()
+ val nextKey = if (response?.size!! < count) null else cursor + 1 //Stops infinite fetching
LoadResult.Page(
data = data,
prevKey = if (cursor == 1) null else cursor - 1,
- nextKey = if (data.isEmpty()) null else cursor + 1
+ nextKey = nextKey
)
} catch (exception: Exception) {
LoadResult.Error(exception)
diff --git a/android/app/src/main/java/com/goliath/emojihub/data_sources/NetworkModule.kt b/android/app/src/main/java/com/goliath/emojihub/data_sources/NetworkModule.kt
index 7c42d631..4851aa09 100644
--- a/android/app/src/main/java/com/goliath/emojihub/data_sources/NetworkModule.kt
+++ b/android/app/src/main/java/com/goliath/emojihub/data_sources/NetworkModule.kt
@@ -3,6 +3,7 @@ package com.goliath.emojihub.data_sources
import com.goliath.emojihub.EmojiHubApplication
import com.goliath.emojihub.data_sources.api.EmojiApi
import com.goliath.emojihub.data_sources.api.PostApi
+import com.goliath.emojihub.data_sources.api.ReactionApi
import com.goliath.emojihub.data_sources.api.UserApi
import dagger.Module
import dagger.Provides
@@ -59,6 +60,11 @@ object NetworkModule {
fun providesPostRestApi(retrofit: Retrofit): PostApi =
retrofit.create(PostApi::class.java)
+ @Provides
+ @Singleton
+ fun providesReactionRestApi(retrofit: Retrofit): ReactionApi =
+ retrofit.create(ReactionApi::class.java)
+
// empty responses should be handled `success`
private val nullOnEmptyConverterFactory = object : Converter.Factory() {
fun converterFactory() = this
diff --git a/android/app/src/main/java/com/goliath/emojihub/data_sources/ReactionPagingSource.kt b/android/app/src/main/java/com/goliath/emojihub/data_sources/ReactionPagingSource.kt
new file mode 100644
index 00000000..6a4992f1
--- /dev/null
+++ b/android/app/src/main/java/com/goliath/emojihub/data_sources/ReactionPagingSource.kt
@@ -0,0 +1,37 @@
+package com.goliath.emojihub.data_sources
+
+import androidx.paging.PagingSource
+import androidx.paging.PagingState
+import com.goliath.emojihub.data_sources.api.ReactionApi
+import com.goliath.emojihub.models.ReactionWithEmojiDto
+import javax.inject.Inject
+
+class ReactionPagingSource @Inject constructor(
+ private val api: ReactionApi,
+ private val postId: String,
+ private val emojiUnicode: String
+): PagingSource(){
+ override suspend fun load(params: LoadParams): LoadResult {
+ val cursor = params.key ?: 1
+ val count = params.loadSize
+ return try {
+ val response: List? = api.fetchReactionList(postId, emojiUnicode, cursor, count).body()
+ val data = response ?: listOf()
+ val nextKey = if (response?.size!! < count) null else cursor + 1 //Stops infinite fetching
+ LoadResult.Page(
+ data = data,
+ prevKey = if (cursor == 1) null else cursor - 1,
+ nextKey = nextKey
+ )
+ } 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)
+ }
+ }
+}
diff --git a/android/app/src/main/java/com/goliath/emojihub/data_sources/SharedLocalStorage.kt b/android/app/src/main/java/com/goliath/emojihub/data_sources/SharedLocalStorage.kt
index 7f064e6e..70539e9e 100644
--- a/android/app/src/main/java/com/goliath/emojihub/data_sources/SharedLocalStorage.kt
+++ b/android/app/src/main/java/com/goliath/emojihub/data_sources/SharedLocalStorage.kt
@@ -8,7 +8,7 @@ import javax.inject.Singleton
interface LocalStorage {
var accessToken: String?
- val currentUser: String?
+ var currentUser: String?
}
@Singleton
diff --git a/android/app/src/main/java/com/goliath/emojihub/data_sources/api/ReactionApi.kt b/android/app/src/main/java/com/goliath/emojihub/data_sources/api/ReactionApi.kt
new file mode 100644
index 00000000..8c8f5459
--- /dev/null
+++ b/android/app/src/main/java/com/goliath/emojihub/data_sources/api/ReactionApi.kt
@@ -0,0 +1,35 @@
+package com.goliath.emojihub.data_sources.api
+
+import com.goliath.emojihub.models.ReactionWithEmojiDto
+import retrofit2.Response
+import retrofit2.http.DELETE
+import retrofit2.http.GET
+import retrofit2.http.POST
+import retrofit2.http.Path
+import retrofit2.http.Query
+
+interface ReactionApi {
+ @POST("reaction")
+ suspend fun uploadReaction(
+ @Query("postId") postId: String,
+ @Query("emojiId") emojiId: String
+ ): Response
+
+ @GET("reaction")
+ suspend fun fetchReactionList(
+ @Query("postId") postId: String,
+ @Query("emojiUnicode") emojiUnicode: String,
+ @Query("index") index: Int,
+ @Query("count") count: Int
+ ): Response>
+
+ @GET("reaction")
+ suspend fun getReactionWithId(
+ @Path("id") id: String
+ ): Response
+
+ @DELETE("reaction")
+ suspend fun deleteReaction(
+ @Query("reactionId") reactionId: String
+ ): Response
+}
\ No newline at end of file
diff --git a/android/app/src/main/java/com/goliath/emojihub/data_sources/api/UserApi.kt b/android/app/src/main/java/com/goliath/emojihub/data_sources/api/UserApi.kt
index 9721787b..170949ef 100644
--- a/android/app/src/main/java/com/goliath/emojihub/data_sources/api/UserApi.kt
+++ b/android/app/src/main/java/com/goliath/emojihub/data_sources/api/UserApi.kt
@@ -2,7 +2,7 @@ package com.goliath.emojihub.data_sources.api
import com.goliath.emojihub.models.LoginUserDto
import com.goliath.emojihub.models.RegisterUserDto
-import com.goliath.emojihub.models.UserDtoList
+import com.goliath.emojihub.models.UserDetailsDto
import com.goliath.emojihub.models.responses.LoginResponseDto
import retrofit2.Response
import retrofit2.http.Body
@@ -15,7 +15,12 @@ interface UserApi {
@GET("user")
suspend fun fetchUserList(
- ): Response>
+ ): Response>
+
+ @GET("user/me")
+ suspend fun fetchMyInfo(
+ @Header("Authorization") authToken: String
+ ): Response
@POST("user/signup")
suspend fun registerUser(
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/extensions/Extensions.kt b/android/app/src/main/java/com/goliath/emojihub/extensions/Extensions.kt
index 417c5724..5c006bbb 100644
--- a/android/app/src/main/java/com/goliath/emojihub/extensions/Extensions.kt
+++ b/android/app/src/main/java/com/goliath/emojihub/extensions/Extensions.kt
@@ -1,5 +1,7 @@
package com.goliath.emojihub.extensions
+import com.goliath.emojihub.models.ReactionWithEmojiUnicode
+
fun String.toEmoji(): String {
return try {
this.trim().split(" ").map { it.removePrefix("U+").toInt(16) }
@@ -9,18 +11,20 @@ fun String.toEmoji(): String {
}
}
-fun reactionsToString (reactions: List): String {
+fun reactionsToString (reactions: List): String {
var emojisStr = ""
if (reactions.size >= 3) {
- for (emoji in reactions) {
- emojisStr += emoji
+ val lastThreeReactions = reactions.takeLast(3)
+ for (reaction in lastThreeReactions) {
+ emojisStr += reaction.emoji_unicode.toEmoji()
emojisStr += " "
}
- emojisStr += "외 ${reactions.size - 3}개의 반응"
+ emojisStr += if (reactions.size == 3) "3개의 반응"
+ else "외 ${reactions.size - 3}개의 반응"
} else {
- for (emoji in reactions) {
- emojisStr += emoji
+ for (reaction in reactions) {
+ emojisStr += reaction.emoji_unicode.toEmoji()
emojisStr += " "
}
emojisStr += "${reactions.size}개의 반응"
diff --git a/android/app/src/main/java/com/goliath/emojihub/models/Post.kt b/android/app/src/main/java/com/goliath/emojihub/models/Post.kt
index 58252c7c..cae22752 100644
--- a/android/app/src/main/java/com/goliath/emojihub/models/Post.kt
+++ b/android/app/src/main/java/com/goliath/emojihub/models/Post.kt
@@ -10,7 +10,7 @@ class Post(
val modifiedAt: String = dto.modifiedAt
val createdBy: String = dto.createdBy
val content: String = dto.content
- val reaction: List = dto.reaction
+ val reaction: List = dto.reaction
override fun equals(other: Any?): Boolean {
if (this === other) return true
@@ -44,22 +44,14 @@ data class PostDto(
@SerializedName("modified_at") val modifiedAt: String,
@SerializedName("created_by") val createdBy: String,
val content: String,
- @SerializedName("reactions") val reaction: List
+ @SerializedName("reactions") val reaction: List
)
data class UploadPostDto(
@SerializedName("content") val content: String
)
-val dummyPost = Post(
- PostDto(
- id = "1234",
- createdAt = "2023.09.16",
- createdBy = "channn",
- content = "조금 전에 앞에 계신 분이 실수로 지갑을 흘리셨다. " +
- "지갑이 하수구 구멍으로 빠지려는 찰나, 발로 굴러가는 지갑을 막아서 다행히 참사는 막을 수 있었다. " +
- "지갑 주인분께서 감사하다고 카페 드림에서 커피도 한 잔 사주셨다.",
- modifiedAt = "2023.10.23",
- reaction = listOf("good", "check", "good")
- )
+data class ReactionWithEmojiUnicode(
+ var id: String = "",
+ var emoji_unicode: String = ""
)
\ No newline at end of file
diff --git a/android/app/src/main/java/com/goliath/emojihub/models/Reaction.kt b/android/app/src/main/java/com/goliath/emojihub/models/Reaction.kt
index f72648d3..109e0c08 100644
--- a/android/app/src/main/java/com/goliath/emojihub/models/Reaction.kt
+++ b/android/app/src/main/java/com/goliath/emojihub/models/Reaction.kt
@@ -2,12 +2,49 @@ package com.goliath.emojihub.models
import com.google.gson.annotations.SerializedName
+class Reaction(
+ dto: ReactionDto
+) {
+ val id: String = dto.id
+ val createdAt: String = dto.createdAt
+ val createdBy: String = dto.createdBy
+ val emojiId: String = dto.emojiId
+ val postId: String = dto.postId
+}
data class ReactionMetaDataDto(
@SerializedName("emoji_unicode_list")
val unicodeList: List
)
data class ReactionDto(
- @SerializedName("emoji_list")
- val emojiList: List
-)
\ No newline at end of file
+ val id: String,
+ @SerializedName("created_at") val createdAt: String,
+ @SerializedName("created_by") val createdBy: String,
+ @SerializedName("emoji_id") val emojiId: String,
+ @SerializedName("post_id") val postId: String
+)
+
+data class UploadReactionDto(
+ @SerializedName("postId") val postId: String,
+ @SerializedName("emojiId") val emojiId: String
+)
+
+data class ReactionWithEmojiDto(
+ val id: String,
+ @SerializedName("created_at") val createdAt: String,
+ @SerializedName("created_by") val createdBy: String,
+ @SerializedName("emoji_id") val emojiId: String,
+ @SerializedName("post_id") val postId: String,
+ @SerializedName("emojiDto") val emojiDto: EmojiDto?
+)
+
+class ReactionWithEmoji(
+ dto: ReactionWithEmojiDto
+) {
+ val id: String = dto.id
+ val createdAt: String = dto.createdAt
+ val createdBy: String = dto.createdBy
+ val emojiId: String = dto.emojiId
+ val postId: String = dto.postId
+ val emojiDto: EmojiDto? = dto.emojiDto
+}
diff --git a/android/app/src/main/java/com/goliath/emojihub/models/User.kt b/android/app/src/main/java/com/goliath/emojihub/models/User.kt
index e4405115..3253a32d 100644
--- a/android/app/src/main/java/com/goliath/emojihub/models/User.kt
+++ b/android/app/src/main/java/com/goliath/emojihub/models/User.kt
@@ -12,8 +12,41 @@ data class UserDto(
val name: String
)
-// user list: will be deprecated
-data class UserDtoList(
+class UserDetails(
+ dto: UserDetailsDto
+) {
+ val name: String = dto.name
+ val email: String = dto.email
+ val savedEmojiList: List? = dto.savedEmojiList
+ val createdEmojiList: List? = dto.createdEmojiList
+ val createdPostList: List? = dto.createdPostList
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (javaClass != other?.javaClass) return false
+
+ other as UserDetails
+
+ if (name != other.name) return false
+ if (email != other.email) return false
+ if (savedEmojiList != other.savedEmojiList) return false
+ if (createdEmojiList != other.createdEmojiList) return false
+ if (createdPostList != other.createdPostList) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = name.hashCode()
+ result = 31 * result + email.hashCode()
+ result = 31 * result + (savedEmojiList?.hashCode() ?: 0)
+ result = 31 * result + (createdEmojiList?.hashCode() ?: 0)
+ result = 31 * result + (createdPostList?.hashCode() ?: 0)
+ return result
+ }
+}
+
+data class UserDetailsDto(
@SerializedName("email")
val email: String,
@@ -23,11 +56,14 @@ data class UserDtoList(
@SerializedName("password")
val password: String,
- @SerializedName("liked_emojis")
- val likedEmojiList: String?,
+ @SerializedName("saved_emojis")
+ val savedEmojiList: List?,
@SerializedName("created_emojis")
- val createdEmojiList: String?
+ val createdEmojiList: List?,
+
+ @SerializedName("created_posts")
+ val createdPostList: List?
)
class RegisterUserDto(
diff --git a/android/app/src/main/java/com/goliath/emojihub/repositories/RepositoryModule.kt b/android/app/src/main/java/com/goliath/emojihub/repositories/RepositoryModule.kt
index d6a431e0..8aea92bc 100644
--- a/android/app/src/main/java/com/goliath/emojihub/repositories/RepositoryModule.kt
+++ b/android/app/src/main/java/com/goliath/emojihub/repositories/RepositoryModule.kt
@@ -6,6 +6,8 @@ import com.goliath.emojihub.repositories.remote.EmojiRepository
import com.goliath.emojihub.repositories.remote.EmojiRepositoryImpl
import com.goliath.emojihub.repositories.remote.PostRepository
import com.goliath.emojihub.repositories.remote.PostRepositoryImpl
+import com.goliath.emojihub.repositories.remote.ReactionRepository
+import com.goliath.emojihub.repositories.remote.ReactionRepositoryImpl
import com.goliath.emojihub.repositories.remote.UserRepository
import com.goliath.emojihub.repositories.remote.UserRepositoryImpl
import dagger.Binds
@@ -27,4 +29,7 @@ abstract class RepositoryModule {
@Binds
abstract fun bindsX3dRepository(impl: X3dRepositoryImpl): X3dRepository
+
+ @Binds
+ abstract fun bindsReactionRepository(impl: ReactionRepositoryImpl): ReactionRepository
}
\ No newline at end of file
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
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 8c78d3d5..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
@@ -1,66 +1,59 @@
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
interface EmojiRepository {
- suspend fun fetchEmojiList(): Flow>
+ suspend fun fetchEmojiList(sortByDate: Int): Flow>
suspend fun fetchMyCreatedEmojiList(): Flow>
suspend fun fetchMySavedEmojiList(): Flow>
suspend fun getEmojiWithId(id: String): EmojiDto?
- suspend fun uploadEmoji(videoFile: File, emojiDto: UploadEmojiDto): Boolean
- suspend fun saveEmoji(id: String): Result
- suspend fun unSaveEmoji(id: String): Result
+ 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
}
@Singleton
class EmojiRepositoryImpl @Inject constructor(
private val emojiApi: EmojiApi,
- @ApplicationContext private val context: Context
+ private val emojiDataSource: EmojiDataSource
): EmojiRepository {
- override suspend fun fetchEmojiList(): Flow> {
+ override suspend fun fetchEmojiList(sortByDate: Int): Flow> {
return Pager(
config = PagingConfig(pageSize = 10, initialLoadSize = 10, enablePlaceholders = false),
- pagingSourceFactory = { EmojiPagingSource(emojiApi, EmojiFetchType.GENERAL) }
+ pagingSourceFactory = { EmojiPagingSource(emojiApi, sortByDate, EmojiFetchType.GENERAL) }
).flow
}
override suspend fun fetchMyCreatedEmojiList(): Flow> {
return Pager(
config = PagingConfig(pageSize = 10, initialLoadSize = 10, enablePlaceholders = false),
- pagingSourceFactory = { EmojiPagingSource(emojiApi, EmojiFetchType.MY_CREATED) }
+ pagingSourceFactory = { EmojiPagingSource(emojiApi, 1, EmojiFetchType.MY_CREATED) }
).flow
}
override suspend fun fetchMySavedEmojiList(): Flow> {
return Pager(
config = PagingConfig(pageSize = 10, initialLoadSize = 10, enablePlaceholders = false),
- pagingSourceFactory = { EmojiPagingSource(emojiApi, EmojiFetchType.MY_SAVED) }
+ pagingSourceFactory = { EmojiPagingSource(emojiApi, 1, EmojiFetchType.MY_SAVED) }
).flow
}
@@ -68,93 +61,31 @@ 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())
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): 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)
- }
+ override suspend fun saveEmoji(id: String): Response {
+ return emojiApi.saveEmoji(id)
}
- 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)
- }
+ override suspend fun unSaveEmoji(id: String): Response {
+ return emojiApi.unSaveEmoji(id)
}
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
diff --git a/android/app/src/main/java/com/goliath/emojihub/repositories/remote/ReactionRepository.kt b/android/app/src/main/java/com/goliath/emojihub/repositories/remote/ReactionRepository.kt
new file mode 100644
index 00000000..9e8ba76c
--- /dev/null
+++ b/android/app/src/main/java/com/goliath/emojihub/repositories/remote/ReactionRepository.kt
@@ -0,0 +1,43 @@
+package com.goliath.emojihub.repositories.remote
+
+import androidx.paging.Pager
+import androidx.paging.PagingConfig
+import androidx.paging.PagingData
+import com.goliath.emojihub.data_sources.ReactionPagingSource
+import com.goliath.emojihub.data_sources.api.ReactionApi
+import com.goliath.emojihub.models.ReactionWithEmojiDto
+import kotlinx.coroutines.flow.Flow
+import retrofit2.Response
+import javax.inject.Inject
+import javax.inject.Singleton
+
+interface ReactionRepository {
+ suspend fun fetchReactionList(postId: String, emojiUnicode: String): Flow>
+ suspend fun uploadReaction(postId: String, emojiId: String): Response
+ suspend fun getReactionWithId(id: String)
+ suspend fun deleteReaction(reactionId: String)
+}
+
+@Singleton
+class ReactionRepositoryImpl @Inject constructor(
+ private val reactionApi: ReactionApi
+): ReactionRepository {
+ override suspend fun fetchReactionList(postId: String, emojiUnicode: String): Flow> {
+ return Pager(
+ config = PagingConfig(pageSize = 10, initialLoadSize = 10, enablePlaceholders = false),
+ pagingSourceFactory = { ReactionPagingSource(reactionApi, postId, emojiUnicode) }
+ ).flow
+ }
+
+ override suspend fun uploadReaction(postId: String, emojiId: String): Response {
+ return reactionApi.uploadReaction(postId, emojiId)
+ }
+
+ override suspend fun getReactionWithId(id: String) {
+ TODO()
+ }
+
+ override suspend fun deleteReaction(reactionId: String) {
+ reactionApi.deleteReaction(reactionId)
+ }
+}
\ No newline at end of file
diff --git a/android/app/src/main/java/com/goliath/emojihub/repositories/remote/UserRepository.kt b/android/app/src/main/java/com/goliath/emojihub/repositories/remote/UserRepository.kt
index b330d564..bb9b0b84 100644
--- a/android/app/src/main/java/com/goliath/emojihub/repositories/remote/UserRepository.kt
+++ b/android/app/src/main/java/com/goliath/emojihub/repositories/remote/UserRepository.kt
@@ -3,15 +3,16 @@ package com.goliath.emojihub.repositories.remote
import com.goliath.emojihub.data_sources.api.UserApi
import com.goliath.emojihub.models.LoginUserDto
import com.goliath.emojihub.models.RegisterUserDto
-import com.goliath.emojihub.models.UserDtoList
+import com.goliath.emojihub.models.UserDetailsDto
import com.goliath.emojihub.models.responses.LoginResponseDto
import retrofit2.Response
import javax.inject.Inject
import javax.inject.Singleton
interface UserRepository {
- suspend fun fetchUserList(): Array
- fun fetchUser(id: String)
+ suspend fun fetchUserList(): Array
+ suspend fun fetchUser(id: String)
+ suspend fun fetchMyInfo(authToken: String): Response
suspend fun registerUser(dto: RegisterUserDto): Response
suspend fun login(dto: LoginUserDto): Response
suspend fun logout(): Response
@@ -22,14 +23,18 @@ interface UserRepository {
class UserRepositoryImpl @Inject constructor(
private val userApi: UserApi
): UserRepository {
- override suspend fun fetchUserList(): Array {
+ override suspend fun fetchUserList(): Array {
return userApi.fetchUserList().body() ?: arrayOf()
}
- override fun fetchUser(id: String) {
+ override suspend fun fetchUser(id: String) {
TODO("Not yet implemented")
}
+ override suspend fun fetchMyInfo(authToken: String): Response {
+ return userApi.fetchMyInfo(authToken)
+ }
+
override suspend fun registerUser(dto: RegisterUserDto): Response {
return userApi.registerUser(dto)
}
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 fa76ea58..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
@@ -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 java.io.File
+import java.io.IOException
+import java.net.ConnectException
import javax.inject.Inject
import javax.inject.Singleton
@@ -25,13 +29,13 @@ interface EmojiUseCase {
suspend fun updateEmojiList(data: PagingData)
suspend fun updateMyCreatedEmojiList(data: PagingData)
suspend fun updateMySavedEmojiList(data: PagingData)
- suspend fun fetchEmojiList(): Flow>
+ suspend fun fetchEmojiList(sortByDate: Int): Flow>
suspend fun fetchMyCreatedEmojiList(): Flow>
suspend fun fetchMySavedEmojiList(): Flow>
suspend fun createEmoji(videoUri: Uri, topK: Int): List
suspend fun uploadEmoji(emojiUnicode: String, emojiLabel: String, videoFile: File): Boolean
- suspend fun saveEmoji(id: String): Result
- suspend fun unSaveEmoji(id: String): Result
+ suspend fun saveEmoji(id: String): Boolean
+ suspend fun unSaveEmoji(id: String): Boolean
}
@Singleton
@@ -65,32 +69,94 @@ class EmojiUseCaseImpl @Inject constructor(
_mySavedEmojiList.emit(data)
}
- override suspend fun fetchEmojiList(): Flow> {
- return emojiRepository.fetchEmojiList().map { it.map { dto -> Emoji(dto) } }
+ override suspend fun fetchEmojiList(sortByDate: Int): Flow> {
+ 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> {
- 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> {
- 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 {
- 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)
+ return try {
+ 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: 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): Result {
- return emojiRepository.saveEmoji(id)
+ 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 on saveEmoji: ${e.message}")
+ false
+ }
}
- override suspend fun unSaveEmoji(id: String): Result {
- return emojiRepository.unSaveEmoji(id)
+ override suspend fun unSaveEmoji(id: String): Boolean {
+ return try {
+ emojiRepository.unSaveEmoji(id).isSuccessful
+ } catch (e: ConnectException) {
+ errorController.setErrorState(CustomError.INTERNAL_SERVER_ERROR.statusCode)
+ false
+ } catch (e: Exception) {
+ Log.e("EmojiUseCase", "Unknown Exception on unSaveEmoji: ${e.message}")
+ false
+ }
}
}
\ No newline at end of file
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/ReactionUseCase.kt b/android/app/src/main/java/com/goliath/emojihub/usecases/ReactionUseCase.kt
new file mode 100644
index 00000000..6ed5d23c
--- /dev/null
+++ b/android/app/src/main/java/com/goliath/emojihub/usecases/ReactionUseCase.kt
@@ -0,0 +1,60 @@
+package com.goliath.emojihub.usecases
+
+import androidx.paging.PagingData
+import androidx.paging.map
+import com.goliath.emojihub.data_sources.ApiErrorController
+import com.goliath.emojihub.models.ReactionWithEmoji
+import com.goliath.emojihub.repositories.remote.ReactionRepository
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.map
+import javax.inject.Inject
+import javax.inject.Singleton
+
+interface ReactionUseCase {
+
+ val reactionList: StateFlow>
+ suspend fun fetchReactionList(postId: String, emojiUnicode: String): Flow>
+ suspend fun updateReactionList(data: PagingData)
+ suspend fun uploadReaction(postId: String, emojiId: String): Boolean
+ suspend fun getReactionWithId(id: String)
+ suspend fun deleteReaction(reactionId: String)
+}
+
+@Singleton
+class ReactionUseCaseImpl @Inject constructor(
+ private val repository: ReactionRepository,
+ private val errorController: ApiErrorController
+): ReactionUseCase {
+
+ private val _reactionList = MutableStateFlow>(PagingData.empty())
+ override val reactionList: StateFlow>
+ get() = _reactionList
+
+ override suspend fun updateReactionList(data: PagingData) {
+ _reactionList.emit(data)
+ }
+
+ override suspend fun fetchReactionList(postId: String, emojiUnicode: String): Flow> {
+ return repository.fetchReactionList(postId, emojiUnicode).map { it.map { dto -> ReactionWithEmoji(dto) } }
+ }
+
+ override suspend fun uploadReaction(postId: String, emojiId: String): Boolean {
+ val response = repository.uploadReaction(postId, emojiId)
+ return if (response.isSuccessful) {
+ true
+ } else {
+ errorController.setErrorState(response.code())
+ false
+ }
+ }
+
+ override suspend fun getReactionWithId(id: String) {
+ repository.getReactionWithId(id)
+ }
+
+ override suspend fun deleteReaction(reactionId: String) {
+ repository.deleteReaction(reactionId)
+ }
+}
\ No newline at end of file
diff --git a/android/app/src/main/java/com/goliath/emojihub/usecases/UseCaseModule.kt b/android/app/src/main/java/com/goliath/emojihub/usecases/UseCaseModule.kt
index d09a6075..5be128cc 100644
--- a/android/app/src/main/java/com/goliath/emojihub/usecases/UseCaseModule.kt
+++ b/android/app/src/main/java/com/goliath/emojihub/usecases/UseCaseModule.kt
@@ -16,4 +16,7 @@ abstract class UseCaseModule {
@Binds
abstract fun bindsPostUseCase(impl: PostUseCaseImpl): PostUseCase
+
+ @Binds
+ abstract fun bindsReactionUseCase(impl: ReactionUseCaseImpl): ReactionUseCase
}
\ No newline at end of file
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 989df348..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
@@ -3,22 +3,27 @@ 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
+import com.goliath.emojihub.models.UserDetails
import com.goliath.emojihub.models.UserDto
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
sealed interface UserUseCase {
val accessTokenState: StateFlow
val userState: StateFlow
+ val userDetailsState: StateFlow
suspend fun fetchUser(id: String)
+ suspend fun fetchMyInfo()
suspend fun registerUser(email: String, name: String, password: String): Boolean
suspend fun login(name: String, password: String)
suspend fun logout()
@@ -39,59 +44,110 @@ class UserUseCaseImpl @Inject constructor(
override val userState: StateFlow
get() = _userState
+ private val _userDetailsState: MutableStateFlow = MutableStateFlow(null)
+ override val userDetailsState: StateFlow
+ get() = _userDetailsState
+
override suspend fun fetchUser(id: String) {
repository.fetchUser(id)
}
+ override suspend fun fetchMyInfo() {
+ val accessToken = EmojiHubApplication.preferences.accessToken ?: return
+ if (accessToken.isEmpty()) return
+ 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())
+ }
+ } 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
- } 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
- _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
- _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
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 6ac0a5c8..0099c72a 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
@@ -3,6 +3,7 @@ package com.goliath.emojihub.viewmodels
import android.net.Uri
import android.util.Log
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
@@ -26,25 +27,29 @@ class EmojiViewModel @Inject constructor(
private val emojiUseCase: EmojiUseCase
): ViewModel() {
lateinit var videoUri: Uri
- var currentEmoji: Emoji? = null
+ lateinit var currentEmoji: Emoji
var bottomSheetContent by mutableStateOf(BottomSheetContent.EMPTY)
- private val _saveEmojiState = MutableStateFlow?>(null)
+ // 0: not saved, 1: saved, -1: not changed
+ private val _saveEmojiState = MutableStateFlow(-1)
val saveEmojiState = _saveEmojiState.asStateFlow()
- private val _unSaveEmojiState = MutableStateFlow?>(null)
+ private val _unSaveEmojiState = MutableStateFlow(-1)
val unSaveEmojiState = _unSaveEmojiState.asStateFlow()
+ var sortByDate by mutableIntStateOf(0)
+
val emojiList = emojiUseCase.emojiList
val myCreatedEmojiList = emojiUseCase.myCreatedEmojiList
val mySavedEmojiList = emojiUseCase.mySavedEmojiList
+
companion object {
private const val _topK = 3
}
fun fetchEmojiList() {
viewModelScope.launch {
- emojiUseCase.fetchEmojiList()
+ emojiUseCase.fetchEmojiList(sortByDate)
.cachedIn(viewModelScope)
.collect {
emojiUseCase.updateEmojiList(it)
@@ -86,15 +91,23 @@ class EmojiViewModel @Inject constructor(
fun saveEmoji(id: String) {
viewModelScope.launch {
- val result = emojiUseCase.saveEmoji(id)
- _saveEmojiState.value = result
+ val isSuccess = emojiUseCase.saveEmoji(id)
+ _saveEmojiState.value = if (isSuccess) 1 else 0
}
}
fun unSaveEmoji(id: String) {
viewModelScope.launch {
- val result = emojiUseCase.unSaveEmoji(id)
- _unSaveEmojiState.value = result
+ val isSuccess = emojiUseCase.unSaveEmoji(id)
+ _unSaveEmojiState.value = if (isSuccess) 1 else 0
}
}
+
+ fun resetSaveEmojiState() {
+ _saveEmojiState.value = -1
+ }
+
+ fun resetUnSaveEmojiState() {
+ _unSaveEmojiState.value = -1
+ }
}
\ No newline at end of file
diff --git a/android/app/src/main/java/com/goliath/emojihub/viewmodels/PostViewModel.kt b/android/app/src/main/java/com/goliath/emojihub/viewmodels/PostViewModel.kt
index 06945776..161c52c8 100644
--- a/android/app/src/main/java/com/goliath/emojihub/viewmodels/PostViewModel.kt
+++ b/android/app/src/main/java/com/goliath/emojihub/viewmodels/PostViewModel.kt
@@ -1,8 +1,12 @@
package com.goliath.emojihub.viewmodels
+import androidx.compose.runtime.getValue
+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.Post
import com.goliath.emojihub.usecases.PostUseCase
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
@@ -15,6 +19,8 @@ class PostViewModel @Inject constructor(
val postList = postUseCase.postList
val myPostList = postUseCase.myPostList
+ var currentPostId by mutableStateOf("")
+ lateinit var currentPost: Post
suspend fun fetchPostList() {
viewModelScope.launch {
diff --git a/android/app/src/main/java/com/goliath/emojihub/viewmodels/ReactionViewModel.kt b/android/app/src/main/java/com/goliath/emojihub/viewmodels/ReactionViewModel.kt
new file mode 100644
index 00000000..ab5d67f1
--- /dev/null
+++ b/android/app/src/main/java/com/goliath/emojihub/viewmodels/ReactionViewModel.kt
@@ -0,0 +1,39 @@
+package com.goliath.emojihub.viewmodels
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import androidx.paging.cachedIn
+import com.goliath.emojihub.usecases.ReactionUseCase
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class ReactionViewModel @Inject constructor(
+ private val reactionUseCase: ReactionUseCase
+): ViewModel() {
+
+ val reactionList = reactionUseCase.reactionList
+
+ suspend fun fetchReactionList(postId: String, emojiUnicode: String) {
+ viewModelScope.launch {
+ reactionUseCase.fetchReactionList(postId, emojiUnicode)
+ .cachedIn(viewModelScope)
+ .collect {
+ reactionUseCase.updateReactionList(it)
+ }
+ }
+ }
+
+ suspend fun uploadReaction(postId: String, emojiId: String): Boolean {
+ return reactionUseCase.uploadReaction(postId, emojiId)
+ }
+
+ suspend fun getReactionWithId(id: String) {
+ reactionUseCase.getReactionWithId(id)
+ }
+
+ suspend fun deleteReaction(reactionId: String) {
+ reactionUseCase.deleteReaction(reactionId)
+ }
+}
\ No newline at end of file
diff --git a/android/app/src/main/java/com/goliath/emojihub/viewmodels/UserViewModel.kt b/android/app/src/main/java/com/goliath/emojihub/viewmodels/UserViewModel.kt
index 81012500..ecc77209 100644
--- a/android/app/src/main/java/com/goliath/emojihub/viewmodels/UserViewModel.kt
+++ b/android/app/src/main/java/com/goliath/emojihub/viewmodels/UserViewModel.kt
@@ -13,11 +13,18 @@ class UserViewModel @Inject constructor(
): ViewModel() {
val accessTokenState = userUseCase.accessTokenState
val userState = userUseCase.userState
+ val userDetailsState = userUseCase.userDetailsState
suspend fun fetchUser(id: String) {
userUseCase.fetchUser(id)
}
+ fun fetchMyInfo() {
+ viewModelScope.launch {
+ userUseCase.fetchMyInfo()
+ }
+ }
+
suspend fun login(username: String, password: String) {
userUseCase.login(username, password)
}
diff --git a/android/app/src/main/java/com/goliath/emojihub/views/CreatePostPage.kt b/android/app/src/main/java/com/goliath/emojihub/views/CreatePostPage.kt
index 1bb19f67..d20c29b1 100644
--- a/android/app/src/main/java/com/goliath/emojihub/views/CreatePostPage.kt
+++ b/android/app/src/main/java/com/goliath/emojihub/views/CreatePostPage.kt
@@ -26,7 +26,7 @@ import kotlinx.coroutines.launch
@Composable
fun CreatePostPage(
- viewModel: PostViewModel
+ postViewModel: PostViewModel
) {
val navController = LocalNavController.current
@@ -42,7 +42,7 @@ fun CreatePostPage(
) {
TextButton(onClick = {
coroutineScope.launch {
- val success = viewModel.uploadPost(content.text)
+ val success = postViewModel.uploadPost(content.text)
if (success) {
showSuccessDialog = true
}
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 2b2ac6db..6128af2c 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
@@ -1,50 +1,75 @@
package com.goliath.emojihub.views
-import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.lazy.grid.GridCells
-import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
-import androidx.compose.material.Icon
-import androidx.compose.material.IconButton
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Add
import android.Manifest
import android.content.pm.PackageManager
-import android.os.Build
+import android.media.MediaMetadataRetriever
+import android.media.MediaMetadataRetriever.METADATA_KEY_DURATION
import android.util.Log
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.PickVisualMediaRequest
import androidx.activity.result.contract.ActivityResultContracts
-import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.grid.GridCells
+import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
+import androidx.compose.material.Button
+import androidx.compose.material.ButtonDefaults
+import androidx.compose.material.DropdownMenu
+import androidx.compose.material.DropdownMenuItem
+import androidx.compose.material.Icon
+import androidx.compose.material.IconButton
import androidx.compose.material.Text
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Add
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.ui.unit.dp
-import com.goliath.emojihub.views.components.EmojiCell
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.dp
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.extensions.toEmoji
+import com.goliath.emojihub.navigateAsOrigin
+import com.goliath.emojihub.ui.theme.Color.Black
import com.goliath.emojihub.ui.theme.Color.White
import com.goliath.emojihub.viewmodels.EmojiViewModel
+import com.goliath.emojihub.viewmodels.UserViewModel
+import com.goliath.emojihub.views.components.CustomDialog
+import com.goliath.emojihub.views.components.EmojiCell
import com.goliath.emojihub.views.components.EmojiCellDisplay
import com.goliath.emojihub.views.components.TopNavigationBar
@Composable
-fun EmojiPage(
-) {
+fun EmojiPage() {
val context = LocalContext.current
val navController = LocalNavController.current
- val viewModel = hiltViewModel()
+ val userViewModel = hiltViewModel()
+ val emojiViewModel = hiltViewModel()
+
+ val currentUser = userViewModel.userState.collectAsState().value
+ val emojiList = emojiViewModel.emojiList.collectAsLazyPagingItems()
+
+ var showNonUserDialog by remember { mutableStateOf(false) }
+ var showVideoTooLongDialog by remember { mutableStateOf(false) }
+ var dropDownMenuExpanded by remember { mutableStateOf(false) }
val permissionLauncher = rememberLauncherForActivityResult(
ActivityResultContracts.RequestPermission()
@@ -55,31 +80,43 @@ fun EmojiPage(
) { uri ->
if (uri != null) {
Log.d("PhotoPicker", "Selected URI: $uri")
- viewModel.videoUri = uri
- navController.navigate(NavigationDestination.TransformVideo)
+ val retriever = MediaMetadataRetriever()
+ retriever.setDataSource(context, uri)
+ val duration = retriever.extractMetadata(METADATA_KEY_DURATION)?.toLongOrNull() ?: 0
+ retriever.release()
+ if (duration >= 6000) {
+ showVideoTooLongDialog = true
+ } else {
+ emojiViewModel.videoUri = uri
+ navController.navigate(NavigationDestination.TransformVideo)
+ }
}
}
- val emojiList = viewModel.emojiList.collectAsLazyPagingItems()
+ // 앱이 처음 실행될 때, 유저 정보를 가져오기 위함
+ LaunchedEffect(userViewModel) {
+ userViewModel.fetchMyInfo()
+ }
- LaunchedEffect(Unit)
- {
- viewModel.fetchEmojiList()
+ LaunchedEffect(Unit) {
+ emojiViewModel.fetchEmojiList()
}
Column(Modifier.background(White)) {
TopNavigationBar("Emoji", shouldNavigate = false) {
IconButton(onClick = {
- when (PackageManager.PERMISSION_GRANTED) {
- ContextCompat.checkSelfPermission(
- context, Manifest.permission.READ_MEDIA_VIDEO
- ) -> {
- pickMediaLauncher.launch(PickVisualMediaRequest(
- ActivityResultContracts.PickVisualMedia.VideoOnly
- ))
- }
- else -> {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ if (currentUser == null) {
+ showNonUserDialog = true
+ } else {
+ when (PackageManager.PERMISSION_GRANTED) {
+ ContextCompat.checkSelfPermission(
+ context, Manifest.permission.READ_MEDIA_VIDEO
+ ) -> {
+ pickMediaLauncher.launch(PickVisualMediaRequest(
+ ActivityResultContracts.PickVisualMedia.VideoOnly
+ ))
+ }
+ else -> {
permissionLauncher.launch(Manifest.permission.READ_MEDIA_VIDEO)
}
}
@@ -95,7 +132,45 @@ fun EmojiPage(
Column(Modifier.padding(horizontal = 16.dp)) {
Spacer(Modifier.height(28.dp))
- Text("Trending 🔥", fontSize = 20.sp, fontWeight = FontWeight.Bold)
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Text(text = if (emojiViewModel.sortByDate == 0) "Trending 🔥" else "Recently added " + "U+D83D U+DD52".toEmoji(), fontSize = 20.sp, fontWeight = FontWeight.Bold)
+
+ Column {
+ Button(
+ onClick = { dropDownMenuExpanded = true },
+ colors = ButtonDefaults.buttonColors(
+ backgroundColor = Black,
+ contentColor = White
+ )
+ ) {
+ Text(text = "Sort by", fontSize = 12.sp)
+ }
+
+ DropdownMenu(
+ expanded = dropDownMenuExpanded,
+ onDismissRequest = { dropDownMenuExpanded = false }
+ ) {
+ DropdownMenuItem(onClick = {
+ emojiViewModel.sortByDate = 1
+ emojiViewModel.fetchEmojiList()
+ dropDownMenuExpanded = false
+ }) {
+ Text(text = "created date")
+ }
+ DropdownMenuItem(onClick = {
+ emojiViewModel.sortByDate = 0
+ emojiViewModel.fetchEmojiList()
+ dropDownMenuExpanded = false
+ }) {
+ Text(text = "save count")
+ }
+ }
+ }
+ }
LazyVerticalGrid(
columns = GridCells.Fixed(2),
@@ -106,12 +181,35 @@ fun EmojiPage(
items(emojiList.itemCount) { index ->
emojiList[index]?.let{
EmojiCell(emoji = it, displayMode = EmojiCellDisplay.VERTICAL) { selectedEmoji ->
- viewModel.currentEmoji = selectedEmoji
+ emojiViewModel.currentEmoji = selectedEmoji
navController.navigate(NavigationDestination.PlayEmojiVideo)
}
}
}
}
}
+
+ if (showVideoTooLongDialog) {
+ CustomDialog(
+ title = "안내",
+ body = "최대 5초 길이의 영상만 업로드할 수 있습니다.",
+ onDismissRequest = { showVideoTooLongDialog = false },
+ confirm = { showVideoTooLongDialog = false }
+ )
+ }
+
+ if (showNonUserDialog) {
+ CustomDialog(
+ title = "비회원 모드",
+ body = "회원만 이모지를 생성할 수 있습니다. 로그인 화면으로 이동할까요?",
+ confirmText = "이동",
+ needsCancelButton = true,
+ onDismissRequest = { showNonUserDialog = false },
+ dismiss = { showNonUserDialog = false },
+ confirm = {
+ navController.navigateAsOrigin(NavigationDestination.Onboard)
+ }
+ )
+ }
}
}
\ No newline at end of file
diff --git a/android/app/src/main/java/com/goliath/emojihub/views/FeedPage.kt b/android/app/src/main/java/com/goliath/emojihub/views/FeedPage.kt
index d444e40b..a81a9b63 100644
--- a/android/app/src/main/java/com/goliath/emojihub/views/FeedPage.kt
+++ b/android/app/src/main/java/com/goliath/emojihub/views/FeedPage.kt
@@ -13,6 +13,11 @@ import androidx.compose.material.icons.filled.Add
import androidx.compose.material3.Divider
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
@@ -21,13 +26,14 @@ import com.goliath.emojihub.LocalBottomSheetController
import com.goliath.emojihub.LocalNavController
import com.goliath.emojihub.NavigationDestination
import com.goliath.emojihub.models.createDummyEmoji
+import com.goliath.emojihub.navigateAsOrigin
import com.goliath.emojihub.ui.theme.Color
import com.goliath.emojihub.ui.theme.Color.EmojiHubDividerColor
import com.goliath.emojihub.viewmodels.EmojiViewModel
import com.goliath.emojihub.viewmodels.PostViewModel
-import com.goliath.emojihub.views.components.EmojiCell
-import com.goliath.emojihub.views.components.EmojiCellDisplay
+import com.goliath.emojihub.viewmodels.UserViewModel
import com.goliath.emojihub.views.components.CustomBottomSheet
+import com.goliath.emojihub.views.components.CustomDialog
import com.goliath.emojihub.views.components.PostCell
import com.goliath.emojihub.views.components.TopNavigationBar
@@ -38,10 +44,20 @@ fun FeedPage() {
val emojiViewModel = hiltViewModel()
val postViewModel = hiltViewModel()
+ val userViewModel = hiltViewModel()
+
+ val currentUser = userViewModel.userState.collectAsState().value
val emojiList = (1..10).map { createDummyEmoji() }
val postList = postViewModel.postList.collectAsLazyPagingItems()
+ var showNonUserDialog by remember { mutableStateOf(false) }
+
+ // 앱이 처음 실행될 때, 유저 정보를 가져오기 위함
+ LaunchedEffect(userViewModel) {
+ userViewModel.fetchMyInfo()
+ }
+
LaunchedEffect(Unit) {
postViewModel.fetchPostList()
}
@@ -51,7 +67,11 @@ fun FeedPage() {
) {
TopNavigationBar("Feed", shouldNavigate = false) {
IconButton(onClick = {
- navController.navigate(NavigationDestination.CreatePost)
+ if (currentUser == null) {
+ showNonUserDialog = true
+ } else {
+ navController.navigate(NavigationDestination.CreatePost)
+ }
}) {
Icon(
imageVector = Icons.Default.Add,
@@ -60,18 +80,14 @@ fun FeedPage() {
}
}
- Box(
- modifier = Modifier
- .weight(1f)
- .fillMaxWidth()
- ) {
+ Box(modifier = Modifier.weight(1f).fillMaxWidth()) {
LazyColumn(
modifier = Modifier.fillMaxWidth(),
contentPadding = PaddingValues(horizontal = 16.dp)
) {
items(postList.itemCount) { index ->
postList[index]?.let {
- PostCell(post = it)
+ PostCell(post = it, isNonUser = currentUser == null)
Divider(color = EmojiHubDividerColor, thickness = 0.5.dp)
}
}
@@ -79,13 +95,23 @@ fun FeedPage() {
}
}
+ if (showNonUserDialog) {
+ CustomDialog(
+ title = "비회원 모드",
+ body = "회원만 글을 작성할 수 있습니다. 로그인 화면으로 이동할까요?",
+ confirmText = "이동",
+ needsCancelButton = true,
+ onDismissRequest = { showNonUserDialog = false },
+ dismiss = { showNonUserDialog = false },
+ confirm = {
+ navController.navigateAsOrigin(NavigationDestination.Onboard)
+ }
+ )
+ }
+
if (bottomSheetController.isVisible) {
CustomBottomSheet(
- bottomSheetContent = emojiViewModel.bottomSheetContent,
- emojiList = emojiList
- ) { emoji ->
- emojiViewModel.currentEmoji = emoji
- navController.navigate(NavigationDestination.PlayEmojiVideo)
- }
+ bottomSheetContent = emojiViewModel.bottomSheetContent
+ )
}
}
\ No newline at end of file
diff --git a/android/app/src/main/java/com/goliath/emojihub/views/LoginPage.kt b/android/app/src/main/java/com/goliath/emojihub/views/LoginPage.kt
index ef675dec..98596b81 100644
--- a/android/app/src/main/java/com/goliath/emojihub/views/LoginPage.kt
+++ b/android/app/src/main/java/com/goliath/emojihub/views/LoginPage.kt
@@ -18,6 +18,8 @@ import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Text
import androidx.compose.material3.ButtonDefaults
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@@ -34,9 +36,11 @@ import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.hilt.navigation.compose.hiltViewModel
+import com.goliath.emojihub.LocalBottomNavigationController
import com.goliath.emojihub.LocalNavController
import com.goliath.emojihub.NavigationDestination
import com.goliath.emojihub.R
+import com.goliath.emojihub.navigateAsOrigin
import com.goliath.emojihub.ui.theme.Color
import com.goliath.emojihub.viewmodels.UserViewModel
import com.goliath.emojihub.views.components.UnderlinedTextField
@@ -47,20 +51,28 @@ fun LoginPage() {
var username by remember { mutableStateOf(TextFieldValue("")) }
var password by remember { mutableStateOf(TextFieldValue("")) }
+ val isLoginButtonDisabled by remember { derivedStateOf { username.text.isEmpty() || password.text.isEmpty() }}
+
val focusManager = LocalFocusManager.current
val interactionSource = remember { MutableInteractionSource() }
val userViewModel = hiltViewModel()
val coroutineScope = rememberCoroutineScope()
+
val navController = LocalNavController.current
+ val bottomNavigationController = LocalBottomNavigationController.current
+
+ LaunchedEffect(Unit) {
+ bottomNavigationController.updateDestination(PageItem.Feed)
+ }
Box(modifier = Modifier
- .background(Color.White)
- .fillMaxSize()
- .padding(horizontal = 16.dp)
- .clickable(interactionSource = interactionSource, indication = null) {
- focusManager.clearFocus()
- },
+ .background(Color.White)
+ .fillMaxSize()
+ .padding(horizontal = 16.dp)
+ .clickable(interactionSource = interactionSource, indication = null) {
+ focusManager.clearFocus()
+ },
contentAlignment = Alignment.Center
) {
Column(
@@ -94,8 +106,13 @@ fun LoginPage() {
coroutineScope.launch {
userViewModel.login(username.text, password.text)
}
+ userViewModel.fetchMyInfo()
},
- modifier = Modifier.padding(top = 24.dp).fillMaxWidth().height(44.dp),
+ modifier = Modifier
+ .padding(top = 24.dp)
+ .fillMaxWidth()
+ .height(44.dp),
+ enabled = !isLoginButtonDisabled,
shape = RoundedCornerShape(50),
colors = ButtonDefaults.buttonColors(
containerColor = Color.Black,
@@ -112,7 +129,9 @@ fun LoginPage() {
Spacer(modifier = Modifier.height(8.dp))
OutlinedButton(
onClick = { navController.navigate(NavigationDestination.SignUp) },
- modifier = Modifier.fillMaxWidth().height(44.dp),
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(44.dp),
shape = RoundedCornerShape(50),
border = BorderStroke(1.dp, Color.Black),
colors = ButtonDefaults.buttonColors(
@@ -133,7 +152,7 @@ fun LoginPage() {
color = Color.DarkGray,
style = TextStyle(textDecoration = TextDecoration.Underline),
modifier = Modifier.clickable {
- navController.navigate(NavigationDestination.MainPage)
+ navController.navigateAsOrigin(NavigationDestination.MainPage)
}
)
Spacer(modifier = Modifier.height(24.dp))
diff --git a/android/app/src/main/java/com/goliath/emojihub/views/MainPage.kt b/android/app/src/main/java/com/goliath/emojihub/views/MainPage.kt
index c6cdd681..875ea166 100644
--- a/android/app/src/main/java/com/goliath/emojihub/views/MainPage.kt
+++ b/android/app/src/main/java/com/goliath/emojihub/views/MainPage.kt
@@ -31,9 +31,7 @@ fun MainPage() {
pageItemList.forEach { pageItem ->
BottomNavigationItem(
selected = currentRoute == pageItem.screenRoute,
- onClick = {
- bottomNavigationController.updateDestination(pageItem)
- },
+ onClick = { bottomNavigationController.updateDestination(pageItem) },
icon = {
Icon(
painter = painterResource(id = pageItem.icon),
diff --git a/android/app/src/main/java/com/goliath/emojihub/views/ProfilePage.kt b/android/app/src/main/java/com/goliath/emojihub/views/ProfilePage.kt
index 5e473e12..6f9c4b92 100644
--- a/android/app/src/main/java/com/goliath/emojihub/views/ProfilePage.kt
+++ b/android/app/src/main/java/com/goliath/emojihub/views/ProfilePage.kt
@@ -29,6 +29,7 @@ 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.navigateAsOrigin
import com.goliath.emojihub.ui.theme.Color
import com.goliath.emojihub.ui.theme.Color.EmojiHubDetailLabel
import com.goliath.emojihub.ui.theme.Color.White
@@ -62,6 +63,11 @@ fun ProfilePage() {
var showLogoutDialog by remember { mutableStateOf(false) }
var showSignOutDialog by remember { mutableStateOf(false) }
+ // 앱이 처음 실행될 때, 유저 정보를 가져오기 위함
+ LaunchedEffect(userViewModel) {
+ userViewModel.fetchMyInfo()
+ }
+
LaunchedEffect(Unit) {
postViewModel.fetchMyPostList()
emojiViewModel.fetchMyCreatedEmojiList()
@@ -125,11 +131,14 @@ fun ProfilePage() {
navigateToDestination = { navController.navigate(NavigationDestination.MyEmojiList) }
) {
items(myCreatedEmojiList.itemCount) { index ->
- myCreatedEmojiList[index]?.let {
+ myCreatedEmojiList[index]?.let { emoji ->
EmojiCell(
- emoji = it,
+ emoji = emoji,
displayMode = EmojiCellDisplay.HORIZONTAL,
- onSelected = {})
+ onSelected = {
+ emojiViewModel.currentEmoji = emoji
+ navController.navigate(NavigationDestination.PlayEmojiVideo)
+ })
}
}
}
@@ -142,11 +151,14 @@ fun ProfilePage() {
navigateToDestination = { navController.navigate(NavigationDestination.MySavedEmojiList) }
) {
items(mySavedEmojiList.itemCount) { index ->
- mySavedEmojiList[index]?.let {
+ mySavedEmojiList[index]?.let { emoji ->
EmojiCell(
- emoji = it,
+ emoji = emoji,
displayMode = EmojiCellDisplay.HORIZONTAL,
- onSelected = {})
+ onSelected = {
+ emojiViewModel.currentEmoji = emoji
+ navController.navigate(NavigationDestination.PlayEmojiVideo)
+ })
}
}
}
@@ -173,7 +185,10 @@ fun ProfilePage() {
needsCancelButton = true,
onDismissRequest = { showLogoutDialog = false },
dismiss = { showLogoutDialog = false },
- confirm = { userViewModel.logout() }
+ confirm = {
+ navController.navigateAsOrigin(NavigationDestination.Onboard)
+ userViewModel.logout()
+ }
)
}
@@ -186,7 +201,10 @@ fun ProfilePage() {
needsCancelButton = true,
onDismissRequest = { showSignOutDialog = false },
dismiss = { showSignOutDialog = false },
- confirm = { userViewModel.signOut() }
+ confirm = {
+ navController.navigateAsOrigin(NavigationDestination.Onboard)
+ userViewModel.signOut()
+ }
)
}
}
diff --git a/android/app/src/main/java/com/goliath/emojihub/views/SignUpPage.kt b/android/app/src/main/java/com/goliath/emojihub/views/SignUpPage.kt
index d4c9f7ad..60d96744 100644
--- a/android/app/src/main/java/com/goliath/emojihub/views/SignUpPage.kt
+++ b/android/app/src/main/java/com/goliath/emojihub/views/SignUpPage.kt
@@ -29,6 +29,8 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.hilt.navigation.compose.hiltViewModel
import com.goliath.emojihub.LocalNavController
+import com.goliath.emojihub.NavigationDestination
+import com.goliath.emojihub.navigateAsOrigin
import com.goliath.emojihub.ui.theme.Color
import com.goliath.emojihub.viewmodels.UserViewModel
import com.goliath.emojihub.views.components.CustomDialog
@@ -121,7 +123,10 @@ fun SignUpPage() {
if (showDialog) {
CustomDialog(
title = "완료",
- body = "계정 생성이 완료되었습니다."
+ body = "계정 생성이 완료되었습니다.",
+ onDismissRequest = { showDialog = false },
+ dismiss = { showDialog = false },
+ confirm = { navController.navigateAsOrigin(NavigationDestination.Login) }
)
}
}
diff --git a/android/app/src/main/java/com/goliath/emojihub/views/TransformVideoPage.kt b/android/app/src/main/java/com/goliath/emojihub/views/TransformVideoPage.kt
index eefa60c2..8263f562 100644
--- a/android/app/src/main/java/com/goliath/emojihub/views/TransformVideoPage.kt
+++ b/android/app/src/main/java/com/goliath/emojihub/views/TransformVideoPage.kt
@@ -29,6 +29,7 @@ import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.runtime.mutableStateOf
@@ -74,6 +75,13 @@ fun TransformVideoPage(
}
}
+ DisposableEffect(Unit) {
+ onDispose {
+ exoPlayer.stop()
+ exoPlayer.release()
+ }
+ }
+
var createdEmojiList by remember { mutableStateOf>(emptyList()) }
Scaffold(
diff --git a/android/app/src/main/java/com/goliath/emojihub/views/components/CreatedEmojiListView.kt b/android/app/src/main/java/com/goliath/emojihub/views/components/CreatedEmojiListView.kt
index 4fbade49..af9cf1f9 100644
--- a/android/app/src/main/java/com/goliath/emojihub/views/components/CreatedEmojiListView.kt
+++ b/android/app/src/main/java/com/goliath/emojihub/views/components/CreatedEmojiListView.kt
@@ -1,21 +1,52 @@
package com.goliath.emojihub.views.components
import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.grid.GridCells
+import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import androidx.paging.compose.collectAsLazyPagingItems
import com.goliath.emojihub.LocalNavController
+import com.goliath.emojihub.NavigationDestination
import com.goliath.emojihub.ui.theme.Color
+import com.goliath.emojihub.viewmodels.EmojiViewModel
@Composable
fun CreatedEmojiListView(
-
+ emojiViewModel: EmojiViewModel
) {
val navController = LocalNavController.current
+ val emojiList = emojiViewModel.myCreatedEmojiList.collectAsLazyPagingItems()
+
Column (
Modifier.background(Color.White)
) {
- TopNavigationBar(navigate = { navController.popBackStack() })
+ TopNavigationBar(
+ title = "내가 만든 이모지",
+ navigate = { navController.popBackStack() }
+ )
+
+ Column(Modifier.padding(horizontal = 16.dp)) {
+ LazyVerticalGrid(
+ columns = GridCells.Fixed(2),
+ modifier = Modifier.padding(top = 18.dp),
+ horizontalArrangement = Arrangement.spacedBy(4.dp),
+ verticalArrangement = Arrangement.spacedBy(4.dp),
+ ) {
+ items(emojiList.itemCount) { index ->
+ emojiList[index]?.let{
+ EmojiCell(emoji = it, displayMode = EmojiCellDisplay.VERTICAL) { selectedEmoji ->
+ emojiViewModel.currentEmoji = selectedEmoji
+ navController.navigate(NavigationDestination.PlayEmojiVideo)
+ }
+ }
+ }
+ }
+ }
}
}
\ No newline at end of file
diff --git a/android/app/src/main/java/com/goliath/emojihub/views/components/CreatedPostListView.kt b/android/app/src/main/java/com/goliath/emojihub/views/components/CreatedPostListView.kt
index 59a0f116..f918dd5d 100644
--- a/android/app/src/main/java/com/goliath/emojihub/views/components/CreatedPostListView.kt
+++ b/android/app/src/main/java/com/goliath/emojihub/views/components/CreatedPostListView.kt
@@ -1,21 +1,63 @@
package com.goliath.emojihub.views.components
import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.material3.Divider
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.paging.PagingData
+import androidx.paging.compose.collectAsLazyPagingItems
+import com.goliath.emojihub.LocalBottomSheetController
import com.goliath.emojihub.LocalNavController
+import com.goliath.emojihub.models.Post
import com.goliath.emojihub.ui.theme.Color
+import com.goliath.emojihub.viewmodels.EmojiViewModel
+import kotlinx.coroutines.flow.StateFlow
@Composable
fun CreatedPostListView(
-
+ postList: StateFlow>
) {
val navController = LocalNavController.current
+ val bottomSheetController = LocalBottomSheetController.current
+
+ val emojiViewModel = hiltViewModel()
+
+ val pagingPostList = postList.collectAsLazyPagingItems()
+
+ Column (Modifier.background(Color.White)) {
+ TopNavigationBar(
+ title = "내가 작성한 포스트",
+ navigate = { navController.popBackStack() }
+ )
+
+ Box(
+ Modifier
+ .weight(1f)
+ .fillMaxWidth()) {
+ LazyColumn(
+ modifier = Modifier.fillMaxWidth(),
+ contentPadding = PaddingValues(horizontal = 16.dp)
+ ) {
+ items(pagingPostList.itemCount) { index ->
+ pagingPostList[index]?.let {
+ PostCell(post = it)
+ Divider(color = Color.EmojiHubDividerColor, thickness = 0.5.dp)
+ }
+ }
+ }
+ }
+ }
- Column (
- Modifier.background(Color.White)
- ) {
- TopNavigationBar(navigate = { navController.popBackStack() })
+ if (bottomSheetController.isVisible) {
+ CustomBottomSheet(
+ bottomSheetContent = emojiViewModel.bottomSheetContent
+ )
}
}
\ No newline at end of file
diff --git a/android/app/src/main/java/com/goliath/emojihub/views/components/CustomBottomSheet.kt b/android/app/src/main/java/com/goliath/emojihub/views/components/CustomBottomSheet.kt
index 4adb5b38..b5938eb8 100644
--- a/android/app/src/main/java/com/goliath/emojihub/views/components/CustomBottomSheet.kt
+++ b/android/app/src/main/java/com/goliath/emojihub/views/components/CustomBottomSheet.kt
@@ -1,6 +1,5 @@
package com.goliath.emojihub.views.components
-import android.util.Log
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
@@ -11,7 +10,6 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
-import androidx.compose.foundation.lazy.grid.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.ButtonDefaults
import androidx.compose.material.OutlinedButton
@@ -36,13 +34,15 @@ import androidx.paging.compose.collectAsLazyPagingItems
import com.goliath.emojihub.LocalBottomSheetController
import com.goliath.emojihub.LocalNavController
import com.goliath.emojihub.NavigationDestination
-import com.goliath.emojihub.ui.theme.Color.EmojiHubDividerColor
import com.goliath.emojihub.extensions.toEmoji
import com.goliath.emojihub.models.Emoji
+import com.goliath.emojihub.ui.theme.Color.EmojiHubDividerColor
import com.goliath.emojihub.ui.theme.Color.LightGray
-import kotlinx.coroutines.launch
import com.goliath.emojihub.ui.theme.Color.White
import com.goliath.emojihub.viewmodels.EmojiViewModel
+import com.goliath.emojihub.viewmodels.PostViewModel
+import com.goliath.emojihub.viewmodels.ReactionViewModel
+import kotlinx.coroutines.launch
enum class BottomSheetContent {
VIEW_REACTION, ADD_REACTION, EMPTY
@@ -51,27 +51,36 @@ enum class BottomSheetContent {
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun CustomBottomSheet (
- bottomSheetContent: BottomSheetContent,
- emojiList: List,
- emojiCellClicked: (Emoji) -> Unit
+ bottomSheetContent: BottomSheetContent
){
val bottomSheetState = LocalBottomSheetController.current
val coroutineScope = rememberCoroutineScope()
val viewModel = hiltViewModel()
- val navController = LocalNavController.current
- var selectedEmojiClass by remember { mutableStateOf("전체") }
- val emojisByClass = emojiList.groupBy { it.unicode }
- val emojiClassFilters = listOf("전체") + emojisByClass.keys.toList()
- val emojiCounts = emojisByClass.mapValues { it.value.size }
+ val reactionViewModel = hiltViewModel()
+ val postViewModel = hiltViewModel()
+ val navController = LocalNavController.current
val myCreatedEmojiList = viewModel.myCreatedEmojiList.collectAsLazyPagingItems()
val mySavedEmojiList = viewModel.mySavedEmojiList.collectAsLazyPagingItems()
+ val reactionList = reactionViewModel.reactionList.collectAsLazyPagingItems()
var displayMyCreatedEmojis by remember { mutableStateOf(true) }
- LaunchedEffect(Unit) {
- viewModel.fetchMyCreatedEmojiList()
- viewModel.fetchMySavedEmojiList()
+ var selectedEmojiClass by remember { mutableStateOf("전체") }
+ val emojisByUnicode = postViewModel.currentPost.reaction.groupBy { it.emoji_unicode }
+ val emojiUnicodeFilters = listOf("전체") + emojisByUnicode.keys.toList()
+ val emojiCounts = emojisByUnicode.mapValues { it.value.size }
+ var selectedEmojiUnicode by remember { mutableStateOf("") }
+
+ LaunchedEffect(selectedEmojiUnicode) {
+ reactionViewModel.fetchReactionList(postViewModel.currentPostId, selectedEmojiUnicode)
+ }
+
+ LaunchedEffect(bottomSheetContent) {
+ if(bottomSheetContent == BottomSheetContent.ADD_REACTION) {
+ viewModel.fetchMyCreatedEmojiList()
+ viewModel.fetchMySavedEmojiList()
+ }
}
ModalBottomSheet(
@@ -93,16 +102,17 @@ fun CustomBottomSheet (
verticalAlignment = Alignment.CenterVertically
) {
EmojiClassFilterRow(
- emojiClass = emojiClassFilters,
+ emojiClass = emojiUnicodeFilters,
emojiCounts = emojiCounts,
onEmojiClassSelected = { selectedEmojiClass = it}
) {
- items(emojiClassFilters.size) { emojiClass ->
+ items(emojiUnicodeFilters.size) { unicode ->
EmojiClassFilterButton(
- text = if (emojiClassFilters[emojiClass] == "전체") "전체" else "${emojiClassFilters[emojiClass].toEmoji()}${emojiCounts[emojiClassFilters[emojiClass]]}",
- isSelected = emojiClassFilters[emojiClass] == selectedEmojiClass,
+ text = if (emojiUnicodeFilters[unicode] == "전체") "전체" else "${emojiUnicodeFilters[unicode].toEmoji()}${emojiCounts[emojiUnicodeFilters[unicode]]}",
+ isSelected = emojiUnicodeFilters[unicode] == selectedEmojiClass,
onSelected = {
- selectedEmojiClass = emojiClassFilters[emojiClass]
+ selectedEmojiClass = emojiUnicodeFilters[unicode]
+ selectedEmojiUnicode = if (emojiUnicodeFilters[unicode] == "전체") "" else emojiUnicodeFilters[unicode]
}
)
}
@@ -174,10 +184,16 @@ fun CustomBottomSheet (
BottomSheetContent.EMPTY -> {}
BottomSheetContent.VIEW_REACTION -> {
- items(if (selectedEmojiClass == "전체") emojiList else emojiList.filter { it.unicode == selectedEmojiClass }, key = { it.id }) { emoji ->
- EmojiCell(emoji = emoji, displayMode = EmojiCellDisplay.VERTICAL) {selectedEmoji ->
- viewModel.currentEmoji = selectedEmoji
- navController.navigate(NavigationDestination.PlayEmojiVideo) //FIXME: make a new destination or fix PlayEmojiVideo's back stack to include BottomSheet
+ items(reactionList.itemCount) { index ->
+ reactionList[index]?.let {
+ val emojiDto = it.emojiDto
+ val reactedBy = it.createdBy
+ if (emojiDto != null){
+ ReactionEmojiCell(emoji = Emoji(emojiDto), reactedBy = reactedBy) { selectedEmoji ->
+ viewModel.currentEmoji = selectedEmoji
+ navController.navigate(NavigationDestination.PlayEmojiVideo)
+ }
+ }
}
}
}
@@ -187,7 +203,15 @@ fun CustomBottomSheet (
items(myCreatedEmojiList.itemCount) { index ->
myCreatedEmojiList[index]?.let {
EmojiCell(emoji = it, displayMode = EmojiCellDisplay.VERTICAL) {
- //TODO: add reaction to post
+ coroutineScope.launch {
+ val success = reactionViewModel.uploadReaction(postId = postViewModel.currentPostId, emojiId = it.id)
+ if (success) {
+ coroutineScope.launch {
+ bottomSheetState.hide()
+ }
+ postViewModel.fetchPostList()
+ }
+ }
}
}
}
@@ -195,7 +219,15 @@ fun CustomBottomSheet (
items(mySavedEmojiList.itemCount) { index ->
mySavedEmojiList[index]?.let {
EmojiCell(emoji = it, displayMode = EmojiCellDisplay.VERTICAL) {
- //TODO: add emoji reaction to post
+ coroutineScope.launch {
+ val success = reactionViewModel.uploadReaction(postId = postViewModel.currentPostId, emojiId = it.id)
+ if (success) {
+ coroutineScope.launch {
+ bottomSheetState.hide()
+ }
+ postViewModel.fetchPostList()
+ }
+ }
}
}
}
diff --git a/android/app/src/main/java/com/goliath/emojihub/views/components/EmptyProfile.kt b/android/app/src/main/java/com/goliath/emojihub/views/components/EmptyProfile.kt
index 4e057e39..5465dabd 100644
--- a/android/app/src/main/java/com/goliath/emojihub/views/components/EmptyProfile.kt
+++ b/android/app/src/main/java/com/goliath/emojihub/views/components/EmptyProfile.kt
@@ -21,6 +21,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.goliath.emojihub.LocalNavController
import com.goliath.emojihub.NavigationDestination
+import com.goliath.emojihub.navigateAsOrigin
import com.goliath.emojihub.ui.theme.Color
@Composable
@@ -54,7 +55,7 @@ fun EmptyProfile() {
}
Spacer(modifier = Modifier.weight(1f))
Button(
- onClick = { navController.navigate(NavigationDestination.Onboard) },
+ onClick = { navController.navigateAsOrigin(NavigationDestination.Onboard) },
modifier = Modifier.fillMaxWidth().height(44.dp),
shape = RoundedCornerShape(50),
colors = ButtonDefaults.buttonColors(
diff --git a/android/app/src/main/java/com/goliath/emojihub/views/components/PlayEmojiView.kt b/android/app/src/main/java/com/goliath/emojihub/views/components/PlayEmojiView.kt
index 5327e414..d0a46eda 100644
--- a/android/app/src/main/java/com/goliath/emojihub/views/components/PlayEmojiView.kt
+++ b/android/app/src/main/java/com/goliath/emojihub/views/components/PlayEmojiView.kt
@@ -19,10 +19,11 @@ import androidx.compose.material.icons.filled.FileDownload
import androidx.compose.material.icons.filled.FileDownloadOff
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
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
@@ -33,29 +34,46 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.viewinterop.AndroidView
+import androidx.lifecycle.Observer
+import androidx.lifecycle.asLiveData
import androidx.media3.common.MediaItem
import androidx.media3.common.Player
import androidx.media3.exoplayer.ExoPlayer
+import androidx.media3.ui.AspectRatioFrameLayout
import androidx.media3.ui.PlayerView
import com.goliath.emojihub.LocalNavController
+import com.goliath.emojihub.NavigationDestination
import com.goliath.emojihub.extensions.toEmoji
+import com.goliath.emojihub.models.Emoji
+import com.goliath.emojihub.models.User
+import com.goliath.emojihub.models.UserDetails
+import com.goliath.emojihub.navigateAsOrigin
import com.goliath.emojihub.ui.theme.Color
import com.goliath.emojihub.viewmodels.EmojiViewModel
-import kotlinx.coroutines.launch
+import com.goliath.emojihub.viewmodels.UserViewModel
+@androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
@Composable
fun PlayEmojiView(
- viewModel: EmojiViewModel
+ emojiViewModel: EmojiViewModel,
+ userViewModel: UserViewModel
) {
- // Play video
val context = LocalContext.current
val navController = LocalNavController.current
- val currentEmoji = viewModel.currentEmoji!!
+ val currentEmoji = emojiViewModel.currentEmoji
+ val currentUser = userViewModel.userState.collectAsState().value
+ val currentUserDetails = userViewModel.userDetailsState.collectAsState().value
- var savedCount by remember { mutableStateOf(currentEmoji.savedCount) }
- var isSaved by remember { mutableStateOf(currentEmoji.isSaved) }
+ var savedCount by remember { mutableIntStateOf(currentEmoji.savedCount) }
+ var isSavedEmoji by remember { mutableStateOf(checkEmojiHasSaved(currentUserDetails, currentEmoji)) }
+ val isCreatedEmoji by remember { mutableStateOf(checkEmojiHasCreated(currentUser, currentEmoji)) }
+
+ val isSaveSuccess = emojiViewModel.saveEmojiState.asLiveData()
+ val isUnSaveSuccess = emojiViewModel.unSaveEmojiState.asLiveData()
+ var showNonUserDialog by remember { mutableStateOf(false) }
var showUnSaveDialog by remember { mutableStateOf(false) }
+ var showCreatedEmojiDialog by remember { mutableStateOf(false) }
val exoPlayer = remember {
ExoPlayer.Builder(context).build().apply {
@@ -69,6 +87,7 @@ fun PlayEmojiView(
onDispose {
exoPlayer.stop()
exoPlayer.release()
+ userViewModel.fetchMyInfo()
}
}
@@ -85,6 +104,7 @@ fun PlayEmojiView(
modifier = Modifier.fillMaxSize(),
factory = {
PlayerView(it).apply {
+ this.resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FILL
player = exoPlayer
}
}
@@ -93,6 +113,7 @@ fun PlayEmojiView(
TopNavigationBar(
title = "@" + currentEmoji.createdBy,
largeTitle = false,
+ needsElevation = false,
navigate = { navController.popBackStack() }
) {}
@@ -106,20 +127,17 @@ fun PlayEmojiView(
IconButton(
modifier = Modifier.size(40.dp),
onClick = {
- if (isSaved) {
- showUnSaveDialog = true
- Toast.makeText(context, "Emoji unsaved!", Toast.LENGTH_SHORT).show()
- } else {
- viewModel.saveEmoji(currentEmoji.id)
- isSaved = true
- savedCount ++
- Toast.makeText(context, "Emoji saved!", Toast.LENGTH_SHORT).show()
+ when {
+ currentUser == null -> showNonUserDialog = true
+ isSavedEmoji -> showUnSaveDialog = true
+ isCreatedEmoji -> showCreatedEmojiDialog = true
+ else -> emojiViewModel.saveEmoji(currentEmoji.id)
}
}
) {
Icon(
imageVector =
- if (isSaved) {
+ if (isSavedEmoji) {
Icons.Default.FileDownloadOff
} else {
Icons.Default.FileDownload
@@ -149,10 +167,50 @@ fun PlayEmojiView(
fontSize = 28.sp,
)
}
-
Spacer(modifier = Modifier.padding(bottom = 32.dp))
}
}
+
+ val saveEmojiStateObserver = Observer {
+ if (it == 1) {
+ isSavedEmoji = true
+ savedCount++
+ Toast.makeText(context, "Emoji saved!", Toast.LENGTH_SHORT).show()
+ } else if (it == 0) {
+ Toast.makeText(context, "Emoji save failed!", Toast.LENGTH_SHORT).show()
+ }
+ emojiViewModel.resetSaveEmojiState()
+ }
+
+ val unSaveEmojiStateObserver = Observer {
+ if (it == 1) {
+ isSavedEmoji = false
+ savedCount--
+ showUnSaveDialog = false
+ Toast.makeText(context, "Emoji unsaved!", Toast.LENGTH_SHORT).show()
+ } else if (it == 0) {
+ showUnSaveDialog = false
+ Toast.makeText(context, "Emoji unsave failed!", Toast.LENGTH_SHORT).show()
+ }
+ emojiViewModel.resetUnSaveEmojiState()
+ }
+
+ isSaveSuccess.observe(navController.currentBackStackEntry!!, saveEmojiStateObserver)
+ isUnSaveSuccess.observe(navController.currentBackStackEntry!!, unSaveEmojiStateObserver)
+
+ if (showNonUserDialog) {
+ CustomDialog(
+ title = "비회원 모드",
+ body = "회원만 이모지를 저장할 수 있습니다. 로그인 화면으로 이동할까요?",
+ confirmText = "이동",
+ needsCancelButton = true,
+ onDismissRequest = { showNonUserDialog = false },
+ dismiss = { showNonUserDialog = false },
+ confirm = {
+ navController.navigateAsOrigin(NavigationDestination.Onboard)
+ }
+ )
+ }
if (showUnSaveDialog) {
CustomDialog(
@@ -162,13 +220,35 @@ fun PlayEmojiView(
isDestructive = true,
needsCancelButton = true,
onDismissRequest = { showUnSaveDialog = false },
- confirm = {
- viewModel.unSaveEmoji(currentEmoji.id)
- isSaved = false
- savedCount --
- showUnSaveDialog = false },
+ confirm = { emojiViewModel.unSaveEmoji(currentEmoji.id) },
dismiss = { showUnSaveDialog = false }
)
}
+
+ if (showCreatedEmojiDialog) {
+ CustomDialog(
+ title = "내가 만든 이모지",
+ body = "내가 만든 이모지는 저장할 수 없습니다.",
+ confirmText = "확인",
+ needsCancelButton = false,
+ onDismissRequest = { showCreatedEmojiDialog = false },
+ confirm = { showCreatedEmojiDialog = false }
+ )
+ }
}
+}
+
+fun checkEmojiHasSaved(currentUserDetails: UserDetails?, currentEmoji: Emoji): Boolean {
+ if (currentUserDetails == null) return false
+ Log.d("checkEmojiHasSaved", "currentUserDetails.savedEmojiList: ${currentUserDetails.savedEmojiList}")
+ Log.d("checkEmojiHasSaved", "currentEmoji.id: ${currentEmoji.id}")
+ if (currentUserDetails.savedEmojiList?.contains(currentEmoji.id) == true)
+ return true
+ return false
+}
+
+fun checkEmojiHasCreated(currentUser: User?, currentEmoji: Emoji): Boolean {
+ if (currentUser == null) return false
+ if (currentUser.name == currentEmoji.createdBy) return true
+ return false
}
\ No newline at end of file
diff --git a/android/app/src/main/java/com/goliath/emojihub/views/components/PostCell.kt b/android/app/src/main/java/com/goliath/emojihub/views/components/PostCell.kt
index a388bb3c..b27805d7 100644
--- a/android/app/src/main/java/com/goliath/emojihub/views/components/PostCell.kt
+++ b/android/app/src/main/java/com/goliath/emojihub/views/components/PostCell.kt
@@ -15,7 +15,11 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.AddReaction
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.text.font.FontWeight
@@ -23,19 +27,28 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.hilt.navigation.compose.hiltViewModel
import com.goliath.emojihub.LocalBottomSheetController
+import com.goliath.emojihub.LocalNavController
+import com.goliath.emojihub.NavigationDestination
import com.goliath.emojihub.extensions.reactionsToString
import com.goliath.emojihub.models.Post
+import com.goliath.emojihub.navigateAsOrigin
import com.goliath.emojihub.ui.theme.Color.EmojiHubDetailLabel
import com.goliath.emojihub.viewmodels.EmojiViewModel
+import com.goliath.emojihub.viewmodels.PostViewModel
import kotlinx.coroutines.launch
@Composable
fun PostCell(
- post: Post
+ post: Post,
+ isNonUser: Boolean = false
) {
+ val navController = LocalNavController.current
val bottomSheetState = LocalBottomSheetController.current
val coroutineScope = rememberCoroutineScope()
val emojiViewModel = hiltViewModel()
+ val postViewModel = hiltViewModel()
+
+ var showNonUserDialog by remember { mutableStateOf(false) }
Box(
modifier = Modifier
@@ -79,13 +92,15 @@ fun PostCell(
onClick = {
coroutineScope.launch {
emojiViewModel.bottomSheetContent = BottomSheetContent.VIEW_REACTION
+ postViewModel.currentPostId = post.id
+ postViewModel.currentPost = post
bottomSheetState.show()
}
},
) {
Text(
- text = reactionsToString(post.reaction), //TODO: Replace with reaction_unicode sent from backend
+ text = reactionsToString(post.reaction),
fontSize = 13.sp,
color = EmojiHubDetailLabel
)
@@ -99,9 +114,15 @@ fun PostCell(
}
IconButton(onClick = {
- coroutineScope.launch {
- emojiViewModel.bottomSheetContent = BottomSheetContent.ADD_REACTION
- bottomSheetState.show()
+ if (isNonUser) {
+ showNonUserDialog = true
+ } else {
+ coroutineScope.launch {
+ emojiViewModel.bottomSheetContent = BottomSheetContent.ADD_REACTION
+ postViewModel.currentPostId = post.id
+ postViewModel.currentPost = post
+ bottomSheetState.show()
+ }
}
}) {
Icon(
@@ -111,5 +132,19 @@ fun PostCell(
}
}
}
+
+ if (showNonUserDialog) {
+ CustomDialog(
+ title = "비회원 모드",
+ body = "회원만 게시물에 반응을 남길 수 있습니다. 로그인 화면으로 이동할까요?",
+ confirmText = "이동",
+ needsCancelButton = true,
+ onDismissRequest = { showNonUserDialog = false },
+ dismiss = { showNonUserDialog = false },
+ confirm = {
+ navController.navigateAsOrigin(NavigationDestination.Onboard)
+ }
+ )
+ }
}
}
\ No newline at end of file
diff --git a/android/app/src/main/java/com/goliath/emojihub/views/components/ReactionEmojiCell.kt b/android/app/src/main/java/com/goliath/emojihub/views/components/ReactionEmojiCell.kt
new file mode 100644
index 00000000..3b287dcd
--- /dev/null
+++ b/android/app/src/main/java/com/goliath/emojihub/views/components/ReactionEmojiCell.kt
@@ -0,0 +1,129 @@
+package com.goliath.emojihub.views.components
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+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.size
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.Card
+import androidx.compose.material.Icon
+import androidx.compose.material.Text
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.SaveAlt
+import androidx.compose.material3.Divider
+import androidx.compose.material3.IconButton
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.alpha
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.hilt.navigation.compose.hiltViewModel
+import coil.compose.rememberAsyncImagePainter
+import com.goliath.emojihub.extensions.toEmoji
+import com.goliath.emojihub.models.Emoji
+import com.goliath.emojihub.viewmodels.UserViewModel
+
+
+@Composable
+fun ReactionEmojiCell (
+ emoji: Emoji,
+ reactedBy: String,
+ onSelected: (Emoji) -> Unit
+) {
+ val thumbnailLink = emoji.thumbnailLink.takeIf{ it.isNotEmpty()} ?:"https://i.pinimg.com/236x/4b/05/0c/4b050ca4fcf588eedc58aa6135f5eecf.jpg"
+
+ val userViewModel = hiltViewModel()
+ val currentUser = userViewModel.userState.collectAsState().value
+
+ val textModifier = if (currentUser?.name == reactedBy) {
+ Modifier
+ .background(color = com.goliath.emojihub.ui.theme.Color.EmojiHubYellow, shape = RoundedCornerShape(10.dp))
+ .padding(4.dp)
+ } else {
+ Modifier
+ }
+
+ Column {
+ Card (
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(292.dp)
+ .clickable { onSelected(emoji) },
+ shape = RoundedCornerShape(4.dp),
+ elevation = 0.dp
+ ) {
+ Box(
+ Modifier
+ .fillMaxSize()
+ .background(Color.Gray)
+ .alpha(0.25F))
+
+ Image(
+ painter = rememberAsyncImagePainter(thumbnailLink),
+ contentDescription = null,
+ modifier = Modifier.fillMaxWidth(),
+ contentScale = ContentScale.Crop
+ )
+
+ Box(modifier = Modifier.padding(8.dp)) {
+ Text(
+ text = "@" + emoji.createdBy,
+ modifier = Modifier.align(Alignment.TopStart),
+ fontSize = 12.sp,
+ color = com.goliath.emojihub.ui.theme.Color.White
+ )
+
+ Row(
+ modifier = Modifier.align(Alignment.BottomStart),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ IconButton(
+ modifier = Modifier.size(16.dp),
+ onClick = {}
+ ) {
+ Icon(
+ imageVector = Icons.Filled.SaveAlt,
+ contentDescription = "",
+ tint = com.goliath.emojihub.ui.theme.Color.White
+ )
+ }
+
+ Text(
+ text = emoji.savedCount.toString(),
+ modifier = Modifier.padding(start = 4.dp),
+ fontSize = 12.sp,
+ color = com.goliath.emojihub.ui.theme.Color.White
+ )
+ }
+ }
+
+ Box(contentAlignment = Alignment.Center) {
+ Text(
+ text = emoji.unicode.toEmoji(),
+ fontSize = 44.sp
+ )
+ }
+ }
+ if (currentUser != null) {
+ Text(
+ text = "@$reactedBy",
+ modifier = textModifier.align(Alignment.Start),
+ fontSize = 12.sp,
+ color = com.goliath.emojihub.ui.theme.Color.Black
+ )
+ Spacer(modifier = Modifier.height(16.dp))
+ }
+ }
+}
\ No newline at end of file
diff --git a/android/app/src/main/java/com/goliath/emojihub/views/components/SaveEmojiListView.kt b/android/app/src/main/java/com/goliath/emojihub/views/components/SaveEmojiListView.kt
deleted file mode 100644
index 7907dc1c..00000000
--- a/android/app/src/main/java/com/goliath/emojihub/views/components/SaveEmojiListView.kt
+++ /dev/null
@@ -1,21 +0,0 @@
-package com.goliath.emojihub.views.components
-
-import androidx.compose.foundation.background
-import androidx.compose.foundation.layout.Column
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import com.goliath.emojihub.LocalNavController
-import com.goliath.emojihub.ui.theme.Color
-
-@Composable
-fun SavedEmojiListView(
-
-) {
- val navController = LocalNavController.current
-
- Column (
- Modifier.background(Color.White)
- ) {
- TopNavigationBar(navigate = { navController.popBackStack() })
- }
-}
\ No newline at end of file
diff --git a/android/app/src/main/java/com/goliath/emojihub/views/components/SavedEmojiListView.kt b/android/app/src/main/java/com/goliath/emojihub/views/components/SavedEmojiListView.kt
new file mode 100644
index 00000000..abde7ec7
--- /dev/null
+++ b/android/app/src/main/java/com/goliath/emojihub/views/components/SavedEmojiListView.kt
@@ -0,0 +1,52 @@
+package com.goliath.emojihub.views.components
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.grid.GridCells
+import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import androidx.paging.compose.collectAsLazyPagingItems
+import com.goliath.emojihub.LocalNavController
+import com.goliath.emojihub.NavigationDestination
+import com.goliath.emojihub.ui.theme.Color
+import com.goliath.emojihub.viewmodels.EmojiViewModel
+
+@Composable
+fun SavedEmojiListView(
+ emojiViewModel: EmojiViewModel
+) {
+ val navController = LocalNavController.current
+
+ val emojiList = emojiViewModel.mySavedEmojiList.collectAsLazyPagingItems()
+
+ Column (
+ Modifier.background(Color.White)
+ ) {
+ TopNavigationBar(
+ title = "저장된 이모지",
+ navigate = { navController.popBackStack() }
+ )
+
+ Column(Modifier.padding(horizontal = 16.dp)) {
+ LazyVerticalGrid(
+ columns = GridCells.Fixed(2),
+ modifier = Modifier.padding(top = 18.dp),
+ horizontalArrangement = Arrangement.spacedBy(4.dp),
+ verticalArrangement = Arrangement.spacedBy(4.dp),
+ ) {
+ items(emojiList.itemCount) { index ->
+ emojiList[index]?.let{
+ EmojiCell(emoji = it, displayMode = EmojiCellDisplay.VERTICAL) { selectedEmoji ->
+ emojiViewModel.currentEmoji = selectedEmoji
+ navController.navigate(NavigationDestination.PlayEmojiVideo)
+ }
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/android/app/src/main/java/com/goliath/emojihub/views/components/TopNavigationBar.kt b/android/app/src/main/java/com/goliath/emojihub/views/components/TopNavigationBar.kt
index 63353cef..1b1a490e 100644
--- a/android/app/src/main/java/com/goliath/emojihub/views/components/TopNavigationBar.kt
+++ b/android/app/src/main/java/com/goliath/emojihub/views/components/TopNavigationBar.kt
@@ -28,13 +28,14 @@ fun TopNavigationBar(
title: String = "",
largeTitle: Boolean = true,
shouldNavigate: Boolean = true,
+ needsElevation: Boolean = true,
navigate: () -> Unit = {},
actions: @Composable () -> Unit = {},
) {
Surface(
color = Color.Transparent,
shape = RectangleShape,
- elevation = 1.dp,
+ elevation = if (needsElevation) 1.dp else 0.dp,
modifier = Modifier.height(64.dp),
) {
Box(Modifier.padding(horizontal = 4.dp)) {
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 ddac0fc5..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
@@ -3,6 +3,7 @@ package com.goliath.emojihub.repositories.remote
import androidx.paging.testing.asSnapshot
import com.goliath.emojihub.data_sources.CustomError
import com.goliath.emojihub.data_sources.api.EmojiApi
+import com.goliath.emojihub.data_sources.remote.EmojiDataSource
import com.goliath.emojihub.mockLogClass
import com.goliath.emojihub.models.EmojiDto
import com.goliath.emojihub.models.UploadEmojiDto
@@ -28,8 +29,8 @@ import java.lang.Exception
@RunWith(JUnit4::class)
class EmojiRepositoryImplTest {
private val emojiApi = mockk()
- private val context = mockk()
- private val emojiRepositoryImpl = EmojiRepositoryImpl(emojiApi, context)
+ private val emojiDataSource = mockk()
+ private val emojiRepositoryImpl = EmojiRepositoryImpl(emojiApi, emojiDataSource)
private val sampleEmojiDto = EmojiDto(
createdBy = "channn",
createdAt = "2023-11-24 14:25:05",
@@ -56,7 +57,7 @@ class EmojiRepositoryImplTest {
emojiApi.fetchEmojiList(any(), any(), any())
} returns Response.success(sampleEmojiDtoList)
// when
- val fetchedEmojiPagingDataFlow = runBlocking { emojiRepositoryImpl.fetchEmojiList() }
+ val fetchedEmojiPagingDataFlow = runBlocking { emojiRepositoryImpl.fetchEmojiList(1) }
val fetchedEmojiDtoList = runBlocking { fetchedEmojiPagingDataFlow.asSnapshot() }
// then
coVerify(exactly = 2) { emojiApi.fetchEmojiList(any(), any(), any()) }
@@ -123,22 +124,22 @@ class EmojiRepositoryImplTest {
emojiApi.uploadEmoji(any(), any(), any())
} returns Response.success(Unit)
- val emojiRepositoryImpl = spyk(EmojiRepositoryImpl(emojiApi, context))
+ val emojiRepositoryImpl = spyk(EmojiRepositoryImpl(emojiApi, emojiDataSource))
every {
- emojiRepositoryImpl.createVideoThumbnail(any(), any())
+ emojiDataSource.createVideoThumbNail(any())
} 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
- fun uploadEmoji_failureWithIOException_returnsFalse() {
+ fun uploadEmoji_failureWithException_throwsException() {
// given
mockkStatic(File::class)
val sampleVideoFile = File("sampleVideoFile")
@@ -147,18 +148,21 @@ class EmojiRepositoryImplTest {
emojiApi.uploadEmoji(any(), any(), any())
} throws mockk()
- val emojiRepositoryImpl = spyk(EmojiRepositoryImpl(emojiApi, context))
+ val emojiRepositoryImpl = spyk(EmojiRepositoryImpl(emojiApi, emojiDataSource))
every {
- emojiRepositoryImpl.createVideoThumbnail(any(), any())
+ emojiDataSource.createVideoThumbNail(any())
} returns File("sampleThumbnailFile")
// when
- val isUploaded = runBlocking {
- emojiRepositoryImpl.uploadEmoji(sampleVideoFile, sampleUploadEmojiDto)
+ try {
+ runBlocking {
+ emojiRepositoryImpl.uploadEmoji(sampleVideoFile, sampleUploadEmojiDto)
+ }
+ } catch (e: Exception) {
+ // then
+ coVerify(exactly = 1) { emojiApi.uploadEmoji(any(), any(), any()) }
+ assertTrue(e is IOException)
}
- // then
- coVerify(exactly = 1) { emojiApi.uploadEmoji(any(), any(), any()) }
- assertFalse(isUploaded)
}
@Test
@@ -171,106 +175,63 @@ class EmojiRepositoryImplTest {
emojiApi.uploadEmoji(any(), any(), any())
} throws mockk()
- val emojiRepositoryImpl = spyk(EmojiRepositoryImpl(emojiApi, context))
+ val emojiRepositoryImpl = spyk(EmojiRepositoryImpl(emojiApi, emojiDataSource))
every {
- emojiRepositoryImpl.createVideoThumbnail(any(), any())
+ emojiDataSource.createVideoThumbNail(any())
} returns File("sampleThumbnailFile")
// when
- val isUploaded = runBlocking {
- emojiRepositoryImpl.uploadEmoji(sampleVideoFile, sampleUploadEmojiDto)
+ try {
+ runBlocking {
+ emojiRepositoryImpl.uploadEmoji(sampleVideoFile, sampleUploadEmojiDto)
+ }
+ } catch (e: Exception) {
+ // then
+ coVerify(exactly = 1) { emojiApi.uploadEmoji(any(), any(), any()) }
+ assertTrue(e is HttpException)
}
- // then
- coVerify(exactly = 1) { emojiApi.uploadEmoji(any(), any(), any()) }
- assertFalse(isUploaded)
- }
-
- @Test
- fun uploadEmoji_failureWithOtherException_returnsFalse() {
- // given
- mockkStatic(File::class)
- val sampleVideoFile = File("sampleVideoFile")
- val sampleUploadEmojiDto = mockk()
- coEvery {
- emojiApi.uploadEmoji(any(), any(), any())
- } throws spyk()
-
- val emojiRepositoryImpl = spyk(EmojiRepositoryImpl(emojiApi, context))
- every {
- emojiRepositoryImpl.createVideoThumbnail(any(), any())
- } returns File("sampleThumbnailFile")
-
- // when
- val isUploaded = runBlocking {
- emojiRepositoryImpl.uploadEmoji(sampleVideoFile, sampleUploadEmojiDto)
- }
- // then
- coVerify(exactly = 1) { emojiApi.uploadEmoji(any(), any(), any()) }
- assertFalse(isUploaded)
}
@Test
- fun saveEmoji_success_returnsSuccessResultUnit() {
+ fun saveEmoji_success_returnsSuccessResponseUnit() {
// given
val sampleEmojiId = "1234"
coEvery {
emojiApi.saveEmoji(any())
} returns Response.success(Unit)
// when
- val result = runBlocking { emojiRepositoryImpl.saveEmoji(sampleEmojiId) }
+ val response = runBlocking { emojiRepositoryImpl.saveEmoji(sampleEmojiId) }
// then
coVerify(exactly = 1) { emojiApi.saveEmoji(sampleEmojiId) }
- assert(result.isSuccess)
+ assertTrue(response.isSuccessful)
}
@Test
- fun saveEmoji_failure_returnsFailureResultUnit() {
+ fun saveEmoji_failure_returnsFailureResponseUnit() {
// given
val sampleEmojiId = "1234"
coEvery {
emojiApi.saveEmoji(any())
} returns Response.error(CustomError.BAD_REQUEST.statusCode, mockk(relaxed=true))
// when
- val result = runBlocking { emojiRepositoryImpl.saveEmoji(sampleEmojiId) }
- // then
- coVerify(exactly = 1) { emojiApi.saveEmoji(sampleEmojiId) }
- assertTrue(result.isFailure)
- assertEquals(
- "Failed to save Emoji (Id: $sampleEmojiId), 400",
- result.exceptionOrNull()?.message
- )
- }
-
- @Test
- fun saveEmoji_exception_returnsFailureResultUnit() {
- // given
- val sampleEmojiId = "1234"
- coEvery {
- emojiApi.saveEmoji(any())
- } throws HttpException(mockk(relaxed=true))
- // when
- val result = runBlocking { emojiRepositoryImpl.saveEmoji(sampleEmojiId) }
+ val response = runBlocking { emojiRepositoryImpl.saveEmoji(sampleEmojiId) }
// then
coVerify(exactly = 1) { emojiApi.saveEmoji(sampleEmojiId) }
- assertTrue(result.isFailure)
- assertEquals(
- HttpException::class.java,
- result.exceptionOrNull()?.javaClass
- )
+ assertFalse(response.isSuccessful)
}
@Test
- fun unSaveEmoji_success_returnsSuccessResultUnit() {
+ fun unSaveEmoji_success_returnsSuccessResponseUnit() {
// given
val sampleEmojiId = "1234"
coEvery {
emojiApi.unSaveEmoji(any())
} returns Response.success(Unit)
// when
- val result = runBlocking { emojiRepositoryImpl.unSaveEmoji(sampleEmojiId) }
+ val response = runBlocking { emojiRepositoryImpl.unSaveEmoji(sampleEmojiId) }
// then
coVerify(exactly = 1) { emojiApi.unSaveEmoji(sampleEmojiId) }
- assert(result.isSuccess)
+ assertTrue(response.isSuccessful)
}
@Test
@@ -284,29 +245,7 @@ class EmojiRepositoryImplTest {
val result = runBlocking { emojiRepositoryImpl.unSaveEmoji(sampleEmojiId) }
// then
coVerify(exactly = 1) { emojiApi.unSaveEmoji(sampleEmojiId) }
- assertTrue(result.isFailure)
- assertEquals(
- "Failed to unsave Emoji (Id: $sampleEmojiId), 400",
- result.exceptionOrNull()?.message
- )
- }
-
- @Test
- fun unSaveEmoji_exception_returnsFailureResponseUnit() {
- // given
- val sampleEmojiId = "1234"
- coEvery {
- emojiApi.unSaveEmoji(any())
- } throws HttpException(mockk(relaxed=true))
- // when
- val result = runBlocking { emojiRepositoryImpl.unSaveEmoji(sampleEmojiId) }
- // then
- coVerify(exactly = 1) { emojiApi.unSaveEmoji(sampleEmojiId) }
- assertTrue(result.isFailure)
- assertEquals(
- HttpException::class.java,
- result.exceptionOrNull()?.javaClass
- )
+ assertFalse(result.isSuccessful)
}
// @Test
diff --git a/android/app/src/test/java/com/goliath/emojihub/repositories/remote/UserRepositoryImplTest.kt b/android/app/src/test/java/com/goliath/emojihub/repositories/remote/UserRepositoryImplTest.kt
index 877c979f..f09265aa 100644
--- a/android/app/src/test/java/com/goliath/emojihub/repositories/remote/UserRepositoryImplTest.kt
+++ b/android/app/src/test/java/com/goliath/emojihub/repositories/remote/UserRepositoryImplTest.kt
@@ -6,6 +6,7 @@ import com.goliath.emojihub.mockLogClass
import com.goliath.emojihub.models.LoginUserDto
import com.goliath.emojihub.models.RegisterUserDto
import com.goliath.emojihub.models.responses.LoginResponseDto
+import com.goliath.emojihub.sampleUserDetailsDto
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.mockk
@@ -36,6 +37,19 @@ class UserRepositoryImplTest {
fun fetchUser() {
}
+ @Test
+ fun fetchMyInfo_withValidToken_returnsResponseSuccessWithUserDetailsDto() {
+ // given
+ coEvery {
+ userApi.fetchMyInfo(any())
+ } returns Response.success(sampleUserDetailsDto)
+ // when
+ val response = runBlocking { userRepositoryImpl.fetchMyInfo("sampleToken") }
+ // then
+ coVerify(exactly = 1) { userApi.fetchMyInfo("sampleToken") }
+ assertEquals(sampleUserDetailsDto, response.body())
+ }
+
@Test
fun registerUser_withValidRegisterUserDto_returnsResponseSuccessWithAccessToken() {
// given
@@ -114,4 +128,30 @@ class UserRepositoryImplTest {
assertFalse(response.isSuccessful)
assertEquals(401, response.code())
}
+
+ @Test
+ fun logout_unit_returnsResponseSuccess() {
+ // given
+ coEvery {
+ userApi.logout()
+ } returns Response.success(Unit)
+ // when
+ val response = runBlocking { userRepositoryImpl.logout() }
+ // then
+ coVerify(exactly = 1) { userApi.logout() }
+ assertTrue(response.isSuccessful)
+ }
+
+ @Test
+ fun signOut_withValidToken_returnsResponseSuccess() {
+ // given
+ coEvery {
+ userApi.signOut(any())
+ } returns Response.success(Unit)
+ // when
+ val response = runBlocking { userRepositoryImpl.signOut("sampleToken") }
+ // then
+ coVerify(exactly = 1) { userApi.signOut("sampleToken") }
+ assertTrue(response.isSuccessful)
+ }
}
\ No newline at end of file
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 0cc91866..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
@@ -3,7 +3,7 @@ package com.goliath.emojihub.usecases
import androidx.paging.PagingData
import androidx.paging.map
import androidx.paging.testing.asSnapshot
-import com.goliath.emojihub.createDeterministicDummyEmojiDtoList
+import com.goliath.emojihub.createDeterministicTrendingEmojiDtoList
import com.goliath.emojihub.data_sources.ApiErrorController
import com.goliath.emojihub.mockLogClass
import com.goliath.emojihub.models.CreatedEmoji
@@ -24,6 +24,7 @@ import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
+import retrofit2.Response
import java.io.File
@RunWith(JUnit4::class)
@@ -72,15 +73,15 @@ class EmojiUseCaseImplTest {
@Test
fun fetchEmojiList_returnsFlowOfEmojiPagingData() {
// given
- val sampleEmojiPagingDataFlow = createDeterministicDummyEmojiDtoList(5)
+ val sampleEmojiPagingDataFlow = createDeterministicTrendingEmojiDtoList(5)
val sampleAnswer = sampleEmojiPagingDataFlow.map { it.map { dto -> Emoji(dto) } }
coEvery {
- emojiRepository.fetchEmojiList()
+ emojiRepository.fetchEmojiList(1)
} returns sampleEmojiPagingDataFlow
// when
- val fetchedEmojiPagingDataFlow = runBlocking { emojiUseCaseImpl.fetchEmojiList() }
+ val fetchedEmojiPagingDataFlow = runBlocking { emojiUseCaseImpl.fetchEmojiList(1) }
// then
- coVerify(exactly = 1) { emojiRepository.fetchEmojiList() }
+ coVerify(exactly = 1) { emojiRepository.fetchEmojiList(1) }
runBlocking {
val sampleAnswerAsSnapshot = sampleAnswer.asSnapshot()
val fetchedEmojiPagingDataFlowAsSnapshot = fetchedEmojiPagingDataFlow.asSnapshot()
@@ -96,7 +97,7 @@ class EmojiUseCaseImplTest {
@Test
fun fetchMyCreatedEmojiList_returnsFlowOfEmojiPagingData() {
// given
- val sampleEmojiPagingDataFlow = createDeterministicDummyEmojiDtoList(5)
+ val sampleEmojiPagingDataFlow = createDeterministicTrendingEmojiDtoList(5)
val sampleAnswer = sampleEmojiPagingDataFlow.map { it.map { dto -> Emoji(dto) } }
coEvery {
emojiRepository.fetchMyCreatedEmojiList()
@@ -120,7 +121,7 @@ class EmojiUseCaseImplTest {
@Test
fun fetchMySavedEmojiList_returnsFlowOfEmojiPagingData() {
// given
- val sampleEmojiPagingDataFlow = createDeterministicDummyEmojiDtoList(5)
+ val sampleEmojiPagingDataFlow = createDeterministicTrendingEmojiDtoList(5)
val sampleAnswer = sampleEmojiPagingDataFlow.map { it.map { dto -> Emoji(dto) } }
coEvery {
emojiRepository.fetchMySavedEmojiList()
@@ -153,9 +154,7 @@ class EmojiUseCaseImplTest {
x3dRepository.createEmoji(videoUri, topK)
} returns sampleTop3CreatedEmojiList
// when
- val createdEmojiList = runBlocking {
- emojiUseCaseImpl.createEmoji(videoUri, topK)
- }
+ val createdEmojiList = runBlocking { emojiUseCaseImpl.createEmoji(videoUri, topK) }
// then
coVerify(exactly = 1) { x3dRepository.createEmoji(videoUri, topK) }
assertEquals(sampleTop3CreatedEmojiList, createdEmojiList)
@@ -170,9 +169,7 @@ class EmojiUseCaseImplTest {
x3dRepository.createEmoji(videoUri, topK)
} returns emptyList()
// when
- val createdEmojiList = runBlocking {
- emojiUseCaseImpl.createEmoji(videoUri, topK)
- }
+ val createdEmojiList = runBlocking { emojiUseCaseImpl.createEmoji(videoUri, topK) }
// then
coVerify(exactly = 1) { x3dRepository.createEmoji(videoUri, topK) }
assertEquals(emptyList(), createdEmojiList)
@@ -187,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)
@@ -208,14 +205,12 @@ class EmojiUseCaseImplTest {
val sampleId = "sampleId"
coEvery {
emojiRepository.saveEmoji(sampleId)
- } returns Result.success(Unit)
+ } returns Response.success(Unit)
// when
- val result = runBlocking {
- emojiUseCaseImpl.saveEmoji(sampleId)
- }
+ val isSuccess = runBlocking { emojiUseCaseImpl.saveEmoji(sampleId) }
// then
coVerify(exactly = 1) { emojiRepository.saveEmoji(sampleId) }
- assertTrue(result.isSuccess)
+ assertTrue(isSuccess)
}
@Test
@@ -224,14 +219,26 @@ class EmojiUseCaseImplTest {
val sampleId = "sampleId"
coEvery {
emojiRepository.saveEmoji(sampleId)
- } returns Result.failure(Exception("Failed to save Emoji (Id: $sampleId), 404"))
+ } returns Response.error(404, mockk(relaxed = true))
// when
- val result = runBlocking {
- emojiUseCaseImpl.saveEmoji(sampleId)
- }
+ val isSuccess = runBlocking { emojiUseCaseImpl.saveEmoji(sampleId) }
+ // then
+ coVerify(exactly = 1) { emojiRepository.saveEmoji(sampleId) }
+ assertFalse(isSuccess)
+ }
+
+ @Test
+ fun saveEmoji_exception_returnsFalse() {
+ // given
+ val sampleId = "sampleId"
+ coEvery {
+ emojiRepository.saveEmoji(sampleId)
+ } throws Exception("Failed to save Emoji (Id: $sampleId), 404")
+ // when
+ val isSuccess = runBlocking { emojiUseCaseImpl.saveEmoji(sampleId) }
// then
coVerify(exactly = 1) { emojiRepository.saveEmoji(sampleId) }
- assertTrue(result.isFailure)
+ assertFalse(isSuccess)
}
@Test
@@ -240,14 +247,12 @@ class EmojiUseCaseImplTest {
val sampleId = "sampleId"
coEvery {
emojiRepository.unSaveEmoji(sampleId)
- } returns Result.success(Unit)
+ } returns Response.success(Unit)
// when
- val result = runBlocking {
- emojiUseCaseImpl.unSaveEmoji(sampleId)
- }
+ val isSuccess = runBlocking { emojiUseCaseImpl.unSaveEmoji(sampleId) }
// then
coVerify(exactly = 1) { emojiRepository.unSaveEmoji(sampleId) }
- assertTrue(result.isSuccess)
+ assertTrue(isSuccess)
}
@Test
@@ -256,13 +261,25 @@ class EmojiUseCaseImplTest {
val sampleId = "sampleId"
coEvery {
emojiRepository.unSaveEmoji(sampleId)
- } returns Result.failure(Exception("Failed to unsave Emoji (Id: $sampleId), 404"))
+ } returns Response.error(404, mockk(relaxed = true))
// when
- val result = runBlocking {
- emojiUseCaseImpl.unSaveEmoji(sampleId)
- }
+ val isSuccess = runBlocking { emojiUseCaseImpl.unSaveEmoji(sampleId) }
+ // then
+ coVerify(exactly = 1) { emojiRepository.unSaveEmoji(sampleId) }
+ assertFalse(isSuccess)
+ }
+
+ @Test
+ fun unSaveEmoji_exception_returnsFalse() {
+ // given
+ val sampleId = "sampleId"
+ coEvery {
+ emojiRepository.unSaveEmoji(sampleId)
+ } throws Exception("Failed to unSave Emoji (Id: $sampleId), 404")
+ // when
+ val isSuccess = runBlocking { emojiUseCaseImpl.unSaveEmoji(sampleId) }
// then
coVerify(exactly = 1) { emojiRepository.unSaveEmoji(sampleId) }
- assertTrue(result.isFailure)
+ assertFalse(isSuccess)
}
}
\ No newline at end of file
diff --git a/android/app/src/test/java/com/goliath/emojihub/usecases/UserUseCaseImplTest.kt b/android/app/src/test/java/com/goliath/emojihub/usecases/UserUseCaseImplTest.kt
index d558ea77..376b7106 100644
--- a/android/app/src/test/java/com/goliath/emojihub/usecases/UserUseCaseImplTest.kt
+++ b/android/app/src/test/java/com/goliath/emojihub/usecases/UserUseCaseImplTest.kt
@@ -6,8 +6,10 @@ import com.goliath.emojihub.data_sources.CustomError
import com.goliath.emojihub.data_sources.LocalStorage
import com.goliath.emojihub.mockLogClass
import com.goliath.emojihub.models.RegisterUserDto
+import com.goliath.emojihub.models.UserDetails
import com.goliath.emojihub.models.responses.LoginResponseDto
import com.goliath.emojihub.repositories.remote.UserRepository
+import com.goliath.emojihub.sampleUserDetailsDto
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.every
@@ -37,14 +39,14 @@ class UserUseCaseImplTest {
// ! Fake SharedPreferences for testing
class FakeSharedLocalStorage : LocalStorage {
- private val fakePreference = mutableMapOf()
+ private val fakePreference = mutableMapOf()
override var accessToken: String?
get() = fakePreference.getOrDefault("accessToken", "")
- set(value) = fakePreference.set("accessToken", value!!)
+ set(value) = fakePreference.set("accessToken", value)
override var currentUser: String?
get() = fakePreference.getOrDefault("currentUser", "")
- set(value) = fakePreference.set("currentUser", value!!)
+ set(value) = fakePreference.set("currentUser", value)
}
@Before
@@ -55,6 +57,39 @@ class UserUseCaseImplTest {
userUseCaseImpl = UserUseCaseImpl(userRepository, apiErrorController)
}
+ @Test
+ fun fetchMyInfo_success_updateUserDetailsState() {
+ // given
+ EmojiHubApplication.preferences.accessToken = sampleAccessToken
+ coEvery {
+ userRepository.fetchMyInfo(any())
+ } returns Response.success(sampleUserDetailsDto)
+ // when
+ runBlocking { userUseCaseImpl.fetchMyInfo() }
+ // then
+ coVerify(exactly = 1) { userRepository.fetchMyInfo(any()) }
+ assertEquals(
+ UserDetails(sampleUserDetailsDto),
+ userUseCaseImpl.userDetailsState.value
+ )
+ }
+
+ @Test
+ fun fetchMyInfo_failure_updateErrorState() {
+ // given
+ EmojiHubApplication.preferences.accessToken = sampleAccessToken
+ coEvery {
+ userRepository.fetchMyInfo(any())
+ } returns Response.error(CustomError.INTERNAL_SERVER_ERROR.statusCode, mockk(relaxed=true))
+ // when
+ runBlocking { userUseCaseImpl.fetchMyInfo() }
+ // then
+ coVerify(exactly = 1) { userRepository.fetchMyInfo(any()) }
+ verify(exactly = 1) {
+ apiErrorController.setErrorState(CustomError.INTERNAL_SERVER_ERROR.statusCode)
+ }
+ }
+
@Test
fun registerUser_withValidUserInfo_returnsTrue() {
// given
@@ -139,13 +174,73 @@ class UserUseCaseImplTest {
verify(exactly = 1) { apiErrorController.setErrorState(401) }
}
- // @Test
- // TODO: Not yet implemented
- fun logout() {
+ @Test
+ fun logout_success_updateAccessTokenAndUserStateToNull() {
+ // given
+ coEvery {
+ userRepository.logout()
+ } returns Response.success(Unit)
+ // when
+ runBlocking { userUseCaseImpl.logout() }
+ // then
+ coVerify(exactly = 1) { userRepository.logout() }
+ assertEquals(
+ null,
+ userUseCaseImpl.accessTokenState.value
+ )
+ assertEquals(
+ null,
+ userUseCaseImpl.userState.value
+ )
+ }
+
+ @Test
+ fun logout_failure_updateErrorState() {
+ // given
+ coEvery {
+ userRepository.logout()
+ } returns Response.error(CustomError.INTERNAL_SERVER_ERROR.statusCode, mockk(relaxed=true))
+ // when
+ runBlocking { userUseCaseImpl.logout() }
+ // then
+ coVerify(exactly = 1) { userRepository.logout() }
+ verify(exactly = 1) {
+ apiErrorController.setErrorState(CustomError.INTERNAL_SERVER_ERROR.statusCode)
+ }
+ }
+
+ @Test
+ fun signOut_success_updateAccessTokenAndUserStateToNull() {
+ // given
+ coEvery {
+ userRepository.signOut(any())
+ } returns Response.success(Unit)
+ // when
+ runBlocking { userUseCaseImpl.signOut() }
+ // then
+ coVerify(exactly = 1) { userRepository.signOut(any()) }
+ assertEquals(
+ null,
+ userUseCaseImpl.accessTokenState.value
+ )
+ assertEquals(
+ null,
+ userUseCaseImpl.userState.value
+ )
}
- // @Test
- // TODO: Not yet implemented
- fun signOut() {
+ @Test
+ fun signOut_failure_updateErrorState() {
+ // given
+ coEvery {
+ userRepository.signOut(any())
+ } returns Response.error(CustomError.INTERNAL_SERVER_ERROR.statusCode, mockk(relaxed=true))
+ // when
+ runBlocking { userUseCaseImpl.signOut() }
+ // then
+ coVerify(exactly = 1) { userRepository.signOut(any()) }
+ verify(exactly = 1) {
+ apiErrorController.setErrorState(CustomError.INTERNAL_SERVER_ERROR.statusCode)
+ }
}
}
\ No newline at end of file
diff --git a/android/app/src/test/java/com/goliath/emojihub/utils.kt b/android/app/src/test/java/com/goliath/emojihub/utils.kt
index 77048eb1..d579fa86 100644
--- a/android/app/src/test/java/com/goliath/emojihub/utils.kt
+++ b/android/app/src/test/java/com/goliath/emojihub/utils.kt
@@ -7,6 +7,8 @@ import com.goliath.emojihub.models.Emoji
import com.goliath.emojihub.models.EmojiDto
import com.goliath.emojihub.models.Post
import com.goliath.emojihub.models.PostDto
+import com.goliath.emojihub.models.ReactionWithEmojiUnicode
+import com.goliath.emojihub.models.UserDetailsDto
import com.goliath.emojihub.models.X3dInferenceResult
import io.mockk.every
import io.mockk.mockkStatic
@@ -22,18 +24,28 @@ fun mockLogClass() {
every { Log.e(any(), any()) } returns 0
}
+// USER TESTING UTILS
+val sampleUserDetailsDto = UserDetailsDto(
+ email = "sampleEmail",
+ name = "sampleName",
+ password = "samplePassword",
+ savedEmojiList = listOf("a", "b", "c"),
+ createdEmojiList = listOf("d", "e", "f"),
+ createdPostList = listOf("g", "h", "i"),
+)
+
// EMOJI TESTING UTILS
val dummyUsernames = listOf("channn", "doggydog", "meow_0w0", "mpunchmm", "kick_back")
val dummyUnicodes = listOf("U+1F44D", "U+1F600", "U+1F970", "U+1F60E", "U+1F621", "U+1F63A", "U+1F496", "U+1F415")
const val dummyMaxSavedCounts = 2000
-fun createDeterministicDummyEmojiDtoList(listSize : Int): Flow> {
+fun createDeterministicTrendingEmojiDtoList(listSize : Int): Flow> {
val dummyEmojiList = mutableListOf()
for (i in 0 until listSize) {
dummyEmojiList.add(
EmojiDto(
createdBy = dummyUsernames[i % dummyUsernames.size],
- createdAt = "2023.09.16",
- savedCount = dummyMaxSavedCounts % (i + 1),
+ createdAt = "2023."+i%12+".16",
+ savedCount = dummyMaxSavedCounts - i*10,
videoLink = "",
thumbnailLink = "",
unicode = dummyUnicodes[i % dummyUnicodes.size],
@@ -44,8 +56,8 @@ fun createDeterministicDummyEmojiDtoList(listSize : Int): Flow> {
- return createDeterministicDummyEmojiDtoList(listSize).map { it.map { dto -> Emoji(dto) } }
+fun createDeterministicTrendingEmojiList(listSize: Int): Flow> {
+ return createDeterministicTrendingEmojiDtoList(listSize).map { it.map { dto -> Emoji(dto) } }
}
// POST TESTING UTILS
@@ -57,7 +69,10 @@ val samplePostDto = PostDto(
"지갑이 하수구 구멍으로 빠지려는 찰나, 발로 굴러가는 지갑을 막아서 다행히 참사는 막을 수 있었다. " +
"지갑 주인분께서 감사하다고 카페 드림에서 커피도 한 잔 사주셨다.",
modifiedAt = "2023.10.23",
- reaction = listOf("good", "check", "good")
+ reaction = listOf(
+ ReactionWithEmojiUnicode("3456", "U+1F44D"),
+ ReactionWithEmojiUnicode("5678", "U+1F44D")
+ )
)
fun createDeterministicDummyPostDtoList(listSize : Int): Flow> {
val dummyPostList = mutableListOf()
@@ -71,7 +86,10 @@ fun createDeterministicDummyPostDtoList(listSize : Int): Flow