Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Fix save, un-save state refresh error #108

Merged
merged 8 commits into from
Dec 6, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -15,7 +15,12 @@ interface UserApi {
@GET("user")
suspend fun fetchUserList(

): Response<Array<UserDtoList>>
): Response<Array<UserDetailsDto>>

@GET("user/me")
suspend fun fetchMyInfo(
@Header("Authorization") authToken: String
): Response<UserDetailsDto>

@POST("user/signup")
suspend fun registerUser(
Expand Down
46 changes: 41 additions & 5 deletions android/app/src/main/java/com/goliath/emojihub/models/User.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>? = dto.savedEmojiList
val createdEmojiList: List<String>? = dto.createdEmojiList
val createdPostList: List<String>? = 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,

Expand All @@ -23,11 +56,14 @@ data class UserDtoList(
@SerializedName("password")
val password: String,

@SerializedName("liked_emojis")
val likedEmojiList: String?,
@SerializedName("saved_emojis")
val savedEmojiList: List<String>?,

@SerializedName("created_emojis")
val createdEmojiList: String?
val createdEmojiList: List<String>?,

@SerializedName("created_posts")
val createdPostList: List<String>?
)

class RegisterUserDto(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ interface EmojiRepository {
suspend fun fetchMySavedEmojiList(): Flow<PagingData<EmojiDto>>
suspend fun getEmojiWithId(id: String): EmojiDto?
suspend fun uploadEmoji(videoFile: File, emojiDto: UploadEmojiDto): Boolean
suspend fun saveEmoji(id: String): Result<Unit>
suspend fun unSaveEmoji(id: String): Result<Unit>
suspend fun saveEmoji(id: String): Response<Unit>
suspend fun unSaveEmoji(id: String): Response<Unit>
suspend fun deleteEmoji(id: String): Response<Unit>
}

Expand Down Expand Up @@ -99,37 +99,12 @@ class EmojiRepositoryImpl @Inject constructor(
}
}

override suspend fun saveEmoji(id: String): Result<Unit> {
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<Unit> {
return emojiApi.saveEmoji(id)
}

override suspend fun unSaveEmoji(id: String): Result<Unit> {
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<Unit> {
return emojiApi.unSaveEmoji(id)
}

override suspend fun deleteEmoji(id: String): Response<Unit> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<UserDtoList>
fun fetchUser(id: String)
suspend fun fetchUserList(): Array<UserDetailsDto>
suspend fun fetchUser(id: String)
suspend fun fetchMyInfo(authToken: String): Response<UserDetailsDto>
suspend fun registerUser(dto: RegisterUserDto): Response<LoginResponseDto>
suspend fun login(dto: LoginUserDto): Response<LoginResponseDto>
suspend fun logout(): Response<Unit>
Expand All @@ -22,14 +23,18 @@ interface UserRepository {
class UserRepositoryImpl @Inject constructor(
private val userApi: UserApi
): UserRepository {
override suspend fun fetchUserList(): Array<UserDtoList> {
override suspend fun fetchUserList(): Array<UserDetailsDto> {
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<UserDetailsDto> {
return userApi.fetchMyInfo(authToken)
}

override suspend fun registerUser(dto: RegisterUserDto): Response<LoginResponseDto> {
return userApi.registerUser(dto)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ interface EmojiUseCase {
suspend fun fetchMySavedEmojiList(): Flow<PagingData<Emoji>>
suspend fun createEmoji(videoUri: Uri, topK: Int): List<CreatedEmoji>
suspend fun uploadEmoji(emojiUnicode: String, emojiLabel: String, videoFile: File): Boolean
suspend fun saveEmoji(id: String): Result<Unit>
suspend fun unSaveEmoji(id: String): Result<Unit>
suspend fun saveEmoji(id: String): Boolean
suspend fun unSaveEmoji(id: String): Boolean
}

@Singleton
Expand Down Expand Up @@ -86,11 +86,38 @@ class EmojiUseCaseImpl @Inject constructor(
return emojiRepository.uploadEmoji(videoFile, dto)
}

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

if (response.isSuccessful) {
Log.d("EmojiUseCase", "Successfully saved Emoji (Id: $id)")
true
} else {
Log.e("EmojiUseCase", "Failed to save Emoji (Id: $id), ${response.code()}")
false
}
} catch (e: Exception) {
Log.e("EmojiUseCase", "Unknown Exception at Save emoji: ${e.message}")
false
}
}

override suspend fun unSaveEmoji(id: String): Result<Unit> {
return emojiRepository.unSaveEmoji(id)
override suspend fun unSaveEmoji(id: String): Boolean {
return try {
val response = emojiRepository.unSaveEmoji(id)
Log.d("EmojiUseCase", "UnSaveEmoji Api response : ${response.code()}")
if (response.isSuccessful) {
Log.d("EmojiUseCase", "Successfully unsaved Emoji (Id: $id)")
true
} else {
Log.e("EmojiUseCase", "Failed to unsave Emoji (Id: $id), ${response.code()}")
false
}
} catch (e: Exception) {
Log.e("EmojiUseCase", "Unknown Exception at UnSave emoji: ${e.message}")
false
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import com.goliath.emojihub.data_sources.ApiErrorController
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
Expand All @@ -16,8 +17,10 @@ import javax.inject.Singleton
sealed interface UserUseCase {
val accessTokenState: StateFlow<String?>
val userState: StateFlow<User?>
val userDetailsState: StateFlow<UserDetails?>

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()
Expand All @@ -38,10 +41,28 @@ class UserUseCaseImpl @Inject constructor(
override val userState: StateFlow<User?>
get() = _userState

private val _userDetailsState: MutableStateFlow<UserDetails?> = MutableStateFlow(null)
override val userDetailsState: StateFlow<UserDetails?>
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

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

override suspend fun registerUser(email: String, name: String, password: String): Boolean {
val dto = RegisterUserDto(email, name, password)
val response = repository.registerUser(dto)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File
Expand All @@ -31,10 +30,11 @@ class EmojiViewModel @Inject constructor(
lateinit var currentEmoji: Emoji
var bottomSheetContent by mutableStateOf(BottomSheetContent.EMPTY)

private val _saveEmojiState = MutableStateFlow<Result<Unit>?>(null)
// 0: not saved, 1: saved, -1: not changed
private val _saveEmojiState = MutableStateFlow(-1)
val saveEmojiState = _saveEmojiState.asStateFlow()

private val _unSaveEmojiState = MutableStateFlow<Result<Unit>?>(null)
private val _unSaveEmojiState = MutableStateFlow(-1)
val unSaveEmojiState = _unSaveEmojiState.asStateFlow()

var sortByDate by mutableIntStateOf(0)
Expand Down Expand Up @@ -96,15 +96,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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ fun EmojiPage() {
}
}

// 앱이 처음 실행될 때, 유저 정보를 가져오기 위함
LaunchedEffect(userViewModel) {
userViewModel.fetchMyInfo()
}

LaunchedEffect(Unit) {
emojiViewModel.fetchEmojiList()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ fun LoginPage() {
coroutineScope.launch {
userViewModel.login(username.text, password.text)
}
userViewModel.fetchMyInfo()
},
modifier = Modifier
.padding(top = 24.dp)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,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()
Expand Down
Loading