탐색 메인 페이지 추가

This commit is contained in:
klaus 2023-08-01 10:29:49 +09:00
parent c2618669c8
commit 662ef64696
18 changed files with 790 additions and 10 deletions

View File

@ -40,6 +40,7 @@
<activity android:name=".live.room.update.LiveRoomEditActivity" /> <activity android:name=".live.room.update.LiveRoomEditActivity" />
<activity android:name=".live.reservation.complete.LiveReservationCompleteActivity" /> <activity android:name=".live.reservation.complete.LiveReservationCompleteActivity" />
<activity android:name=".live.room.LiveRoomActivity" /> <activity android:name=".live.room.LiveRoomActivity" />
<activity android:name=".explorer.profile.UserProfileActivity" />
<activity <activity
android:name="com.google.android.gms.oss.licenses.OssLicensesMenuActivity" android:name="com.google.android.gms.oss.licenses.OssLicensesMenuActivity"

View File

@ -4,6 +4,9 @@ import android.content.Context
import com.google.gson.GsonBuilder import com.google.gson.GsonBuilder
import kr.co.vividnext.sodalive.BuildConfig import kr.co.vividnext.sodalive.BuildConfig
import kr.co.vividnext.sodalive.common.ApiBuilder import kr.co.vividnext.sodalive.common.ApiBuilder
import kr.co.vividnext.sodalive.explorer.ExplorerApi
import kr.co.vividnext.sodalive.explorer.ExplorerRepository
import kr.co.vividnext.sodalive.explorer.ExplorerViewModel
import kr.co.vividnext.sodalive.live.LiveApi import kr.co.vividnext.sodalive.live.LiveApi
import kr.co.vividnext.sodalive.live.LiveRepository import kr.co.vividnext.sodalive.live.LiveRepository
import kr.co.vividnext.sodalive.live.LiveViewModel import kr.co.vividnext.sodalive.live.LiveViewModel
@ -88,6 +91,7 @@ class AppDI(private val context: Context, isDebugMode: Boolean) {
single { ApiBuilder().build(get(), EventApi::class.java) } single { ApiBuilder().build(get(), EventApi::class.java) }
single { ApiBuilder().build(get(), ReportApi::class.java) } single { ApiBuilder().build(get(), ReportApi::class.java) }
single { ApiBuilder().build(get(), LiveRecommendApi::class.java) } single { ApiBuilder().build(get(), LiveRecommendApi::class.java) }
single { ApiBuilder().build(get(), ExplorerApi::class.java) }
} }
private val viewModelModule = module { private val viewModelModule = module {
@ -107,6 +111,7 @@ class AppDI(private val context: Context, isDebugMode: Boolean) {
viewModel { LiveRoomEditViewModel(get()) } viewModel { LiveRoomEditViewModel(get()) }
viewModel { LiveRoomViewModel(get(), get(), get()) } viewModel { LiveRoomViewModel(get(), get(), get()) }
viewModel { LiveRoomDonationMessageViewModel(get()) } viewModel { LiveRoomDonationMessageViewModel(get()) }
viewModel { ExplorerViewModel(get()) }
} }
private val repositoryModule = module { private val repositoryModule = module {
@ -119,6 +124,7 @@ class AppDI(private val context: Context, isDebugMode: Boolean) {
factory { CanRepository(get()) } factory { CanRepository(get()) }
factory { LiveTagRepository(get()) } factory { LiveTagRepository(get()) }
factory { ReportRepository(get()) } factory { ReportRepository(get()) }
factory { ExplorerRepository(get()) }
} }
private val moduleList = listOf( private val moduleList = listOf(

View File

@ -0,0 +1,121 @@
package kr.co.vividnext.sodalive.explorer
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Color
import android.graphics.Rect
import android.text.SpannableString
import android.text.Spanned
import android.text.style.ForegroundColorSpan
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import kr.co.vividnext.sodalive.databinding.ItemExplorerBinding
import kr.co.vividnext.sodalive.extensions.dpToPx
class ExplorerAdapter(
private val onClickItem: (Long) -> Unit
) : RecyclerView.Adapter<ExplorerAdapter.ViewHolder>() {
private val items = mutableListOf<GetExplorerSectionResponse>()
inner class ViewHolder(
private val context: Context,
private val binding: ItemExplorerBinding
) : RecyclerView.ViewHolder(binding.root) {
fun bind(item: GetExplorerSectionResponse) {
setTitle(item)
setCreatorList(item)
}
private fun setTitle(item: GetExplorerSectionResponse) {
binding.tvTitle.text = if (
!item.coloredTitle.isNullOrBlank() &&
!item.color.isNullOrBlank()
) {
val spStr = SpannableString(item.title)
try {
spStr.setSpan(
ForegroundColorSpan(
Color.parseColor("#${item.color}")
),
item.title.indexOf(item.coloredTitle),
item.title.indexOf(item.coloredTitle) + item.coloredTitle.length,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
)
spStr
} catch (e: IllegalArgumentException) {
item.title
}
} else {
item.title
}
}
private fun setCreatorList(item: GetExplorerSectionResponse) {
val adapter = ExplorerSectionAdapter(onClickItem = onClickItem)
binding.rvExplorerSection.layoutManager = LinearLayoutManager(
context,
LinearLayoutManager.HORIZONTAL,
false
)
binding.rvExplorerSection.addItemDecoration(object : RecyclerView.ItemDecoration() {
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State
) {
super.getItemOffsets(outRect, view, parent, state)
when (parent.getChildAdapterPosition(view)) {
0 -> {
outRect.left = 0
outRect.right = 6.7f.dpToPx().toInt()
}
adapter.itemCount - 1 -> {
outRect.left = 6.7f.dpToPx().toInt()
outRect.right = 0
}
else -> {
outRect.left = 6.7f.dpToPx().toInt()
outRect.right = 6.7f.dpToPx().toInt()
}
}
}
})
binding.rvExplorerSection.adapter = adapter
adapter.addItems(item.creators)
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder(
parent.context,
ItemExplorerBinding.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 addItems(items: List<GetExplorerSectionResponse>) {
this.items.addAll(items)
notifyDataSetChanged()
}
}

View File

@ -0,0 +1,21 @@
package kr.co.vividnext.sodalive.explorer
import io.reactivex.rxjava3.core.Single
import kr.co.vividnext.sodalive.common.ApiResponse
import kr.co.vividnext.sodalive.live.room.detail.GetRoomDetailUser
import retrofit2.http.GET
import retrofit2.http.Header
import retrofit2.http.Query
interface ExplorerApi {
@GET("/explorer")
fun getExplorer(
@Header("Authorization") authHeader: String
): Single<ApiResponse<GetExplorerResponse>>
@GET("/explorer/search/channel")
fun searchChannel(
@Query("channel") channel: String,
@Header("Authorization") authHeader: String
): Single<ApiResponse<List<GetRoomDetailUser>>>
}

View File

@ -1,9 +1,202 @@
package kr.co.vividnext.sodalive.explorer package kr.co.vividnext.sodalive.explorer
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.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.jakewharton.rxbinding4.widget.textChanges
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.schedulers.Schedulers
import kr.co.vividnext.sodalive.base.BaseFragment import kr.co.vividnext.sodalive.base.BaseFragment
import kr.co.vividnext.sodalive.common.Constants
import kr.co.vividnext.sodalive.common.LoadingDialog
import kr.co.vividnext.sodalive.databinding.FragmentExplorerBinding import kr.co.vividnext.sodalive.databinding.FragmentExplorerBinding
import kr.co.vividnext.sodalive.explorer.profile.UserProfileActivity
import kr.co.vividnext.sodalive.extensions.dpToPx
import kr.co.vividnext.sodalive.message.MessageSelectRecipientAdapter
import org.koin.android.ext.android.inject
import java.util.concurrent.TimeUnit
class ExplorerFragment : BaseFragment<FragmentExplorerBinding>( class ExplorerFragment : BaseFragment<FragmentExplorerBinding>(
FragmentExplorerBinding::inflate FragmentExplorerBinding::inflate
) { ) {
private val viewModel: ExplorerViewModel by inject()
private lateinit var loadingDialog: LoadingDialog
private lateinit var adapter: ExplorerAdapter
private lateinit var imm: InputMethodManager
private val handler = Handler(Looper.getMainLooper())
private lateinit var searchChannelAdapter: MessageSelectRecipientAdapter
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
loadingDialog = LoadingDialog(requireActivity(), layoutInflater)
imm = requireContext().getSystemService(
Service.INPUT_METHOD_SERVICE
) as InputMethodManager
setupView()
bindData()
viewModel.getExplorer()
}
private fun hideKeyboard() {
handler.postDelayed({
imm.hideSoftInputFromWindow(
requireActivity().window.decorView.applicationWindowToken,
InputMethodManager.HIDE_NOT_ALWAYS
)
}, 100)
}
private fun setupView() {
adapter = ExplorerAdapter {
val intent = Intent(requireContext(), UserProfileActivity::class.java)
intent.putExtra(Constants.EXTRA_USER_ID, it)
startActivity(intent)
}
binding.rvExplorer.layoutManager = LinearLayoutManager(
context,
LinearLayoutManager.VERTICAL,
false
)
binding.rvExplorer.addItemDecoration(object : RecyclerView.ItemDecoration() {
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State
) {
super.getItemOffsets(outRect, view, parent, state)
when (parent.getChildAdapterPosition(view)) {
0 -> {
outRect.top = 0
outRect.bottom = 30f.dpToPx().toInt()
}
adapter.itemCount - 1 -> {
outRect.top = 30f.dpToPx().toInt()
outRect.bottom = 0
}
else -> {
outRect.top = 30f.dpToPx().toInt()
outRect.bottom = 30f.dpToPx().toInt()
}
}
}
})
binding.rvExplorer.adapter = adapter
setupSearchChannelView()
}
private fun setupSearchChannelView() {
searchChannelAdapter = MessageSelectRecipientAdapter {
hideKeyboard()
val intent = Intent(requireContext(), UserProfileActivity::class.java)
intent.putExtra(Constants.EXTRA_USER_ID, it.id)
startActivity(intent)
}
binding.rvSearchChannel.layoutManager = LinearLayoutManager(
context,
LinearLayoutManager.VERTICAL,
false
)
binding.rvSearchChannel.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.rvSearchChannel.adapter = searchChannelAdapter
compositeDisposable.add(
binding.etSearchChannel.textChanges().skip(1)
.debounce(500, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe {
binding.ivX.visibility = if (it.length > 1) {
View.VISIBLE
} else {
View.GONE
}
if (it.length >= 2) {
viewModel.searchChannel(it.toString())
binding.rvSearchChannel.visibility = View.VISIBLE
binding.rvExplorer.visibility = View.GONE
} else {
binding.rvSearchChannel.visibility = View.GONE
binding.rvExplorer.visibility = View.VISIBLE
}
binding.tvResultX.visibility = View.GONE
}
)
binding.ivX.setOnClickListener {
hideKeyboard()
binding.etSearchChannel.setText("")
}
}
@SuppressLint("NotifyDataSetChanged")
private fun bindData() {
viewModel.isLoading.observe(viewLifecycleOwner) {
if (it) {
loadingDialog.show(screenWidth)
} else {
loadingDialog.dismiss()
}
}
viewModel.toastLiveData.observe(viewLifecycleOwner) {
it?.let { Toast.makeText(requireContext(), it, Toast.LENGTH_LONG).show() }
}
viewModel.responseLiveData.observe(viewLifecycleOwner) {
adapter.addItems(it.sections)
}
viewModel.searchChannelLiveData.observe(viewLifecycleOwner) {
searchChannelAdapter.items.clear()
if (it.isNotEmpty()) {
searchChannelAdapter.items.addAll(it)
binding.rvSearchChannel.visibility = View.VISIBLE
binding.tvResultX.visibility = View.GONE
} else {
binding.rvSearchChannel.visibility = View.GONE
binding.tvResultX.visibility = View.VISIBLE
}
searchChannelAdapter.notifyDataSetChanged()
}
}
} }

View File

@ -0,0 +1,12 @@
package kr.co.vividnext.sodalive.explorer
class ExplorerRepository(
private val api: ExplorerApi
) {
fun getExplorer(token: String) = api.getExplorer(authHeader = token)
fun searchChannel(channel: String, token: String) = api.searchChannel(
channel = channel,
authHeader = token
)
}

View File

@ -0,0 +1,54 @@
package kr.co.vividnext.sodalive.explorer
import android.annotation.SuppressLint
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import coil.load
import coil.transform.CircleCropTransformation
import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.databinding.ItemExplorerSectionBinding
class ExplorerSectionAdapter(
private val onClickItem: (Long) -> Unit
) : RecyclerView.Adapter<ExplorerSectionAdapter.ViewHolder>() {
private val items = mutableListOf<GetExplorerSectionCreatorResponse>()
inner class ViewHolder(
private val binding: ItemExplorerSectionBinding
) : RecyclerView.ViewHolder(binding.root) {
fun bind(item: GetExplorerSectionCreatorResponse) {
binding.tvNickname.text = item.nickname
binding.tvTags.text = item.tags
binding.ivProfile.load(item.profileImageUrl) {
transformations(CircleCropTransformation())
placeholder(R.drawable.bg_placeholder)
crossfade(true)
}
binding.root.setOnClickListener { onClickItem(item.id) }
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder(
ItemExplorerSectionBinding.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 addItems(items: List<GetExplorerSectionCreatorResponse>) {
this.items.addAll(items)
notifyDataSetChanged()
}
}

View File

@ -0,0 +1,91 @@
package kr.co.vividnext.sodalive.explorer
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
import kr.co.vividnext.sodalive.live.room.detail.GetRoomDetailUser
class ExplorerViewModel(private val repository: ExplorerRepository) : BaseViewModel() {
private val _responseLiveData = MutableLiveData<GetExplorerResponse>()
val responseLiveData: LiveData<GetExplorerResponse>
get() = _responseLiveData
private val _searchChannelLiveData = MutableLiveData<List<GetRoomDetailUser>>()
val searchChannelLiveData: LiveData<List<GetRoomDetailUser>>
get() = _searchChannelLiveData
private val _toastLiveData = MutableLiveData<String?>()
val toastLiveData: LiveData<String?>
get() = _toastLiveData
private var _isLoading = MutableLiveData(false)
val isLoading: LiveData<Boolean>
get() = _isLoading
fun searchChannel(channel: String) {
compositeDisposable.add(
repository.searchChannel(
channel = channel,
token = "Bearer ${SharedPreferenceManager.token}"
).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{
if (it.success && it.data != null) {
_searchChannelLiveData.value = it.data!!
} else {
if (it.message != null) {
_toastLiveData.postValue(it.message)
} else {
_toastLiveData.postValue(
"알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
)
}
}
},
{
it.message?.let { message -> Logger.e(message) }
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
}
)
)
}
fun getExplorer() {
if (!_isLoading.value!!) {
_isLoading.value = true
}
compositeDisposable.add(
repository.getExplorer(token = "Bearer ${SharedPreferenceManager.token}")
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{
if (it.success && it.data != null) {
_responseLiveData.value = it.data!!
} else {
if (it.message != null) {
_toastLiveData.postValue(it.message)
} else {
_toastLiveData.postValue(
"알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
)
}
}
_isLoading.value = false
},
{
_isLoading.value = false
it.message?.let { message -> Logger.e(message) }
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
}
)
)
}
}

View File

@ -0,0 +1,21 @@
package kr.co.vividnext.sodalive.explorer
import com.google.gson.annotations.SerializedName
data class GetExplorerResponse(
@SerializedName("sections") val sections: List<GetExplorerSectionResponse>
)
data class GetExplorerSectionResponse(
@SerializedName("title") val title: String,
@SerializedName("coloredTitle") val coloredTitle: String?,
@SerializedName("color") val color: String?,
@SerializedName("creators") val creators: List<GetExplorerSectionCreatorResponse>
)
data class GetExplorerSectionCreatorResponse(
@SerializedName("id") val id: Long,
@SerializedName("nickname") val nickname: String,
@SerializedName("tags") val tags: String,
@SerializedName("profileImageUrl") val profileImageUrl: String
)

View File

@ -0,0 +1,11 @@
package kr.co.vividnext.sodalive.explorer.profile
import kr.co.vividnext.sodalive.base.BaseActivity
import kr.co.vividnext.sodalive.databinding.ActivityUserProfileBinding
class UserProfileActivity: BaseActivity<ActivityUserProfileBinding>(
ActivityUserProfileBinding::inflate
) {
override fun setupView() {
}
}

View File

@ -0,0 +1,48 @@
package kr.co.vividnext.sodalive.message
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import coil.load
import coil.transform.RoundedCornersTransformation
import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.databinding.ItemSelectRecipientBinding
import kr.co.vividnext.sodalive.extensions.dpToPx
import kr.co.vividnext.sodalive.live.room.detail.GetRoomDetailUser
class MessageSelectRecipientAdapter(
private val onClickItem: (GetRoomDetailUser) -> Unit
) : RecyclerView.Adapter<MessageSelectRecipientAdapter.ViewHolder>() {
inner class ViewHolder(
private val binding: ItemSelectRecipientBinding
) : RecyclerView.ViewHolder(binding.root) {
fun bind(item: GetRoomDetailUser) {
binding.ivProfile.load(item.profileImageUrl) {
crossfade(true)
placeholder(R.drawable.bg_placeholder)
transformations(RoundedCornersTransformation(23.4f.dpToPx()))
}
binding.tvNickname.text = item.nickname
binding.root.setOnClickListener { onClickItem(item) }
}
}
val items: MutableList<GetRoomDetailUser> = mutableListOf()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder(
ItemSelectRecipientBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(items[position])
}
override fun getItemCount() = items.size
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/color_222222" />
<corners android:radius="6.7dp" />
<stroke
android:width="1.3dp"
android:color="@color/color_bbbbbb" />
</shape>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,16 +1,96 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<TextView <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="match_parent">
app:layout_constraintBottom_toBottomOf="parent"
<RelativeLayout
android:id="@+id/fl_search_channel"
android:layout_width="0dp"
android:layout_height="50dp"
android:layout_marginHorizontal="13.3dp"
android:layout_marginTop="20dp"
android:background="@drawable/bg_round_corner_6_7_222222_bbbbbb"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
android:text="탐색" app:layout_constraintTop_toTopOf="parent">
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout> <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_channel"
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_eeeeee"
android:textCursorDrawable="@drawable/edit_text_cursor"
android:textSize="13.3sp"
tools:ignore="LabelFor" />
<ImageView
android:id="@+id/iv_x"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:layout_gravity="center_vertical"
android:layout_marginEnd="21.3dp"
android:contentDescription="@null"
android:src="@drawable/ic_close_white" />
</RelativeLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_explorer"
android:layout_width="0dp"
android:layout_height="match_parent"
android:clipToPadding="false"
android:nestedScrollingEnabled="false"
android:paddingVertical="40dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/fl_search_channel" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_search_channel"
android:layout_width="0dp"
android:layout_height="match_parent"
android:clipToPadding="false"
android:nestedScrollingEnabled="false"
android:paddingVertical="40dp"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/fl_search_channel" />
<TextView
android:id="@+id/tv_result_x"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginVertical="40dp"
android:text="검색 결과가 없습니다."
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/fl_search_channel" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView>

View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/tv_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:fontFamily="@font/gmarket_sans_bold"
android:paddingHorizontal="13.3dp"
android:textColor="@color/color_eeeeee"
android:textSize="18.3sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="인기 급상승" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_explorer_section"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="13.3dp"
android:clipToPadding="false"
android:paddingHorizontal="13.3dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_title" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="93.3dp"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/iv_profile"
android:layout_width="0dp"
android:layout_height="93.3dp"
android:contentDescription="@null"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_nickname"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="13.3dp"
android:ellipsize="end"
android:fontFamily="@font/gmarket_sans_medium"
android:gravity="center"
android:maxLines="1"
android:textColor="@color/color_bbbbbb"
android:textSize="11.3sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/iv_profile"
tools:text="상남자12039" />
<TextView
android:id="@+id/tv_tags"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="3.3dp"
android:ellipsize="end"
android:fontFamily="@font/gmarket_sans_medium"
android:gravity="center"
android:maxLines="1"
android:textColor="@color/color_9970ff"
android:textSize="10sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_nickname"
tools:text="#연애 #일상 #훈련"
tools:ignore="SmallSp" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,25 @@
<?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="46.7dp"
android:layout_height="46.7dp"
android:contentDescription="@null" />
<TextView
android:id="@+id/tv_nickname"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="13.3dp"
android:fontFamily="@font/gmarket_sans_medium"
android:textColor="@color/color_eeeeee"
android:textSize="13.3sp"
tools:text="slefjeiwok" />
</LinearLayout>