diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 4036e32..c2f6af4 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -40,6 +40,7 @@
+
Unit
+) : RecyclerView.Adapter() {
+
+ private val items = mutableListOf()
+
+ 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) {
+ this.items.addAll(items)
+ notifyDataSetChanged()
+ }
+}
diff --git a/app/src/main/java/kr/co/vividnext/sodalive/explorer/ExplorerApi.kt b/app/src/main/java/kr/co/vividnext/sodalive/explorer/ExplorerApi.kt
new file mode 100644
index 0000000..5b0ed86
--- /dev/null
+++ b/app/src/main/java/kr/co/vividnext/sodalive/explorer/ExplorerApi.kt
@@ -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>
+
+ @GET("/explorer/search/channel")
+ fun searchChannel(
+ @Query("channel") channel: String,
+ @Header("Authorization") authHeader: String
+ ): Single>>
+}
diff --git a/app/src/main/java/kr/co/vividnext/sodalive/explorer/ExplorerFragment.kt b/app/src/main/java/kr/co/vividnext/sodalive/explorer/ExplorerFragment.kt
index 9500a0a..0aa26ac 100644
--- a/app/src/main/java/kr/co/vividnext/sodalive/explorer/ExplorerFragment.kt
+++ b/app/src/main/java/kr/co/vividnext/sodalive/explorer/ExplorerFragment.kt
@@ -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::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()
+ }
+ }
}
diff --git a/app/src/main/java/kr/co/vividnext/sodalive/explorer/ExplorerRepository.kt b/app/src/main/java/kr/co/vividnext/sodalive/explorer/ExplorerRepository.kt
new file mode 100644
index 0000000..20e3984
--- /dev/null
+++ b/app/src/main/java/kr/co/vividnext/sodalive/explorer/ExplorerRepository.kt
@@ -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
+ )
+}
diff --git a/app/src/main/java/kr/co/vividnext/sodalive/explorer/ExplorerSectionAdapter.kt b/app/src/main/java/kr/co/vividnext/sodalive/explorer/ExplorerSectionAdapter.kt
new file mode 100644
index 0000000..db41f8d
--- /dev/null
+++ b/app/src/main/java/kr/co/vividnext/sodalive/explorer/ExplorerSectionAdapter.kt
@@ -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() {
+
+ private val items = mutableListOf()
+
+ 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) {
+ this.items.addAll(items)
+ notifyDataSetChanged()
+ }
+}
diff --git a/app/src/main/java/kr/co/vividnext/sodalive/explorer/ExplorerViewModel.kt b/app/src/main/java/kr/co/vividnext/sodalive/explorer/ExplorerViewModel.kt
new file mode 100644
index 0000000..92efee0
--- /dev/null
+++ b/app/src/main/java/kr/co/vividnext/sodalive/explorer/ExplorerViewModel.kt
@@ -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()
+ val responseLiveData: LiveData
+ get() = _responseLiveData
+
+ private val _searchChannelLiveData = MutableLiveData>()
+ val searchChannelLiveData: LiveData>
+ get() = _searchChannelLiveData
+
+ private val _toastLiveData = MutableLiveData()
+ val toastLiveData: LiveData
+ get() = _toastLiveData
+
+ private var _isLoading = MutableLiveData(false)
+ val isLoading: LiveData
+ 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("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
+ }
+ )
+ )
+ }
+}
diff --git a/app/src/main/java/kr/co/vividnext/sodalive/explorer/GetExplorerResponse.kt b/app/src/main/java/kr/co/vividnext/sodalive/explorer/GetExplorerResponse.kt
new file mode 100644
index 0000000..e7f32f2
--- /dev/null
+++ b/app/src/main/java/kr/co/vividnext/sodalive/explorer/GetExplorerResponse.kt
@@ -0,0 +1,21 @@
+package kr.co.vividnext.sodalive.explorer
+
+import com.google.gson.annotations.SerializedName
+
+data class GetExplorerResponse(
+ @SerializedName("sections") val sections: List
+)
+
+data class GetExplorerSectionResponse(
+ @SerializedName("title") val title: String,
+ @SerializedName("coloredTitle") val coloredTitle: String?,
+ @SerializedName("color") val color: String?,
+ @SerializedName("creators") val creators: List
+)
+
+data class GetExplorerSectionCreatorResponse(
+ @SerializedName("id") val id: Long,
+ @SerializedName("nickname") val nickname: String,
+ @SerializedName("tags") val tags: String,
+ @SerializedName("profileImageUrl") val profileImageUrl: String
+)
diff --git a/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/UserProfileActivity.kt b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/UserProfileActivity.kt
new file mode 100644
index 0000000..6719175
--- /dev/null
+++ b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/UserProfileActivity.kt
@@ -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::inflate
+) {
+ override fun setupView() {
+ }
+}
diff --git a/app/src/main/java/kr/co/vividnext/sodalive/message/MessageSelectRecipientAdapter.kt b/app/src/main/java/kr/co/vividnext/sodalive/message/MessageSelectRecipientAdapter.kt
new file mode 100644
index 0000000..4971ec6
--- /dev/null
+++ b/app/src/main/java/kr/co/vividnext/sodalive/message/MessageSelectRecipientAdapter.kt
@@ -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() {
+ 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 = 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
+}
diff --git a/app/src/main/res/drawable-xxhdpi/ic_title_search_black.png b/app/src/main/res/drawable-xxhdpi/ic_title_search_black.png
new file mode 100644
index 0000000..3f5220a
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_title_search_black.png differ
diff --git a/app/src/main/res/drawable/bg_round_corner_6_7_222222_bbbbbb.xml b/app/src/main/res/drawable/bg_round_corner_6_7_222222_bbbbbb.xml
new file mode 100644
index 0000000..ff03afb
--- /dev/null
+++ b/app/src/main/res/drawable/bg_round_corner_6_7_222222_bbbbbb.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_user_profile.xml b/app/src/main/res/layout/activity_user_profile.xml
new file mode 100644
index 0000000..1354408
--- /dev/null
+++ b/app/src/main/res/layout/activity_user_profile.xml
@@ -0,0 +1,6 @@
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_explorer.xml b/app/src/main/res/layout/fragment_explorer.xml
index 0241f98..c56da6c 100644
--- a/app/src/main/res/layout/fragment_explorer.xml
+++ b/app/src/main/res/layout/fragment_explorer.xml
@@ -1,16 +1,96 @@
-
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/item_explorer.xml b/app/src/main/res/layout/item_explorer.xml
new file mode 100644
index 0000000..d8fc6e4
--- /dev/null
+++ b/app/src/main/res/layout/item_explorer.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/item_explorer_section.xml b/app/src/main/res/layout/item_explorer_section.xml
new file mode 100644
index 0000000..c70f1eb
--- /dev/null
+++ b/app/src/main/res/layout/item_explorer_section.xml
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/item_select_recipient.xml b/app/src/main/res/layout/item_select_recipient.xml
new file mode 100644
index 0000000..0b1b35b
--- /dev/null
+++ b/app/src/main/res/layout/item_select_recipient.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+