Skip to content

Commit

Permalink
Merge pull request #46 from Nexters/feature/fix-detail
Browse files Browse the repository at this point in the history
λ””ν…ŒμΌ μˆ˜μ •
  • Loading branch information
DwEnn authored Oct 18, 2023
2 parents 50ce6ca + 54d9881 commit 32707fb
Show file tree
Hide file tree
Showing 27 changed files with 301 additions and 251 deletions.
16 changes: 16 additions & 0 deletions app/src/main/assets/sample.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<!DOCTYPE html>
<html>
<head>
<title>Ready Story</title>
</head>
<script type="text/javascript">

var broswerInfo = navigator.userAgent;
alert(broswerInfo);
</script>
<body>

<h1 id="sample-title">Sample for the injected local js file.</h1>
<script type="text/javascript" src="sample.js"></script>
</body>
</html>
25 changes: 0 additions & 25 deletions app/src/main/java/com/keyme/app/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
package com.keyme.app

import android.animation.ObjectAnimator
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.view.View
import android.view.animation.AnticipateInterpolator
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.animation.doOnEnd
import androidx.core.content.ContextCompat
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
Expand Down Expand Up @@ -52,8 +48,6 @@ class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

setSplashScreen()

setContent {
KeymeApp()
}
Expand All @@ -67,25 +61,6 @@ class MainActivity : ComponentActivity() {
}
}

private fun setSplashScreen() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
splashScreen.setOnExitAnimationListener { splashScreenView ->
val slideUp = ObjectAnimator.ofFloat(
splashScreenView,
View.TRANSLATION_Y,
0f,
-splashScreenView.height.toFloat(),
)
slideUp.interpolator = AnticipateInterpolator()
slideUp.duration = 200L

slideUp.doOnEnd { splashScreenView.remove() }

slideUp.start()
}
}
}

