Skip to content

Commit

Permalink
[feat/#124] 대화 주제 사용성 개선
Browse files Browse the repository at this point in the history
- Activity를 Fragment로 쪼개고 하나의 뷰모델을 사용해 data를 공유하도록 함.
- 누적 흔들기 3초, 최소 2개 이상 데이터 수신 시에만 TopicFragment 노출 설정
- 사용자가 천천히 흔들면 그만큼 data를 미리 받아올 수 있는 여유가 생겨서 좋고, 빠르게 흔들어도 data가 2개는 먼저 들어가있으니까 기다릴 일도 없어져서 좋음
  • Loading branch information
unam98 committed Feb 1, 2024
1 parent 70a8fd8 commit 6c84779
Show file tree
Hide file tree
Showing 18 changed files with 379 additions and 212 deletions.
13 changes: 6 additions & 7 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"
<uses-permission
android:name="android.permission.POST_NOTIFICATIONS"
android:minSdkVersion="33" />

<application
Expand All @@ -26,6 +27,9 @@
android:theme="@style/Theme.TeumTeum"
android:usesCleartextTraffic="true"
tools:targetApi="tiramisu">
<activity
android:name=".presentation.shaketopic.ShakeTopicActivity"
android:exported="false" />
<service
android:name=".config.TeumMessagingService"
android:enabled="true"
Expand All @@ -34,15 +38,10 @@
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>

<activity
android:name=".presentation.familiar.FamiliarDialogActivity"
android:exported="false" />
<activity
android:name=".presentation.familiar.topic.TopicActivity"
android:exported="false" />
<activity
android:name=".presentation.familiar.shake.ShakeActivity"
android:exported="false" />
<activity
android:name=".presentation.MainActivity"
android:exported="true" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,13 @@ package com.teumteum.teumteum.presentation.familiar
import android.content.Intent
import android.os.Bundle
import android.view.View
import androidx.activity.viewModels
import com.teumteum.base.BindingActivity
import com.teumteum.domain.entity.Friend
import com.teumteum.teumteum.R
import com.teumteum.teumteum.databinding.ActivityFamiliarDialogBinding
import com.teumteum.teumteum.presentation.MainActivity
import com.teumteum.teumteum.presentation.familiar.introduce.IntroduceActivity
import com.teumteum.teumteum.presentation.familiar.introduce.IntroduceActivity.Companion.EXTRA_FRIENDS
import com.teumteum.teumteum.presentation.familiar.neighbor.NeighborViewModel
import com.teumteum.teumteum.presentation.familiar.shake.ShakeActivity
import com.teumteum.teumteum.presentation.shaketopic.ShakeTopicActivity
import dagger.hilt.android.AndroidEntryPoint
import java.io.Serializable

Expand Down Expand Up @@ -79,7 +76,7 @@ class FamiliarDialogActivity :
private fun startActivity() {
when (source) {
SOURCE_INTRODUCE -> {
val intent = Intent(this, ShakeActivity::class.java).apply {
val intent = Intent(this, ShakeTopicActivity::class.java).apply {
putExtra(EXTRA_FRIENDS, friends as Serializable)
}
startActivity(intent)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import com.teumteum.teumteum.presentation.familiar.FamiliarDialogActivity
import com.teumteum.teumteum.presentation.familiar.FamiliarDialogActivity.Companion.EXTRA_SOURCE
import com.teumteum.teumteum.presentation.familiar.FamiliarDialogActivity.Companion.SOURCE_INTRODUCE
import com.teumteum.teumteum.presentation.familiar.neighbor.NeighborActivity.Companion.EXTRA_NEIGHBORS_IDS
import com.teumteum.teumteum.presentation.familiar.shake.ShakeActivity
import dagger.hilt.android.AndroidEntryPoint
import java.io.Serializable

Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package com.teumteum.teumteum.presentation.shaketopic

import android.os.Bundle
import androidx.activity.viewModels
import androidx.fragment.app.commit
import com.teumteum.base.BindingActivity
import com.teumteum.domain.entity.Friend
import com.teumteum.teumteum.R
import com.teumteum.teumteum.databinding.ActivityShakeTopicBinding
import com.teumteum.teumteum.presentation.familiar.introduce.IntroduceActivity.Companion.EXTRA_FRIENDS
import com.teumteum.teumteum.presentation.shaketopic.shake.ShakeFragment
import com.teumteum.teumteum.presentation.shaketopic.topic.TopicFragment
import dagger.hilt.android.AndroidEntryPoint
import timber.log.Timber

@AndroidEntryPoint
class ShakeTopicActivity :
BindingActivity<ActivityShakeTopicBinding>(R.layout.activity_shake_topic) {

private val viewModel by viewModels<ShakeTopicViewModel>()
private var isShakeCompleted = false
private var isApiDataReceived = false

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

initView()
passDataToViewModel()
setUpObserver()
}

private fun initView() {
val initialFragment = ShakeFragment()
supportFragmentManager.commit {
add(R.id.fl_main, initialFragment)
}
}

private fun setUpObserver() {
viewModel.topics.observe(this) { topics ->
if (topics.size >= 2 && !isApiDataReceived) {
onApiDataReceived()
}
}
}
fun onShakeCompleted() {
Timber.d("흔들기 3초 완료")
isShakeCompleted = true
checkAndShowTopicFragment()
}

fun onApiDataReceived() {
Timber.d("데이터 최소 2개 수신 완료")
isApiDataReceived = true
checkAndShowTopicFragment()
}

private fun checkAndShowTopicFragment() {
Timber.d("조건 2개 충족 완료 페이지 이동 실행")
if (isShakeCompleted && isApiDataReceived) {
showTopicFragment()
}
}

private fun showTopicFragment() {
val topicFragment = TopicFragment()
supportFragmentManager.commit {
replace(R.id.fl_main, topicFragment)
addToBackStack(null)
}
}

private fun passDataToViewModel() {
val friends = intent.getSerializableExtra(EXTRA_FRIENDS) as? List<Friend>
viewModel.setFriendsData(friends ?: listOf())
}

companion object {}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package com.teumteum.teumteum.presentation.familiar.topic
package com.teumteum.teumteum.presentation.shaketopic

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.teumteum.domain.entity.Friend
import com.teumteum.domain.entity.TopicResponse
import com.teumteum.domain.repository.TopicRepository
import com.teumteum.teumteum.util.custom.uistate.UiState
Expand All @@ -13,42 +14,57 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.joinAll
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import timber.log.Timber
import java.util.Collections
import javax.inject.Inject
import kotlin.random.Random

@HiltViewModel
class TopicViewModel @Inject constructor(
class ShakeTopicViewModel @Inject constructor(
private val topicRepository: TopicRepository
) : ViewModel() {

private var _topicState = MutableLiveData<UiState>(UiState.Empty)
val topicState: LiveData<UiState>
get() = _topicState
private val _friends = MutableLiveData<List<Friend>>()
val friends: LiveData<List<Friend>>
get() = _friends

fun setFriendsData(friends: List<Friend>) {
_friends.value = friends
}

private val _topics = MutableLiveData<List<TopicResponse>>()
val topics: LiveData<List<TopicResponse>>
get() = _topics

private var isFetching = false

fun getTopics(userIds: List<String>) {
viewModelScope.launch {
_topicState.value = UiState.Loading
if (isFetching || (_topics.value?.size ?: 0) >= 5) {
// 이미 API 호출 중이거나 필요한 데이터를 모두 받았다면 요청하지 않습니다.
return
}
isFetching = true

viewModelScope.launch {
val responses = mutableListOf<Job>()

for (i in 1..5) {
val type = if (i % 2 == 0) "balance" else "story"
responses.add(
launch {
retryFetch { topicRepository.getTopics(userIds, type) }
}
)
val type = if (i % 2 == 0) "story" else "balance"
if ((_topics.value?.size ?: 0) < 5) { // 이미 5개의 데이터를 받았다면 추가 호출을 하지 않습니다.
responses.add(
launch {
retryFetch { topicRepository.getTopics(userIds, type) }
}
)
}
}

responses.joinAll()
checkDataAndRetryIfNeeded(userIds)
isFetching = false
// 'checkDataAndRetryIfNeeded' 함수 호출 부분을 제거합니다.
}
}

private suspend fun retryFetch(fetch: suspend () -> Result<TopicResponse>) {
var retryCount = 0
var success = false
Expand All @@ -58,6 +74,11 @@ class TopicViewModel @Inject constructor(
if (response != null) {
withContext(Dispatchers.Main) {
updateViewPagerWithApiData(response)
// 받은 데이터가 5개면 더 이상 재시도하지 않습니다.
if (_topics.value?.size == 5) {
Timber.d("data 5개 다 받아옴")
return@withContext
}
}
success = true
} else {
Expand All @@ -66,17 +87,6 @@ class TopicViewModel @Inject constructor(
}
}


private suspend fun checkDataAndRetryIfNeeded(userIds: List<String>) {
val currentSize = _topics.value?.size ?: 0
val missingCount = 5 - currentSize

for (i in 1..missingCount) {
val type = if (Random.nextBoolean()) "balance" else "story"
retryFetch { topicRepository.getTopics(userIds, type) }
}
}

companion object {
const val MAX_RETRY = 3
}
Expand Down
Loading

0 comments on commit 6c84779

Please sign in to comment.