검색 UI 추가
This commit is contained in:
parent
e4b0dbae82
commit
c7af522cfb
|
@ -35,8 +35,8 @@ android {
|
||||||
applicationId "kr.co.vividnext.sodalive"
|
applicationId "kr.co.vividnext.sodalive"
|
||||||
minSdk 23
|
minSdk 23
|
||||||
targetSdk 34
|
targetSdk 34
|
||||||
versionCode 154
|
versionCode 156
|
||||||
versionName "1.31.1"
|
versionName "1.32.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
|
|
|
@ -174,6 +174,8 @@
|
||||||
<activity android:name=".audio_content.main.v2.series.origianl_audio_drama.OriginalAudioDramaContentAllActivity" />
|
<activity android:name=".audio_content.main.v2.series.origianl_audio_drama.OriginalAudioDramaContentAllActivity" />
|
||||||
<activity android:name=".audio_content.main.v2.series.completed.CompletedSeriesActivity" />
|
<activity android:name=".audio_content.main.v2.series.completed.CompletedSeriesActivity" />
|
||||||
|
|
||||||
|
<activity android:name=".search.SearchActivity" />
|
||||||
|
|
||||||
<activity android:name=".mypage.alarm.AlarmListActivity" />
|
<activity android:name=".mypage.alarm.AlarmListActivity" />
|
||||||
<activity android:name=".mypage.alarm.AddAlarmActivity" />
|
<activity android:name=".mypage.alarm.AddAlarmActivity" />
|
||||||
<activity android:name=".mypage.alarm.select_audio_content.AlarmSelectAudioContentActivity" />
|
<activity android:name=".mypage.alarm.select_audio_content.AlarmSelectAudioContentActivity" />
|
||||||
|
|
|
@ -46,6 +46,7 @@ import kr.co.vividnext.sodalive.extensions.dpToPx
|
||||||
import kr.co.vividnext.sodalive.live.event_banner.EventBannerAdapter
|
import kr.co.vividnext.sodalive.live.event_banner.EventBannerAdapter
|
||||||
import kr.co.vividnext.sodalive.main.MainActivity
|
import kr.co.vividnext.sodalive.main.MainActivity
|
||||||
import kr.co.vividnext.sodalive.mypage.can.charge.CanChargeActivity
|
import kr.co.vividnext.sodalive.mypage.can.charge.CanChargeActivity
|
||||||
|
import kr.co.vividnext.sodalive.search.SearchActivity
|
||||||
import kr.co.vividnext.sodalive.settings.event.EventDetailActivity
|
import kr.co.vividnext.sodalive.settings.event.EventDetailActivity
|
||||||
import kr.co.vividnext.sodalive.settings.notice.NoticeDetailActivity
|
import kr.co.vividnext.sodalive.settings.notice.NoticeDetailActivity
|
||||||
import kr.co.vividnext.sodalive.settings.notification.MemberRole
|
import kr.co.vividnext.sodalive.settings.notification.MemberRole
|
||||||
|
@ -154,6 +155,12 @@ class AudioContentMainTabHomeFragment : BaseFragment<FragmentAudioContentMainTab
|
||||||
if (SharedPreferenceManager.token.isNotBlank()) {
|
if (SharedPreferenceManager.token.isNotBlank()) {
|
||||||
binding.flSearch.visibility = View.VISIBLE
|
binding.flSearch.visibility = View.VISIBLE
|
||||||
binding.flSearch.setOnClickListener {
|
binding.flSearch.setOnClickListener {
|
||||||
|
startActivity(
|
||||||
|
Intent(
|
||||||
|
requireContext(),
|
||||||
|
SearchActivity::class.java
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
binding.flSearch.visibility = View.GONE
|
binding.flSearch.visibility = View.GONE
|
||||||
|
|
|
@ -137,6 +137,9 @@ import kr.co.vividnext.sodalive.mypage.service_center.ServiceCenterViewModel
|
||||||
import kr.co.vividnext.sodalive.network.TokenAuthenticator
|
import kr.co.vividnext.sodalive.network.TokenAuthenticator
|
||||||
import kr.co.vividnext.sodalive.report.ReportApi
|
import kr.co.vividnext.sodalive.report.ReportApi
|
||||||
import kr.co.vividnext.sodalive.report.ReportRepository
|
import kr.co.vividnext.sodalive.report.ReportRepository
|
||||||
|
import kr.co.vividnext.sodalive.search.SearchApi
|
||||||
|
import kr.co.vividnext.sodalive.search.SearchRepository
|
||||||
|
import kr.co.vividnext.sodalive.search.SearchViewModel
|
||||||
import kr.co.vividnext.sodalive.settings.ContentSettingsViewModel
|
import kr.co.vividnext.sodalive.settings.ContentSettingsViewModel
|
||||||
import kr.co.vividnext.sodalive.settings.SettingsViewModel
|
import kr.co.vividnext.sodalive.settings.SettingsViewModel
|
||||||
import kr.co.vividnext.sodalive.settings.event.EventApi
|
import kr.co.vividnext.sodalive.settings.event.EventApi
|
||||||
|
@ -229,6 +232,7 @@ class AppDI(private val context: Context, isDebugMode: Boolean) {
|
||||||
single { ApiBuilder().build(get(), PlaylistApi::class.java) }
|
single { ApiBuilder().build(get(), PlaylistApi::class.java) }
|
||||||
single { ApiBuilder().build(get(), AuditionApi::class.java) }
|
single { ApiBuilder().build(get(), AuditionApi::class.java) }
|
||||||
single { ApiBuilder().build(get(), AdTrackingApi::class.java) }
|
single { ApiBuilder().build(get(), AdTrackingApi::class.java) }
|
||||||
|
single { ApiBuilder().build(get(), SearchApi::class.java) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private val viewModelModule = module {
|
private val viewModelModule = module {
|
||||||
|
@ -323,6 +327,7 @@ class AppDI(private val context: Context, isDebugMode: Boolean) {
|
||||||
viewModel { IntroduceCreatorViewModel(get()) }
|
viewModel { IntroduceCreatorViewModel(get()) }
|
||||||
viewModel { CompletedSeriesViewModel(get()) }
|
viewModel { CompletedSeriesViewModel(get()) }
|
||||||
viewModel { AlarmContentAllViewModel(get()) }
|
viewModel { AlarmContentAllViewModel(get()) }
|
||||||
|
viewModel { SearchViewModel(get()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private val repositoryModule = module {
|
private val repositoryModule = module {
|
||||||
|
@ -363,6 +368,7 @@ class AppDI(private val context: Context, isDebugMode: Boolean) {
|
||||||
factory { AudioContentMainTabFreeRepository(get()) }
|
factory { AudioContentMainTabFreeRepository(get()) }
|
||||||
factory { OriginalAudioDramaContentAllRepository(get()) }
|
factory { OriginalAudioDramaContentAllRepository(get()) }
|
||||||
factory { AdTrackingRepository(get()) }
|
factory { AdTrackingRepository(get()) }
|
||||||
|
factory { SearchRepository(get()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private val moduleList = listOf(
|
private val moduleList = listOf(
|
||||||
|
|
|
@ -0,0 +1,556 @@
|
||||||
|
package kr.co.vividnext.sodalive.search
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.Service
|
||||||
|
import android.content.Intent
|
||||||
|
import android.graphics.Rect
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
|
import android.view.View
|
||||||
|
import android.view.inputmethod.InputMethodManager
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.annotation.OptIn
|
||||||
|
import androidx.media3.common.util.UnstableApi
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import com.google.android.material.tabs.TabLayout
|
||||||
|
import com.jakewharton.rxbinding4.widget.textChanges
|
||||||
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||||
|
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||||
|
import kr.co.vividnext.sodalive.audio_content.detail.AudioContentDetailActivity
|
||||||
|
import kr.co.vividnext.sodalive.audio_content.series.detail.SeriesDetailActivity
|
||||||
|
import kr.co.vividnext.sodalive.base.BaseActivity
|
||||||
|
import kr.co.vividnext.sodalive.common.Constants
|
||||||
|
import kr.co.vividnext.sodalive.common.LoadingDialog
|
||||||
|
import kr.co.vividnext.sodalive.databinding.ActivitySearchBinding
|
||||||
|
import kr.co.vividnext.sodalive.explorer.profile.UserProfileActivity
|
||||||
|
import kr.co.vividnext.sodalive.extensions.dpToPx
|
||||||
|
import org.koin.android.ext.android.inject
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
class SearchActivity : BaseActivity<ActivitySearchBinding>(ActivitySearchBinding::inflate) {
|
||||||
|
|
||||||
|
private val viewModel: SearchViewModel by inject()
|
||||||
|
|
||||||
|
private lateinit var imm: InputMethodManager
|
||||||
|
private lateinit var loadingDialog: LoadingDialog
|
||||||
|
|
||||||
|
private val handler = Handler(Looper.getMainLooper())
|
||||||
|
|
||||||
|
private lateinit var unifiedChannelAdapter: SearchAdapter
|
||||||
|
private lateinit var unifiedContentAdapter: SearchAdapter
|
||||||
|
private lateinit var unifiedSeriesAdapter: SearchAdapter
|
||||||
|
|
||||||
|
private lateinit var channelAdapter: SearchAdapter
|
||||||
|
private lateinit var contentAdapter: SearchAdapter
|
||||||
|
private lateinit var seriesAdapter: SearchAdapter
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
imm = getSystemService(
|
||||||
|
Service.INPUT_METHOD_SERVICE
|
||||||
|
) as InputMethodManager
|
||||||
|
|
||||||
|
binding.etSearch.requestFocus()
|
||||||
|
handler.postDelayed({
|
||||||
|
val imm = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
|
||||||
|
imm.showSoftInput(binding.etSearch, InputMethodManager.SHOW_IMPLICIT)
|
||||||
|
}, 500)
|
||||||
|
|
||||||
|
bindData()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPause() {
|
||||||
|
hideKeyboard()
|
||||||
|
super.onPause()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setupView() {
|
||||||
|
loadingDialog = LoadingDialog(this, layoutInflater)
|
||||||
|
binding.ivBack.setOnClickListener { finish() }
|
||||||
|
|
||||||
|
setupTabs()
|
||||||
|
setupUnifiedView()
|
||||||
|
setupChannelListView()
|
||||||
|
setupContentListView()
|
||||||
|
setupSeriesListView()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupChannelListView() {
|
||||||
|
channelAdapter = SearchAdapter { clickItem(it) }
|
||||||
|
|
||||||
|
binding.rvCreator.layoutManager = LinearLayoutManager(
|
||||||
|
this,
|
||||||
|
LinearLayoutManager.VERTICAL,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
|
||||||
|
binding.rvCreator.addItemDecoration(object : RecyclerView.ItemDecoration() {
|
||||||
|
override fun getItemOffsets(
|
||||||
|
outRect: Rect,
|
||||||
|
view: View,
|
||||||
|
parent: RecyclerView,
|
||||||
|
state: RecyclerView.State
|
||||||
|
) {
|
||||||
|
super.getItemOffsets(outRect, view, parent, state)
|
||||||
|
|
||||||
|
outRect.left = 13.3f.dpToPx().toInt()
|
||||||
|
outRect.right = 13.3f.dpToPx().toInt()
|
||||||
|
outRect.top = 13.3f.dpToPx().toInt()
|
||||||
|
outRect.bottom = 13.3f.dpToPx().toInt()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
binding.rvCreator.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||||
|
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||||
|
super.onScrolled(recyclerView, dx, dy)
|
||||||
|
|
||||||
|
val layoutManager = recyclerView.layoutManager as? LinearLayoutManager
|
||||||
|
if (
|
||||||
|
layoutManager != null &&
|
||||||
|
layoutManager
|
||||||
|
.findLastCompletelyVisibleItemPosition() == channelAdapter.itemCount - 1
|
||||||
|
) {
|
||||||
|
viewModel.searchCreatorList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
binding.rvCreator.adapter = channelAdapter
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupContentListView() {
|
||||||
|
contentAdapter = SearchAdapter { clickItem(it) }
|
||||||
|
|
||||||
|
binding.rvContent.layoutManager = LinearLayoutManager(
|
||||||
|
this,
|
||||||
|
LinearLayoutManager.VERTICAL,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
|
||||||
|
binding.rvContent.addItemDecoration(object : RecyclerView.ItemDecoration() {
|
||||||
|
override fun getItemOffsets(
|
||||||
|
outRect: Rect,
|
||||||
|
view: View,
|
||||||
|
parent: RecyclerView,
|
||||||
|
state: RecyclerView.State
|
||||||
|
) {
|
||||||
|
super.getItemOffsets(outRect, view, parent, state)
|
||||||
|
|
||||||
|
outRect.left = 13.3f.dpToPx().toInt()
|
||||||
|
outRect.right = 13.3f.dpToPx().toInt()
|
||||||
|
outRect.top = 13.3f.dpToPx().toInt()
|
||||||
|
outRect.bottom = 13.3f.dpToPx().toInt()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
binding.rvContent.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||||
|
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||||
|
super.onScrolled(recyclerView, dx, dy)
|
||||||
|
|
||||||
|
val layoutManager = recyclerView.layoutManager as? LinearLayoutManager
|
||||||
|
if (
|
||||||
|
layoutManager != null &&
|
||||||
|
layoutManager
|
||||||
|
.findLastCompletelyVisibleItemPosition() == contentAdapter.itemCount - 1
|
||||||
|
) {
|
||||||
|
viewModel.searchContentList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
binding.rvContent.adapter = contentAdapter
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupSeriesListView() {
|
||||||
|
seriesAdapter = SearchAdapter { clickItem(it) }
|
||||||
|
|
||||||
|
binding.rvSeries.layoutManager = LinearLayoutManager(
|
||||||
|
this,
|
||||||
|
LinearLayoutManager.VERTICAL,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
|
||||||
|
binding.rvSeries.addItemDecoration(object : RecyclerView.ItemDecoration() {
|
||||||
|
override fun getItemOffsets(
|
||||||
|
outRect: Rect,
|
||||||
|
view: View,
|
||||||
|
parent: RecyclerView,
|
||||||
|
state: RecyclerView.State
|
||||||
|
) {
|
||||||
|
super.getItemOffsets(outRect, view, parent, state)
|
||||||
|
|
||||||
|
outRect.left = 13.3f.dpToPx().toInt()
|
||||||
|
outRect.right = 13.3f.dpToPx().toInt()
|
||||||
|
outRect.top = 13.3f.dpToPx().toInt()
|
||||||
|
outRect.bottom = 13.3f.dpToPx().toInt()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
binding.rvSeries.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||||
|
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
||||||
|
super.onScrolled(recyclerView, dx, dy)
|
||||||
|
|
||||||
|
val layoutManager = recyclerView.layoutManager as? LinearLayoutManager
|
||||||
|
if (
|
||||||
|
layoutManager != null &&
|
||||||
|
layoutManager
|
||||||
|
.findLastCompletelyVisibleItemPosition() == seriesAdapter.itemCount - 1
|
||||||
|
) {
|
||||||
|
viewModel.searchSeriesList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
binding.rvSeries.adapter = seriesAdapter
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupUnifiedView() {
|
||||||
|
unifiedChannelAdapter = SearchAdapter { clickItem(it) }
|
||||||
|
unifiedContentAdapter = SearchAdapter { clickItem(it) }
|
||||||
|
unifiedSeriesAdapter = SearchAdapter { clickItem(it) }
|
||||||
|
|
||||||
|
binding.tvMoreCreator.setOnClickListener {
|
||||||
|
viewModel.changeTab(SearchViewModel.SearchPageTab.CREATOR)
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.tvMoreContent.setOnClickListener {
|
||||||
|
viewModel.changeTab(SearchViewModel.SearchPageTab.CONTENT)
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.tvMoreSeries.setOnClickListener {
|
||||||
|
viewModel.changeTab(SearchViewModel.SearchPageTab.SERIES)
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.rvUnifiedCreator.layoutManager = LinearLayoutManager(
|
||||||
|
this,
|
||||||
|
LinearLayoutManager.VERTICAL,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
binding.rvUnifiedCreator.addItemDecoration(object : RecyclerView.ItemDecoration() {
|
||||||
|
override fun getItemOffsets(
|
||||||
|
outRect: Rect,
|
||||||
|
view: View,
|
||||||
|
parent: RecyclerView,
|
||||||
|
state: RecyclerView.State
|
||||||
|
) {
|
||||||
|
super.getItemOffsets(outRect, view, parent, state)
|
||||||
|
|
||||||
|
outRect.left = 13.3f.dpToPx().toInt()
|
||||||
|
outRect.right = 13.3f.dpToPx().toInt()
|
||||||
|
outRect.top = 13.3f.dpToPx().toInt()
|
||||||
|
outRect.bottom = 13.3f.dpToPx().toInt()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
binding.rvUnifiedCreator.adapter = unifiedChannelAdapter
|
||||||
|
|
||||||
|
binding.rvUnifiedContent.layoutManager = LinearLayoutManager(
|
||||||
|
this,
|
||||||
|
LinearLayoutManager.VERTICAL,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
binding.rvUnifiedContent.addItemDecoration(object : RecyclerView.ItemDecoration() {
|
||||||
|
override fun getItemOffsets(
|
||||||
|
outRect: Rect,
|
||||||
|
view: View,
|
||||||
|
parent: RecyclerView,
|
||||||
|
state: RecyclerView.State
|
||||||
|
) {
|
||||||
|
super.getItemOffsets(outRect, view, parent, state)
|
||||||
|
|
||||||
|
outRect.left = 13.3f.dpToPx().toInt()
|
||||||
|
outRect.right = 13.3f.dpToPx().toInt()
|
||||||
|
outRect.top = 13.3f.dpToPx().toInt()
|
||||||
|
outRect.bottom = 13.3f.dpToPx().toInt()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
binding.rvUnifiedContent.adapter = unifiedContentAdapter
|
||||||
|
|
||||||
|
binding.rvUnifiedSeries.layoutManager = LinearLayoutManager(
|
||||||
|
this,
|
||||||
|
LinearLayoutManager.VERTICAL,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
binding.rvUnifiedSeries.addItemDecoration(object : RecyclerView.ItemDecoration() {
|
||||||
|
override fun getItemOffsets(
|
||||||
|
outRect: Rect,
|
||||||
|
view: View,
|
||||||
|
parent: RecyclerView,
|
||||||
|
state: RecyclerView.State
|
||||||
|
) {
|
||||||
|
super.getItemOffsets(outRect, view, parent, state)
|
||||||
|
|
||||||
|
outRect.left = 13.3f.dpToPx().toInt()
|
||||||
|
outRect.right = 13.3f.dpToPx().toInt()
|
||||||
|
outRect.top = 13.3f.dpToPx().toInt()
|
||||||
|
outRect.bottom = 13.3f.dpToPx().toInt()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
binding.rvUnifiedSeries.adapter = unifiedSeriesAdapter
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(UnstableApi::class)
|
||||||
|
private fun clickItem(item: SearchResponseItem) {
|
||||||
|
hideKeyboard()
|
||||||
|
|
||||||
|
startActivity(
|
||||||
|
when (item.type) {
|
||||||
|
SearchResponseType.CREATOR -> {
|
||||||
|
Intent(applicationContext, UserProfileActivity::class.java).apply {
|
||||||
|
putExtra(Constants.EXTRA_USER_ID, item.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SearchResponseType.CONTENT -> {
|
||||||
|
Intent(applicationContext, AudioContentDetailActivity::class.java).apply {
|
||||||
|
putExtra(Constants.EXTRA_AUDIO_CONTENT_ID, item.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SearchResponseType.SERIES -> {
|
||||||
|
Intent(applicationContext, SeriesDetailActivity::class.java).apply {
|
||||||
|
putExtra(Constants.EXTRA_SERIES_ID, item.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupTabs() {
|
||||||
|
val tabs = binding.tabs
|
||||||
|
val tabTitles = listOf("통합", "채널", "콘텐츠", "시리즈")
|
||||||
|
for (title in tabTitles) {
|
||||||
|
tabs.addTab(tabs.newTab().setText(title))
|
||||||
|
}
|
||||||
|
|
||||||
|
tabs.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
|
||||||
|
override fun onTabSelected(tab: TabLayout.Tab) {
|
||||||
|
val selectedTab = SearchViewModel.SearchPageTab.fromOrdinal(tab.position)
|
||||||
|
viewModel.changeTab(selectedTab!!)
|
||||||
|
|
||||||
|
tab.view.isSelected = true
|
||||||
|
hideKeyboard()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onTabUnselected(tab: TabLayout.Tab) {
|
||||||
|
tab.view.isSelected = false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onTabReselected(tab: TabLayout.Tab) {
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
|
private fun bindData() {
|
||||||
|
compositeDisposable.add(
|
||||||
|
binding.etSearch.textChanges().skip(1)
|
||||||
|
.debounce(500, TimeUnit.MILLISECONDS)
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.subscribe {
|
||||||
|
channelAdapter.clear()
|
||||||
|
contentAdapter.clear()
|
||||||
|
seriesAdapter.clear()
|
||||||
|
|
||||||
|
unifiedChannelAdapter.clear()
|
||||||
|
unifiedContentAdapter.clear()
|
||||||
|
unifiedSeriesAdapter.clear()
|
||||||
|
|
||||||
|
viewModel.keyword = it.toString()
|
||||||
|
if (it.length >= 2) {
|
||||||
|
viewModel.searchUnified()
|
||||||
|
binding.tabs.visibility = View.VISIBLE
|
||||||
|
binding.tvResultX.visibility = View.GONE
|
||||||
|
} else {
|
||||||
|
binding.nsSearchUnified.visibility = View.GONE
|
||||||
|
binding.rvCreator.visibility = View.GONE
|
||||||
|
binding.rvContent.visibility = View.GONE
|
||||||
|
binding.rvSeries.visibility = View.GONE
|
||||||
|
binding.tabs.visibility = View.GONE
|
||||||
|
binding.tvResultX.visibility = View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
viewModel.toastLiveData.observe(this) {
|
||||||
|
it?.let { Toast.makeText(this@SearchActivity, it, Toast.LENGTH_LONG).show() }
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModel.isLoading.observe(this) {
|
||||||
|
if (it) {
|
||||||
|
loadingDialog.show(screenWidth, "")
|
||||||
|
} else {
|
||||||
|
loadingDialog.dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModel.searchUnifiedLiveData.observe(this) {
|
||||||
|
if (
|
||||||
|
it.creatorList.isEmpty() &&
|
||||||
|
it.seriesList.isEmpty() &&
|
||||||
|
it.contentList.isEmpty()
|
||||||
|
) {
|
||||||
|
binding.tabs.visibility = View.GONE
|
||||||
|
hideAllView()
|
||||||
|
if (viewModel.keyword.isNotBlank()) {
|
||||||
|
binding.tvResultX.visibility = View.VISIBLE
|
||||||
|
} else {
|
||||||
|
binding.tvResultX.visibility = View.GONE
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
binding.tabs.visibility = View.VISIBLE
|
||||||
|
binding.nsSearchUnified.visibility = View.VISIBLE
|
||||||
|
|
||||||
|
if (it.creatorList.isNotEmpty()) {
|
||||||
|
binding.tvCreatorTitle.visibility = View.VISIBLE
|
||||||
|
binding.tvMoreCreator.visibility = View.VISIBLE
|
||||||
|
binding.rvUnifiedCreator.visibility = View.VISIBLE
|
||||||
|
unifiedChannelAdapter.items.addAll(it.creatorList)
|
||||||
|
unifiedChannelAdapter.notifyDataSetChanged()
|
||||||
|
} else {
|
||||||
|
binding.tvCreatorTitle.visibility = View.GONE
|
||||||
|
binding.tvMoreCreator.visibility = View.GONE
|
||||||
|
binding.rvUnifiedCreator.visibility = View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
if (it.contentList.isNotEmpty()) {
|
||||||
|
binding.tvContentTitle.visibility = View.VISIBLE
|
||||||
|
binding.tvMoreContent.visibility = View.VISIBLE
|
||||||
|
binding.rvUnifiedContent.visibility = View.VISIBLE
|
||||||
|
unifiedContentAdapter.items.addAll(it.contentList)
|
||||||
|
unifiedContentAdapter.notifyDataSetChanged()
|
||||||
|
} else {
|
||||||
|
binding.tvContentTitle.visibility = View.GONE
|
||||||
|
binding.tvMoreContent.visibility = View.GONE
|
||||||
|
binding.rvUnifiedContent.visibility = View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
if (it.seriesList.isNotEmpty()) {
|
||||||
|
binding.tvSeriesTitle.visibility = View.VISIBLE
|
||||||
|
binding.tvMoreSeries.visibility = View.VISIBLE
|
||||||
|
binding.rvUnifiedSeries.visibility = View.VISIBLE
|
||||||
|
unifiedSeriesAdapter.items.addAll(it.seriesList)
|
||||||
|
unifiedSeriesAdapter.notifyDataSetChanged()
|
||||||
|
} else {
|
||||||
|
binding.tvSeriesTitle.visibility = View.GONE
|
||||||
|
binding.tvMoreSeries.visibility = View.GONE
|
||||||
|
binding.rvUnifiedSeries.visibility = View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModel.searchCreatorLiveData.observe(this) {
|
||||||
|
channelAdapter.items.addAll(it)
|
||||||
|
channelAdapter.notifyDataSetChanged()
|
||||||
|
|
||||||
|
hideAllView()
|
||||||
|
if (channelAdapter.items.isEmpty()) {
|
||||||
|
if (viewModel.keyword.isNotBlank()) {
|
||||||
|
binding.tvResultX.visibility = View.VISIBLE
|
||||||
|
} else {
|
||||||
|
binding.tvResultX.visibility = View.GONE
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
binding.rvCreator.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModel.searchContentLiveData.observe(this) {
|
||||||
|
contentAdapter.items.addAll(it)
|
||||||
|
contentAdapter.notifyDataSetChanged()
|
||||||
|
|
||||||
|
hideAllView()
|
||||||
|
if (contentAdapter.items.isEmpty()) {
|
||||||
|
if (viewModel.keyword.isNotBlank()) {
|
||||||
|
binding.tvResultX.visibility = View.VISIBLE
|
||||||
|
} else {
|
||||||
|
binding.tvResultX.visibility = View.GONE
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
binding.rvContent.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModel.searchSeriesLiveData.observe(this) {
|
||||||
|
seriesAdapter.items.addAll(it)
|
||||||
|
seriesAdapter.notifyDataSetChanged()
|
||||||
|
|
||||||
|
hideAllView()
|
||||||
|
if (seriesAdapter.items.isEmpty()) {
|
||||||
|
if (viewModel.keyword.isNotBlank()) {
|
||||||
|
binding.tvResultX.visibility = View.VISIBLE
|
||||||
|
} else {
|
||||||
|
binding.tvResultX.visibility = View.GONE
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
binding.rvSeries.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModel.currentTabLiveData.observe(this) { currentTab ->
|
||||||
|
hideAllView()
|
||||||
|
|
||||||
|
binding.tabs.getTabAt(currentTab.ordinal)?.select()
|
||||||
|
when (currentTab) {
|
||||||
|
SearchViewModel.SearchPageTab.CREATOR -> {
|
||||||
|
if (channelAdapter.items.isEmpty()) {
|
||||||
|
viewModel.searchCreatorList()
|
||||||
|
} else {
|
||||||
|
binding.rvCreator.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SearchViewModel.SearchPageTab.CONTENT -> {
|
||||||
|
if (contentAdapter.items.isEmpty()) {
|
||||||
|
viewModel.searchContentList()
|
||||||
|
} else {
|
||||||
|
binding.rvContent.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SearchViewModel.SearchPageTab.SERIES -> {
|
||||||
|
if (seriesAdapter.items.isEmpty()) {
|
||||||
|
viewModel.searchSeriesList()
|
||||||
|
} else {
|
||||||
|
binding.rvSeries.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
if (
|
||||||
|
unifiedChannelAdapter.items.isEmpty() &&
|
||||||
|
unifiedContentAdapter.items.isEmpty() &&
|
||||||
|
unifiedSeriesAdapter.items.isEmpty()
|
||||||
|
) {
|
||||||
|
if (viewModel.keyword.isNotBlank()) {
|
||||||
|
binding.tvResultX.visibility = View.VISIBLE
|
||||||
|
} else {
|
||||||
|
binding.tvResultX.visibility = View.GONE
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
binding.nsSearchUnified.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun hideAllView() {
|
||||||
|
binding.nsSearchUnified.visibility = View.GONE
|
||||||
|
binding.rvCreator.visibility = View.GONE
|
||||||
|
binding.rvContent.visibility = View.GONE
|
||||||
|
binding.rvSeries.visibility = View.GONE
|
||||||
|
binding.tvResultX.visibility = View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun hideKeyboard() {
|
||||||
|
handler.postDelayed({
|
||||||
|
imm.hideSoftInputFromWindow(
|
||||||
|
window.decorView.applicationWindowToken,
|
||||||
|
InputMethodManager.HIDE_NOT_ALWAYS
|
||||||
|
)
|
||||||
|
}, 100)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,97 @@
|
||||||
|
package kr.co.vividnext.sodalive.search
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Context
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import coil.load
|
||||||
|
import coil.transform.CircleCropTransformation
|
||||||
|
import coil.transform.RoundedCornersTransformation
|
||||||
|
import kr.co.vividnext.sodalive.R
|
||||||
|
import kr.co.vividnext.sodalive.databinding.ItemSearchBinding
|
||||||
|
import kr.co.vividnext.sodalive.extensions.dpToPx
|
||||||
|
|
||||||
|
class SearchAdapter(
|
||||||
|
private val onClickItem: (SearchResponseItem) -> Unit
|
||||||
|
) : RecyclerView.Adapter<SearchAdapter.ViewHolder>() {
|
||||||
|
inner class ViewHolder(
|
||||||
|
private val context: Context,
|
||||||
|
private val binding: ItemSearchBinding
|
||||||
|
) : RecyclerView.ViewHolder(binding.root) {
|
||||||
|
|
||||||
|
@SuppressLint("CheckResult")
|
||||||
|
fun bind(item: SearchResponseItem) {
|
||||||
|
binding.ivProfile.visibility = View.GONE
|
||||||
|
binding.ivContent.visibility = View.GONE
|
||||||
|
binding.ivSeries.visibility = View.GONE
|
||||||
|
binding.tvNickname.visibility = View.GONE
|
||||||
|
|
||||||
|
when (item.type) {
|
||||||
|
SearchResponseType.CREATOR -> {
|
||||||
|
binding.ivProfile.visibility = View.VISIBLE
|
||||||
|
binding.ivProfile.load(item.imageUrl) {
|
||||||
|
crossfade(true)
|
||||||
|
placeholder(R.drawable.bg_placeholder)
|
||||||
|
transformations(CircleCropTransformation())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SearchResponseType.CONTENT -> {
|
||||||
|
binding.ivContent.visibility = View.VISIBLE
|
||||||
|
binding.ivContent.load(item.imageUrl) {
|
||||||
|
crossfade(true)
|
||||||
|
placeholder(R.drawable.bg_placeholder)
|
||||||
|
transformations(RoundedCornersTransformation(5.3f.dpToPx()))
|
||||||
|
}
|
||||||
|
binding.tvNickname.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
|
||||||
|
SearchResponseType.SERIES -> {
|
||||||
|
binding.ivSeries.visibility = View.VISIBLE
|
||||||
|
binding.ivSeries.load(item.imageUrl) {
|
||||||
|
crossfade(true)
|
||||||
|
placeholder(R.drawable.bg_placeholder)
|
||||||
|
transformations(RoundedCornersTransformation(5.3f.dpToPx()))
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.ivSeries.load(item.imageUrl) {
|
||||||
|
crossfade(true)
|
||||||
|
placeholder(R.drawable.bg_placeholder)
|
||||||
|
transformations(RoundedCornersTransformation(5.3f.dpToPx()))
|
||||||
|
}
|
||||||
|
binding.tvNickname.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.tvTitle.text = item.title
|
||||||
|
binding.tvNickname.text = item.nickname
|
||||||
|
|
||||||
|
binding.root.setOnClickListener { onClickItem(item) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val items: MutableList<SearchResponseItem> = mutableListOf()
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder(
|
||||||
|
parent.context,
|
||||||
|
ItemSearchBinding.inflate(
|
||||||
|
LayoutInflater.from(parent.context),
|
||||||
|
parent,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
|
holder.bind(items[position])
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount() = items.size
|
||||||
|
|
||||||
|
@SuppressLint("NotifyDataSetChanged")
|
||||||
|
fun clear() {
|
||||||
|
items.clear()
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
package kr.co.vividnext.sodalive.search
|
||||||
|
|
||||||
|
import io.reactivex.rxjava3.core.Single
|
||||||
|
import kr.co.vividnext.sodalive.common.ApiResponse
|
||||||
|
import kr.co.vividnext.sodalive.settings.ContentType
|
||||||
|
import retrofit2.http.GET
|
||||||
|
import retrofit2.http.Header
|
||||||
|
import retrofit2.http.Query
|
||||||
|
|
||||||
|
interface SearchApi {
|
||||||
|
@GET("/search")
|
||||||
|
fun searchUnified(
|
||||||
|
@Query("keyword") keyword: String,
|
||||||
|
@Query("isAdultContentVisible") isAdultContentVisible: Boolean,
|
||||||
|
@Query("contentType") contentType: ContentType,
|
||||||
|
@Header("Authorization") authHeader: String
|
||||||
|
): Single<ApiResponse<SearchUnifiedResponse>>
|
||||||
|
|
||||||
|
@GET("/search/creators")
|
||||||
|
fun searchCreatorList(
|
||||||
|
@Query("keyword") keyword: String,
|
||||||
|
@Query("isAdultContentVisible") isAdultContentVisible: Boolean,
|
||||||
|
@Query("contentType") contentType: ContentType,
|
||||||
|
@Query("page") page: Int,
|
||||||
|
@Query("size") size: Int,
|
||||||
|
@Header("Authorization") authHeader: String
|
||||||
|
): Single<ApiResponse<SearchResponse>>
|
||||||
|
|
||||||
|
@GET("/search/contents")
|
||||||
|
fun searchContentList(
|
||||||
|
@Query("keyword") keyword: String,
|
||||||
|
@Query("isAdultContentVisible") isAdultContentVisible: Boolean,
|
||||||
|
@Query("contentType") contentType: ContentType,
|
||||||
|
@Query("page") page: Int,
|
||||||
|
@Query("size") size: Int,
|
||||||
|
@Header("Authorization") authHeader: String
|
||||||
|
): Single<ApiResponse<SearchResponse>>
|
||||||
|
|
||||||
|
@GET("/search/series")
|
||||||
|
fun searchSeriesList(
|
||||||
|
@Query("keyword") keyword: String,
|
||||||
|
@Query("isAdultContentVisible") isAdultContentVisible: Boolean,
|
||||||
|
@Query("contentType") contentType: ContentType,
|
||||||
|
@Query("page") page: Int,
|
||||||
|
@Query("size") size: Int,
|
||||||
|
@Header("Authorization") authHeader: String
|
||||||
|
): Single<ApiResponse<SearchResponse>>
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
package kr.co.vividnext.sodalive.search
|
||||||
|
|
||||||
|
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
|
||||||
|
import kr.co.vividnext.sodalive.settings.ContentType
|
||||||
|
|
||||||
|
class SearchRepository(private val api: SearchApi) {
|
||||||
|
fun searchUnified(
|
||||||
|
keyword: String,
|
||||||
|
token: String
|
||||||
|
) = api.searchUnified(
|
||||||
|
keyword = keyword,
|
||||||
|
isAdultContentVisible = SharedPreferenceManager.isAdultContentVisible,
|
||||||
|
contentType = ContentType.values()[SharedPreferenceManager.contentPreference],
|
||||||
|
authHeader = token
|
||||||
|
)
|
||||||
|
|
||||||
|
fun searchCreatorList(
|
||||||
|
keyword: String,
|
||||||
|
page: Int,
|
||||||
|
size: Int,
|
||||||
|
token: String
|
||||||
|
) = api.searchCreatorList(
|
||||||
|
keyword = keyword,
|
||||||
|
isAdultContentVisible = SharedPreferenceManager.isAdultContentVisible,
|
||||||
|
contentType = ContentType.values()[SharedPreferenceManager.contentPreference],
|
||||||
|
page = page - 1,
|
||||||
|
size = size,
|
||||||
|
authHeader = token
|
||||||
|
)
|
||||||
|
|
||||||
|
fun searchContentList(
|
||||||
|
keyword: String,
|
||||||
|
page: Int,
|
||||||
|
size: Int,
|
||||||
|
token: String
|
||||||
|
) = api.searchContentList(
|
||||||
|
keyword = keyword,
|
||||||
|
isAdultContentVisible = SharedPreferenceManager.isAdultContentVisible,
|
||||||
|
contentType = ContentType.values()[SharedPreferenceManager.contentPreference],
|
||||||
|
page = page - 1,
|
||||||
|
size = size,
|
||||||
|
authHeader = token
|
||||||
|
)
|
||||||
|
|
||||||
|
fun searchSeriesList(
|
||||||
|
keyword: String,
|
||||||
|
page: Int,
|
||||||
|
size: Int,
|
||||||
|
token: String
|
||||||
|
) = api.searchSeriesList(
|
||||||
|
keyword = keyword,
|
||||||
|
isAdultContentVisible = SharedPreferenceManager.isAdultContentVisible,
|
||||||
|
contentType = ContentType.values()[SharedPreferenceManager.contentPreference],
|
||||||
|
page = page - 1,
|
||||||
|
size = size,
|
||||||
|
authHeader = token
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
package kr.co.vividnext.sodalive.search
|
||||||
|
|
||||||
|
import androidx.annotation.Keep
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
data class SearchUnifiedResponse(
|
||||||
|
@SerializedName("creatorList") val creatorList: List<SearchResponseItem>,
|
||||||
|
@SerializedName("contentList") val contentList: List<SearchResponseItem>,
|
||||||
|
@SerializedName("seriesList") val seriesList: List<SearchResponseItem>
|
||||||
|
)
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
data class SearchResponse(
|
||||||
|
@SerializedName("totalCount") val totalCount: Int,
|
||||||
|
@SerializedName("items") val items: List<SearchResponseItem>
|
||||||
|
)
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
data class SearchResponseItem(
|
||||||
|
@SerializedName("id") val id: Long,
|
||||||
|
@SerializedName("imageUrl") val imageUrl: String,
|
||||||
|
@SerializedName("title") val title: String,
|
||||||
|
@SerializedName("nickname") val nickname: String,
|
||||||
|
@SerializedName("type") val type: SearchResponseType
|
||||||
|
)
|
||||||
|
|
||||||
|
@Keep
|
||||||
|
enum class SearchResponseType {
|
||||||
|
@SerializedName("CREATOR") CREATOR,
|
||||||
|
@SerializedName("CONTENT") CONTENT,
|
||||||
|
@SerializedName("SERIES") SERIES
|
||||||
|
}
|
|
@ -0,0 +1,250 @@
|
||||||
|
package kr.co.vividnext.sodalive.search
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import com.orhanobut.logger.Logger
|
||||||
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||||
|
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||||
|
import kr.co.vividnext.sodalive.base.BaseViewModel
|
||||||
|
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
|
||||||
|
|
||||||
|
class SearchViewModel(
|
||||||
|
private val repository: SearchRepository
|
||||||
|
) : BaseViewModel() {
|
||||||
|
enum class SearchPageTab {
|
||||||
|
UNIFIED, CREATOR, CONTENT, SERIES;
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun fromOrdinal(ordinal: Int): SearchPageTab? {
|
||||||
|
return SearchPageTab.values().getOrNull(ordinal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var keyword = ""
|
||||||
|
|
||||||
|
private val _currentTabLiveData = MutableLiveData(SearchPageTab.UNIFIED)
|
||||||
|
val currentTabLiveData: LiveData<SearchPageTab>
|
||||||
|
get() = _currentTabLiveData
|
||||||
|
|
||||||
|
private val _toastLiveData = MutableLiveData<String?>()
|
||||||
|
val toastLiveData: LiveData<String?>
|
||||||
|
get() = _toastLiveData
|
||||||
|
|
||||||
|
private var _isLoading = MutableLiveData(false)
|
||||||
|
val isLoading: LiveData<Boolean>
|
||||||
|
get() = _isLoading
|
||||||
|
|
||||||
|
private var _searchUnifiedLiveData = MutableLiveData<SearchUnifiedResponse>()
|
||||||
|
val searchUnifiedLiveData: LiveData<SearchUnifiedResponse>
|
||||||
|
get() = _searchUnifiedLiveData
|
||||||
|
|
||||||
|
private var _searchCreatorLiveData = MutableLiveData<List<SearchResponseItem>>()
|
||||||
|
val searchCreatorLiveData: LiveData<List<SearchResponseItem>>
|
||||||
|
get() = _searchCreatorLiveData
|
||||||
|
|
||||||
|
private var _searchContentLiveData = MutableLiveData<List<SearchResponseItem>>()
|
||||||
|
val searchContentLiveData: LiveData<List<SearchResponseItem>>
|
||||||
|
get() = _searchContentLiveData
|
||||||
|
|
||||||
|
private var _searchSeriesLiveData = MutableLiveData<List<SearchResponseItem>>()
|
||||||
|
val searchSeriesLiveData: LiveData<List<SearchResponseItem>>
|
||||||
|
get() = _searchSeriesLiveData
|
||||||
|
|
||||||
|
private var searchCreatorPage = 1
|
||||||
|
private var searchContentPage = 1
|
||||||
|
private var searchSeriesPage = 1
|
||||||
|
|
||||||
|
private var isSearchCreatorLast = false
|
||||||
|
private var isSearchContentLast = false
|
||||||
|
private var isSearchSeriesLast = false
|
||||||
|
|
||||||
|
private val size = 20
|
||||||
|
|
||||||
|
fun changeTab(tab: SearchPageTab) {
|
||||||
|
_currentTabLiveData.value = tab
|
||||||
|
}
|
||||||
|
|
||||||
|
fun searchUnified() {
|
||||||
|
if (!_isLoading.value!!) {
|
||||||
|
_currentTabLiveData.value = SearchPageTab.UNIFIED
|
||||||
|
|
||||||
|
searchCreatorPage = 1
|
||||||
|
searchContentPage = 1
|
||||||
|
searchSeriesPage = 1
|
||||||
|
|
||||||
|
isSearchCreatorLast = false
|
||||||
|
isSearchContentLast = false
|
||||||
|
isSearchSeriesLast = false
|
||||||
|
|
||||||
|
_isLoading.value = true
|
||||||
|
compositeDisposable.add(
|
||||||
|
repository.searchUnified(
|
||||||
|
keyword = keyword,
|
||||||
|
token = "Bearer ${SharedPreferenceManager.token}"
|
||||||
|
)
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(
|
||||||
|
{
|
||||||
|
_isLoading.value = false
|
||||||
|
if (it.success && it.data != null) {
|
||||||
|
_searchUnifiedLiveData.value = it.data!!
|
||||||
|
} else {
|
||||||
|
if (it.message != null) {
|
||||||
|
_toastLiveData.value = it.message
|
||||||
|
} else {
|
||||||
|
_toastLiveData
|
||||||
|
.value = "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_isLoading.value = false
|
||||||
|
it.message?.let { message -> Logger.e(message) }
|
||||||
|
_toastLiveData
|
||||||
|
.value = "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun searchCreatorList() {
|
||||||
|
if (!_isLoading.value!! && !isSearchCreatorLast) {
|
||||||
|
_isLoading.value = true
|
||||||
|
compositeDisposable.add(
|
||||||
|
repository.searchCreatorList(
|
||||||
|
keyword = keyword,
|
||||||
|
page = searchCreatorPage,
|
||||||
|
size = size,
|
||||||
|
token = "Bearer ${SharedPreferenceManager.token}"
|
||||||
|
)
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(
|
||||||
|
{
|
||||||
|
_isLoading.value = false
|
||||||
|
if (it.success && it.data != null) {
|
||||||
|
searchCreatorPage += 1
|
||||||
|
|
||||||
|
val data = it.data
|
||||||
|
_searchCreatorLiveData.value = data.items
|
||||||
|
|
||||||
|
if (data.items.isEmpty()) {
|
||||||
|
isSearchCreatorLast = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (it.message != null) {
|
||||||
|
_toastLiveData.value = it.message
|
||||||
|
} else {
|
||||||
|
_toastLiveData
|
||||||
|
.value = "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_isLoading.value = false
|
||||||
|
it.message?.let { message -> Logger.e(message) }
|
||||||
|
_toastLiveData
|
||||||
|
.value = "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
_searchCreatorLiveData.value = emptyList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun searchContentList() {
|
||||||
|
if (!_isLoading.value!! && !isSearchContentLast) {
|
||||||
|
_isLoading.value = true
|
||||||
|
compositeDisposable.add(
|
||||||
|
repository.searchContentList(
|
||||||
|
keyword = keyword,
|
||||||
|
page = searchContentPage,
|
||||||
|
size = size,
|
||||||
|
token = "Bearer ${SharedPreferenceManager.token}"
|
||||||
|
)
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(
|
||||||
|
{
|
||||||
|
_isLoading.value = false
|
||||||
|
if (it.success && it.data != null) {
|
||||||
|
searchContentPage += 1
|
||||||
|
|
||||||
|
val data = it.data
|
||||||
|
_searchContentLiveData.value = data.items
|
||||||
|
|
||||||
|
if (data.items.isEmpty()) {
|
||||||
|
isSearchContentLast = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (it.message != null) {
|
||||||
|
_toastLiveData.value = it.message
|
||||||
|
} else {
|
||||||
|
_toastLiveData
|
||||||
|
.value = "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_isLoading.value = false
|
||||||
|
it.message?.let { message -> Logger.e(message) }
|
||||||
|
_toastLiveData
|
||||||
|
.value = "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
_searchContentLiveData.value = emptyList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun searchSeriesList() {
|
||||||
|
if (!_isLoading.value!! && !isSearchSeriesLast) {
|
||||||
|
_isLoading.value = true
|
||||||
|
compositeDisposable.add(
|
||||||
|
repository.searchSeriesList(
|
||||||
|
keyword = keyword,
|
||||||
|
page = searchSeriesPage,
|
||||||
|
size = size,
|
||||||
|
token = "Bearer ${SharedPreferenceManager.token}"
|
||||||
|
)
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(
|
||||||
|
{
|
||||||
|
_isLoading.value = false
|
||||||
|
if (it.success && it.data != null) {
|
||||||
|
searchSeriesPage += 1
|
||||||
|
|
||||||
|
val data = it.data
|
||||||
|
_searchSeriesLiveData.value = data.items
|
||||||
|
|
||||||
|
if (data.items.isEmpty()) {
|
||||||
|
isSearchSeriesLast = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (it.message != null) {
|
||||||
|
_toastLiveData.value = it.message
|
||||||
|
} else {
|
||||||
|
_toastLiveData
|
||||||
|
.value = "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_isLoading.value = false
|
||||||
|
it.message?.let { message -> Logger.e(message) }
|
||||||
|
_toastLiveData
|
||||||
|
.value = "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
_searchSeriesLiveData.value = emptyList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,219 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="@color/black"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="13.3dp"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:paddingTop="13.3dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/iv_back"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:contentDescription="@null"
|
||||||
|
android:padding="13.3dp"
|
||||||
|
android:src="@drawable/ic_back" />
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="50dp"
|
||||||
|
android:background="@drawable/bg_round_corner_6_7_222222_bbbbbb">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_marginStart="21.3dp"
|
||||||
|
android:contentDescription="@null"
|
||||||
|
android:src="@drawable/ic_title_search_black" />
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/et_search"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:background="@null"
|
||||||
|
android:fontFamily="@font/gmarket_sans_medium"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:hint="검색"
|
||||||
|
android:importantForAutofill="no"
|
||||||
|
android:inputType="textWebEditText"
|
||||||
|
android:paddingHorizontal="54.67dp"
|
||||||
|
android:textColor="@color/color_eeeeee"
|
||||||
|
android:textColorHint="@color/color_555555"
|
||||||
|
android:textCursorDrawable="@drawable/edit_text_cursor"
|
||||||
|
android:textSize="13.3sp" />
|
||||||
|
</RelativeLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<com.google.android.material.tabs.TabLayout
|
||||||
|
android:id="@+id/tabs"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="13.3dp"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:elevation="0dp"
|
||||||
|
android:paddingHorizontal="13.3dp"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:tabGravity="start"
|
||||||
|
app:tabIndicatorFullWidth="false"
|
||||||
|
app:tabIndicatorHeight="0dp"
|
||||||
|
app:tabMinWidth="45dp"
|
||||||
|
app:tabMode="scrollable"
|
||||||
|
app:tabPaddingBottom="15dp"
|
||||||
|
app:tabPaddingTop="15dp"
|
||||||
|
app:tabSelectedTextColor="@color/color_3bb9f1"
|
||||||
|
app:tabTextAppearance="@style/ContentMainTabText"
|
||||||
|
app:tabTextColor="@color/color_bbbbbb" />
|
||||||
|
|
||||||
|
<androidx.core.widget.NestedScrollView
|
||||||
|
android:id="@+id/ns_search_unified"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:visibility="gone">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_creator_title"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="13.3dp"
|
||||||
|
android:fontFamily="@font/gmarket_sans_bold"
|
||||||
|
android:text="채널"
|
||||||
|
android:textColor="@color/color_eeeeee"
|
||||||
|
android:textSize="16sp" />
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/rv_unified_creator"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:padding="13.3dp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_more_creator"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="13.3dp"
|
||||||
|
android:layout_marginBottom="30dp"
|
||||||
|
android:background="@color/color_cc333333"
|
||||||
|
android:fontFamily="@font/gmarket_sans_medium"
|
||||||
|
android:gravity="center"
|
||||||
|
android:paddingVertical="10dp"
|
||||||
|
android:text="더보기 >"
|
||||||
|
android:textColor="@color/color_777777"
|
||||||
|
android:textSize="13.3sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_content_title"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="13.3dp"
|
||||||
|
android:fontFamily="@font/gmarket_sans_bold"
|
||||||
|
android:text="콘텐츠"
|
||||||
|
android:textColor="@color/color_eeeeee"
|
||||||
|
android:textSize="16sp" />
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/rv_unified_content"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:padding="13.3dp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_more_content"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="13.3dp"
|
||||||
|
android:layout_marginBottom="30dp"
|
||||||
|
android:background="@color/color_cc333333"
|
||||||
|
android:fontFamily="@font/gmarket_sans_medium"
|
||||||
|
android:gravity="center"
|
||||||
|
android:paddingVertical="10dp"
|
||||||
|
android:text="더보기 >"
|
||||||
|
android:textColor="@color/color_777777"
|
||||||
|
android:textSize="13.3sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_series_title"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="13.3dp"
|
||||||
|
android:fontFamily="@font/gmarket_sans_bold"
|
||||||
|
android:text="시리즈"
|
||||||
|
android:textColor="@color/color_eeeeee"
|
||||||
|
android:textSize="16sp" />
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/rv_unified_series"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:padding="13.3dp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_more_series"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="13.3dp"
|
||||||
|
android:layout_marginBottom="13.3dp"
|
||||||
|
android:background="@color/color_cc333333"
|
||||||
|
android:fontFamily="@font/gmarket_sans_medium"
|
||||||
|
android:gravity="center"
|
||||||
|
android:paddingVertical="10dp"
|
||||||
|
android:text="더보기 >"
|
||||||
|
android:textColor="@color/color_777777"
|
||||||
|
android:textSize="13.3sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
</androidx.core.widget.NestedScrollView>
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/rv_creator"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:padding="13.3dp"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/rv_content"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:padding="13.3dp"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/rv_series"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
android:padding="13.3dp"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_result_x"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginVertical="40dp"
|
||||||
|
android:fontFamily="@font/gmarket_sans_medium"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="검색 결과가 없습니다."
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="18.3sp"
|
||||||
|
android:visibility="gone" />
|
||||||
|
</LinearLayout>
|
|
@ -0,0 +1,57 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@color/black"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/iv_profile"
|
||||||
|
android:layout_width="60dp"
|
||||||
|
android:layout_height="60dp"
|
||||||
|
android:contentDescription="@null"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/iv_content"
|
||||||
|
android:layout_width="60dp"
|
||||||
|
android:layout_height="60dp"
|
||||||
|
android:contentDescription="@null"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/iv_series"
|
||||||
|
android:layout_width="60dp"
|
||||||
|
android:layout_height="85dp"
|
||||||
|
android:contentDescription="@null"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="13.3dp"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_title"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:fontFamily="@font/gmarket_sans_medium"
|
||||||
|
android:textColor="@color/color_eeeeee"
|
||||||
|
android:textSize="13.3sp"
|
||||||
|
tools:text="slefjeiwok" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_nickname"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="6.7dp"
|
||||||
|
android:fontFamily="@font/gmarket_sans_medium"
|
||||||
|
android:textColor="@color/color_777777"
|
||||||
|
android:textSize="10sp"
|
||||||
|
tools:ignore="SmallSp"
|
||||||
|
tools:text="slefjeiwok" />
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
Loading…
Reference in New Issue