private fun handleUiEvent() {
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ fun DailyKeymeTestRoute(
val dailyKeymeTestStatistic by dailyKeymeTestViewModel.dailyKeymeTestStatisticState.collectAsStateWithLifecycle()

val context = LocalContext.current

KeymeBackgroundAnim()
Box(
modifier = Modifier
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.zIndex
import coil.compose.AsyncImage
import coil.request.ImageRequest
import coil.size.Precision
import com.keyme.domain.entity.member.Member
import com.keyme.domain.entity.response.QuestionStatistic
import com.keyme.domain.entity.response.TestStatistic
Expand Down Expand Up @@ -128,7 +129,10 @@ private fun LazyListScope.statisticList(
dailyKeymeTestStatistic: TestStatistic,
onClickItem: (QuestionStatistic) -> Unit,
) {
items(dailyKeymeTestStatistic.questionsStatistics) {
items(
items = dailyKeymeTestStatistic.questionsStatistics,
key = { it.questionId },
) {
QuestionStatisticItem(
myCharacter = myCharacter,
statistic = it,
Expand Down Expand Up @@ -165,7 +169,11 @@ private fun QuestionStatisticItem(
) {
AsyncImage(
modifier = Modifier.size(20.dp),
model = ImageRequest.Builder(LocalContext.current).data(statistic.category.iconUrl).build(),
model = ImageRequest.Builder(LocalContext.current)
.data(statistic.category.iconUrl)
.memoryCacheKey(statistic.category.iconUrl)
.precision(Precision.EXACT)
.build(),
contentDescription = "",
contentScale = ContentScale.Crop,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,6 @@ import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
import javax.inject.Inject

Expand Down Expand Up @@ -47,16 +43,9 @@ class DailyKeymeTestViewModel @Inject constructor(
// apiCall(apiRequest = { getDailyKeymeTestUseCase() }) {
// NOTE: 데일리 문제λ₯Ό μ œκ³΅ν•˜λŠ” λŒ€μ‹  μ˜¨λ³΄λ”© 문제λ₯Ό 계속 κ³΅μœ ν•  수 있게 ν•˜λŠ” ν”Œλ‘œμš°λ‘œ μˆ˜μ • λ°©ν–₯ 생각쀑
apiCall(apiRequest = { getOnboardingKeymeTestUseCase() }) {
_dailyKeymeTestState.value = it
apiCall(apiRequest = { getKeymeTestStatisticUseCase(it.testId) }) { statistic ->
_dailyKeymeTestStatisticState.value = statistic
}
}

dailyKeymeTestState
.filter { it.testResultId != 0 }
.distinctUntilChanged()
.onEach {
apiCall(apiRequest = { getKeymeTestStatisticUseCase(it.testId) }) { statistic ->
_dailyKeymeTestStatisticState.value = statistic
}
}.launchIn(baseViewModelScope)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.SideEffect
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalView
import androidx.core.view.WindowCompat
Expand Down Expand Up @@ -46,7 +47,7 @@ fun KeymeTheme(
val systemUiController = rememberSystemUiController()
DisposableEffect(systemUiController) {
systemUiController.setSystemBarsColor(
color = keyme_black,
color = Color(0xff1c1c1c),
darkIcons = false,
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,27 +80,28 @@ class EditProfileViewModel @Inject constructor(
}

fun onNicknameChange(nickname: String) {
baseViewModelScope.launch {
verifyNicknameUseCase(nickname)
.onSuccess {
_editProfileUiState.value = _editProfileUiState.value.copy(
nickname = nickname,
isValidNickname = it.valid && verifyNickname(nickname),
verifyDescription = it.description,
)
}.onApiError { code, message ->
_editProfileUiState.value = _editProfileUiState.value.copy(isValidNickname = false)

baseViewModelScope.launch {
uiEventManager.onEvent(UiEvent.Toast(message))
}
}.onFailure {
_editProfileUiState.value = _editProfileUiState.value.copy(isValidNickname = false)

baseViewModelScope.launch {
uiEventManager.onEvent(UiEvent.Toast(it.message ?: ""))
if (verifyNickname(nickname)) {
baseViewModelScope.launch {
verifyNicknameUseCase(nickname)
.onSuccess {
_editProfileUiState.value = _editProfileUiState.value.copy(
nickname = nickname,
isValidNickname = it.valid && verifyNickname(nickname),
verifyDescription = "μ‚¬μš© κ°€λŠ₯ν•©λ‹ˆλ‹€",
)
}.onApiError { code, _ ->
val description = if (code == "201") "이미 μ‚¬μš© 쀑인 λ‹‰λ„€μž„μž…λ‹ˆλ‹€" else ""
_editProfileUiState.value = _editProfileUiState.value.copy(isValidNickname = false, verifyDescription = description)
}.onFailure {
_editProfileUiState.value = _editProfileUiState.value.copy(isValidNickname = false)

baseViewModelScope.launch {
uiEventManager.onEvent(UiEvent.Toast(it.message ?: ""))
}
}
}
}
} else {
_editProfileUiState.value = _editProfileUiState.value.copy(isValidNickname = false, verifyDescription = "")
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,15 +137,15 @@ fun KeymeTestResultItem(
.padding(top = 33.dp, start = 32.dp, end = 32.dp),
) {
KeymeText(
text = item.category.name,
text = item.keyword,
keymeTextType = KeymeTextType.BODY_4,
color = Color.White,
)

Spacer(modifier = Modifier.height(8.dp))

KeymeText(
text = "${myCharacter.nickname}λ‹˜μ˜ ${item.keyword} μ •λ„λŠ”?",
text = myCharacter.nickname + item.title,
keymeTextType = KeymeTextType.HEADING_1,
color = Color.White,
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,76 +1,112 @@
package com.keyme.presentation.myprofile

import com.keyme.domain.entity.member.Member
import com.keyme.domain.entity.onApiError
import com.keyme.domain.entity.onFailure
import com.keyme.domain.entity.onSuccess
import com.keyme.domain.entity.response.MemberStatistics
import com.keyme.domain.usecase.GetMyCharacterUseCase
import com.keyme.domain.usecase.GetMyStatisticsUseCase
import com.keyme.domain.usecase.GetOnboardingKeymeTestUseCase
import com.keyme.presentation.BaseViewModel
import com.keyme.presentation.UiEvent
import com.keyme.presentation.utils.KeymeLinkUtil
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine
import javax.inject.Inject
import kotlin.coroutines.resume

@HiltViewModel
class MyProfileViewModel @Inject constructor(
private val getMyCharacterUseCase: GetMyCharacterUseCase,
private val getMyStatisticsUseCase: GetMyStatisticsUseCase,
private val getOnboardingKeymeTestUseCase: GetOnboardingKeymeTestUseCase,
) : BaseViewModel() {
private val _mySimilarStatisticsState = MutableStateFlow(MemberStatistics())
val mySimilarStatisticsState = _mySimilarStatisticsState.asStateFlow()

private val _mySimilarStatisticsState = MutableStateFlow(MemberStatistics())
private val _myDifferentStatisticsState = MutableStateFlow(MemberStatistics())
val myDifferentStatisticsState = _myDifferentStatisticsState.asStateFlow()

private val _myProfileUiState = MutableStateFlow(MyProfileUiState())
val myProfileUiState = _myProfileUiState.asStateFlow()

val myCharacterState = getMyCharacterUseCase().stateIn(
started = SharingStarted.WhileSubscribed(5000L),
initialValue = Member.EMPTY,
val myProfileUiState = combine(
getMyCharacterUseCase(),
_mySimilarStatisticsState,
_myDifferentStatisticsState,
) { myCharacter, similarStatistics, differentStatistics ->
if (statisticsEmpty(similarStatistics, differentStatistics)) {
MyProfileUiState.EmptyStatistics(
myCharacter = myCharacter,
testLink = KeymeLinkUtil.getTestLink(getTestForShare().testId),
)
} else {
MyProfileUiState.Statistics(
myCharacter = myCharacter,
similar = _mySimilarStatisticsState.value,
different = _myDifferentStatisticsState.value,
)
}
}.stateIn(
started = SharingStarted.Eagerly,
initialValue = MyProfileUiState.EmptyStatistics(),
scope = baseViewModelScope,
)

init {
showToolTip()
loadMyStatistics()
}

private fun loadMyStatistics() {
apiCall(apiRequest = { getMyStatisticsUseCase.invoke(type = MemberStatistics.StatisticsType.SIMILAR) }) {
_mySimilarStatisticsState.value = it
}
apiCall(apiRequest = { getMyStatisticsUseCase.invoke(type = MemberStatistics.StatisticsType.DIFFERENT) }) {
_myDifferentStatisticsState.value = it
private suspend fun getTestForShare() = suspendCancellableCoroutine { continuation ->
apiCall(apiRequest = { getOnboardingKeymeTestUseCase() }) {
continuation.resume(it)
}
}

private var toolTipTimerJob: Job? = null

fun showToolTip() {
if (_myProfileUiState.value.showToolTip.not()) {
_myProfileUiState.value = _myProfileUiState.value.copy(showToolTip = true)

toolTipTimerJob = baseViewModelScope.launch {
delay(3000L)
dismissToolTip()
}
}
private fun statisticsEmpty(
similarStatistics: MemberStatistics,
differentStatistics: MemberStatistics,
): Boolean {
return similarStatistics.results.isEmpty() || differentStatistics.results.isEmpty()
}

fun dismissToolTip() {
_myProfileUiState.value = _myProfileUiState.value.copy(showToolTip = false)

private fun loadMyStatistics() {
baseViewModelScope.launch {
toolTipTimerJob?.cancelAndJoin()
toolTipTimerJob = null
getMyStatisticsUseCase(type = MemberStatistics.StatisticsType.SIMILAR)
.onSuccess {
_mySimilarStatisticsState.value = it
}.onApiError { code, message ->
_mySimilarStatisticsState.value = MemberStatistics()
}.onFailure {
baseViewModelScope.launch {
uiEventManager.onEvent(UiEvent.Toast(it.message ?: ""))
}
}

getMyStatisticsUseCase(type = MemberStatistics.StatisticsType.DIFFERENT)
.onSuccess {
_myDifferentStatisticsState.value = it
}.onApiError { code, message ->
_myDifferentStatisticsState.value = MemberStatistics()
}.onFailure {
baseViewModelScope.launch {
uiEventManager.onEvent(UiEvent.Toast(it.message ?: ""))
}
}
}
}
}

data class MyProfileUiState(val showToolTip: Boolean = false)
sealed class MyProfileUiState(open val myCharacter: Member) {
data class EmptyStatistics(
override val myCharacter: Member = Member.EMPTY,
val testLink: String = "",
) : MyProfileUiState(myCharacter)

data class Statistics(
override val myCharacter: Member = Member.EMPTY,
val similar: MemberStatistics,
val different: MemberStatistics,
) : MyProfileUiState(myCharacter)
}
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ private fun KeymeQuestionStatisticsInfo(
text = if (showMyScore) {
"λ‚˜μ˜ 점수"
} else {
questionStatistic.category.name
questionStatistic.keyword
},
keymeTextType = KeymeTextType.BODY_3_REGULAR,
color = Color.White,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ fun ColumnScope.KeymeQuestionSolvedScoreList(
KeymeQuestionScoreListContainer(
header = {
KeymeQuestionInfo(
title = "${myCharacter.nickname}λ‹˜μ˜ ${statistic.keyword} μ •λ„λŠ”?",
title = myCharacter.nickname + statistic.title,
solvedCount = solvedScorePagingItem.itemCount,
)

Expand Down
Loading

0 comments on commit 32707fb

Please sign in to comment.