feat(chat): 탭별 empty state를 표시한다
This commit is contained in:
@@ -6,6 +6,7 @@ import android.view.View
|
||||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.Toast
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import kr.co.vividnext.sodalive.R
|
||||
@@ -21,6 +22,7 @@ import kr.co.vividnext.sodalive.v2.main.chat.model.ChatRoomFilter
|
||||
import kr.co.vividnext.sodalive.v2.main.chat.model.ChatRoomListUiItem
|
||||
import kr.co.vividnext.sodalive.v2.main.chat.model.ChatRoomListUiState
|
||||
import kr.co.vividnext.sodalive.v2.main.chat.model.ChatRoomType
|
||||
import kr.co.vividnext.sodalive.v2.main.chat.model.emptyMessageResId
|
||||
import kr.co.vividnext.sodalive.v2.main.chat.ui.ChatRoomListAdapter
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
|
||||
@@ -44,22 +46,25 @@ class ChatMainFragment : BaseFragment<FragmentV2MainChatBinding>(
|
||||
private val viewModel: ChatMainViewModel by viewModel()
|
||||
private val chatRoomListAdapter = ChatRoomListAdapter { onChatRoomClick(it) }
|
||||
private lateinit var loadingDialog: LoadingDialog
|
||||
private var selectedFilter: ChatRoomFilter = ChatRoomFilter.ALL
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
loadingDialog = LoadingDialog(requireActivity(), layoutInflater)
|
||||
|
||||
setupTitleBar()
|
||||
val initialFilter = getInitialFilter()
|
||||
setupFilterTabs(initialFilter ?: ChatRoomFilter.ALL)
|
||||
val initialFilter = getInitialFilter() ?: ChatRoomFilter.ALL
|
||||
selectedFilter = initialFilter
|
||||
setupFilterTabs(initialFilter)
|
||||
setupChatRooms()
|
||||
bindViewModel()
|
||||
|
||||
binding.btnChatFloating.setOnClickListener { }
|
||||
viewModel.loadFirstPage(initialFilter ?: ChatRoomFilter.ALL)
|
||||
viewModel.loadFirstPage(initialFilter)
|
||||
}
|
||||
|
||||
fun selectFilter(filter: ChatRoomFilter) {
|
||||
selectedFilter = filter
|
||||
selectFilterTab(filter)
|
||||
viewModel.selectFilter(filter)
|
||||
}
|
||||
@@ -102,7 +107,9 @@ class ChatMainFragment : BaseFragment<FragmentV2MainChatBinding>(
|
||||
selectedIndex = initialFilter.tabIndex
|
||||
)
|
||||
binding.viewChatFilterTabs.root.setOnTabSelectedListener { index ->
|
||||
viewModel.selectFilter(ChatRoomFilter.fromTabIndex(index))
|
||||
val filter = ChatRoomFilter.fromTabIndex(index)
|
||||
selectedFilter = filter
|
||||
viewModel.selectFilter(filter)
|
||||
binding.rvChatRooms.scrollToPosition(0)
|
||||
}
|
||||
}
|
||||
@@ -130,14 +137,14 @@ class ChatMainFragment : BaseFragment<FragmentV2MainChatBinding>(
|
||||
viewModel.chatRoomStateLiveData.observe(viewLifecycleOwner) { state ->
|
||||
when (state) {
|
||||
is ChatRoomListUiState.Content -> {
|
||||
chatRoomListAdapter.submitItems(state.items)
|
||||
bindChatRooms(state.items, showEmpty = false)
|
||||
if (!state.isAppending) {
|
||||
binding.rvChatRooms.scrollToPosition(0)
|
||||
}
|
||||
}
|
||||
ChatRoomListUiState.Empty -> chatRoomListAdapter.submitItems(emptyList())
|
||||
is ChatRoomListUiState.Error -> chatRoomListAdapter.submitItems(emptyList())
|
||||
ChatRoomListUiState.Loading -> Unit
|
||||
ChatRoomListUiState.Empty -> bindChatRooms(emptyList(), showEmpty = true)
|
||||
is ChatRoomListUiState.Error -> bindChatRooms(emptyList(), showEmpty = false)
|
||||
ChatRoomListUiState.Loading -> binding.tvChatEmptyMessage.isVisible = false
|
||||
}
|
||||
}
|
||||
viewModel.isLoading.observe(viewLifecycleOwner) {
|
||||
@@ -155,6 +162,15 @@ class ChatMainFragment : BaseFragment<FragmentV2MainChatBinding>(
|
||||
}
|
||||
}
|
||||
|
||||
private fun bindChatRooms(items: List<ChatRoomListUiItem>, showEmpty: Boolean) {
|
||||
chatRoomListAdapter.submitItems(items)
|
||||
binding.rvChatRooms.isVisible = !showEmpty
|
||||
binding.tvChatEmptyMessage.isVisible = showEmpty
|
||||
if (showEmpty) {
|
||||
binding.tvChatEmptyMessage.setText(selectedFilter.emptyMessageResId())
|
||||
}
|
||||
}
|
||||
|
||||
private fun onChatRoomClick(item: ChatRoomListUiItem) {
|
||||
when (item.chatType) {
|
||||
ChatRoomType.AI -> startActivity(ChatRoomActivity.newIntent(requireContext(), item.roomId))
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
package kr.co.vividnext.sodalive.v2.main.chat.model
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import kr.co.vividnext.sodalive.R
|
||||
|
||||
enum class ChatRoomFilter(val apiValue: String, val tabIndex: Int) {
|
||||
ALL("ALL", 0),
|
||||
AI("AI", 1),
|
||||
@@ -13,3 +16,10 @@ enum class ChatRoomFilter(val apiValue: String, val tabIndex: Int) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@StringRes
|
||||
fun ChatRoomFilter.emptyMessageResId(): Int = when (this) {
|
||||
ChatRoomFilter.ALL -> R.string.screen_chat_empty_all
|
||||
ChatRoomFilter.AI -> R.string.screen_chat_empty_ai
|
||||
ChatRoomFilter.DM -> R.string.screen_chat_empty_dm
|
||||
}
|
||||
|
||||
@@ -37,6 +37,20 @@
|
||||
app:layout_constraintTop_toBottomOf="@id/view_chat_filter_tabs"
|
||||
tools:listitem="@layout/item_v2_chat_room" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_chat_empty_message"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:textColor="@color/color_777777"
|
||||
android:textSize="14sp"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/view_chat_filter_tabs"
|
||||
tools:text="아직 나눈 대화가 없어요.\n크리에이터와 이야기를 시작해 보세요." />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/btn_chat_floating"
|
||||
android:layout_width="@dimen/spacing_48"
|
||||
|
||||
@@ -52,12 +52,15 @@ class ChatMainFragmentLayoutTest {
|
||||
val tabBar = requireNotNull(root.findViewById<CapsuleTabBarView>(R.id.view_chat_filter_tabs))
|
||||
val recyclerView = requireNotNull(root.findViewById<RecyclerView>(R.id.rv_chat_rooms))
|
||||
val floatingButton = requireNotNull(root.findViewById<ImageView>(R.id.btn_chat_floating))
|
||||
val emptyMessage = requireNotNull(root.findViewById<TextView>(R.id.tv_chat_empty_message))
|
||||
|
||||
assertEquals(Color.BLACK, (root.background as ColorDrawable).color)
|
||||
assertSame(root, titleBar.parent)
|
||||
assertSame(root, tabBar.parent)
|
||||
assertSame(root, recyclerView.parent)
|
||||
assertSame(root, floatingButton.parent)
|
||||
assertSame(root, emptyMessage.parent)
|
||||
assertEquals(View.GONE, emptyMessage.visibility)
|
||||
assertFalse(root.containsClassName("com.google.android.material.bottomnavigation.BottomNavigationView"))
|
||||
assertFalse(root.containsViewIdContaining("unread"))
|
||||
}
|
||||
@@ -69,8 +72,10 @@ class ChatMainFragmentLayoutTest {
|
||||
val tabBar = requireNotNull(root.findViewById<CapsuleTabBarView>(R.id.view_chat_filter_tabs))
|
||||
val recyclerView = requireNotNull(root.findViewById<RecyclerView>(R.id.rv_chat_rooms))
|
||||
val floatingButton = requireNotNull(root.findViewById<ImageView>(R.id.btn_chat_floating))
|
||||
val emptyMessage = requireNotNull(root.findViewById<TextView>(R.id.tv_chat_empty_message))
|
||||
val tabParams = tabBar.layoutParams as ConstraintLayout.LayoutParams
|
||||
val listParams = recyclerView.layoutParams as ConstraintLayout.LayoutParams
|
||||
val emptyParams = emptyMessage.layoutParams as ConstraintLayout.LayoutParams
|
||||
val buttonParams = floatingButton.layoutParams as ConstraintLayout.LayoutParams
|
||||
|
||||
assertEquals(60.dpToPx(), titleBar.layoutParams.height)
|
||||
@@ -78,6 +83,8 @@ class ChatMainFragmentLayoutTest {
|
||||
assertEquals(R.id.view_chat_title_bar, tabParams.topToBottom)
|
||||
assertEquals(R.id.view_chat_filter_tabs, listParams.topToBottom)
|
||||
assertEquals(ConstraintLayout.LayoutParams.PARENT_ID, listParams.bottomToBottom)
|
||||
assertEquals(R.id.view_chat_filter_tabs, emptyParams.topToBottom)
|
||||
assertEquals(ConstraintLayout.LayoutParams.PARENT_ID, emptyParams.bottomToBottom)
|
||||
assertEquals(false, recyclerView.clipToPadding)
|
||||
assertTrue(recyclerView.paddingBottom >= 28.dpToPx())
|
||||
assertEquals(ConstraintLayout.LayoutParams.PARENT_ID, buttonParams.endToEnd)
|
||||
@@ -101,7 +108,8 @@ class ChatMainFragmentLayoutTest {
|
||||
assertTrue(source.contains("R.string.screen_chat_filter_all"))
|
||||
assertTrue(source.contains("R.string.screen_chat_filter_ai"))
|
||||
assertTrue(source.contains("R.string.screen_chat_filter_dm"))
|
||||
assertTrue(source.contains("viewModel.loadFirstPage(initialFilter ?: ChatRoomFilter.ALL)"))
|
||||
assertTrue(source.contains("val initialFilter = getInitialFilter() ?: ChatRoomFilter.ALL"))
|
||||
assertTrue(source.contains("viewModel.loadFirstPage(initialFilter)"))
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -145,6 +153,23 @@ class ChatMainFragmentLayoutTest {
|
||||
assertTrue(source.contains("!state.isAppending"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `채팅 fragment source는 탭별 empty state 문구를 표시한다`() {
|
||||
val source = chatMainFragmentSource()
|
||||
val filter = projectFile(
|
||||
"app/src/main/java/kr/co/vividnext/sodalive/v2/main/chat/model/ChatRoomFilter.kt"
|
||||
).readText()
|
||||
|
||||
assertTrue(source.contains("private var selectedFilter: ChatRoomFilter = ChatRoomFilter.ALL"))
|
||||
assertTrue(source.contains("binding.tvChatEmptyMessage.setText(selectedFilter.emptyMessageResId())"))
|
||||
assertTrue(source.contains("ChatRoomListUiState.Empty -> bindChatRooms(emptyList(), showEmpty = true)"))
|
||||
assertTrue(source.contains("is ChatRoomListUiState.Error -> bindChatRooms(emptyList(), showEmpty = false)"))
|
||||
assertTrue(source.contains("binding.rvChatRooms.isVisible = !showEmpty"))
|
||||
assertTrue(filter.contains("ChatRoomFilter.ALL -> R.string.screen_chat_empty_all"))
|
||||
assertTrue(filter.contains("ChatRoomFilter.AI -> R.string.screen_chat_empty_ai"))
|
||||
assertTrue(filter.contains("ChatRoomFilter.DM -> R.string.screen_chat_empty_dm"))
|
||||
}
|
||||
|
||||
private fun inflateView(layoutResId: Int): View {
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
return LayoutInflater.from(context).inflate(layoutResId, null, false)
|
||||
|
||||
Reference in New Issue
Block a user