탐색 메인 페이지 추가

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.reservation.complete.LiveReservationCompleteActivity" />
<activity android:name=".live.room.LiveRoomActivity" />
<activity android:name=".explorer.profile.UserProfileActivity" />
<activity
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 kr.co.vividnext.sodalive.BuildConfig
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.LiveRepository
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(), ReportApi::class.java) }
single { ApiBuilder().build(get(), LiveRecommendApi::class.java) }
single { ApiBuilder().build(get(), ExplorerApi::class.java) }
}
private val viewModelModule = module {
@ -107,6 +111,7 @@ class AppDI(private val context: Context, isDebugMode: Boolean) {
viewModel { LiveRoomEditViewModel(get()) }
viewModel { LiveRoomViewModel(get(), get(), get()) }
viewModel { LiveRoomDonationMessageViewModel(get()) }
viewModel { ExplorerViewModel(get()) }
}
private val repositoryModule = module {
@ -119,6 +124,7 @@ class AppDI(private val context: Context, isDebugMode: Boolean) {
factory { CanRepository(get()) }
factory { LiveTagRepository(get()) }
factory { ReportRepository(get()) }
factory { ExplorerRepository(get()) }
}
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
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.common.Constants
import kr.co.vividnext.sodalive.common.LoadingDialog
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>(
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"?>
<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:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:text="탐색"
app:layout_constraintTop_toTopOf="parent" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
</androidx.constraintlayout.widget.ConstraintLayout>
<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_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<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>