From 662ef6469652e39c2f7939e34ea8a6ab5b6a4452 Mon Sep 17 00:00:00 2001 From: klaus Date: Tue, 1 Aug 2023 10:29:49 +0900 Subject: [PATCH] =?UTF-8?q?=ED=83=90=EC=83=89=20=EB=A9=94=EC=9D=B8=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/AndroidManifest.xml | 1 + .../java/kr/co/vividnext/sodalive/di/AppDI.kt | 6 + .../sodalive/explorer/ExplorerAdapter.kt | 121 +++++++++++ .../sodalive/explorer/ExplorerApi.kt | 21 ++ .../sodalive/explorer/ExplorerFragment.kt | 193 ++++++++++++++++++ .../sodalive/explorer/ExplorerRepository.kt | 12 ++ .../explorer/ExplorerSectionAdapter.kt | 54 +++++ .../sodalive/explorer/ExplorerViewModel.kt | 91 +++++++++ .../sodalive/explorer/GetExplorerResponse.kt | 21 ++ .../explorer/profile/UserProfileActivity.kt | 11 + .../message/MessageSelectRecipientAdapter.kt | 48 +++++ .../drawable-xxhdpi/ic_title_search_black.png | Bin 0 -> 1134 bytes .../bg_round_corner_6_7_222222_bbbbbb.xml | 8 + .../main/res/layout/activity_user_profile.xml | 6 + app/src/main/res/layout/fragment_explorer.xml | 100 ++++++++- app/src/main/res/layout/item_explorer.xml | 32 +++ .../main/res/layout/item_explorer_section.xml | 50 +++++ .../main/res/layout/item_select_recipient.xml | 25 +++ 18 files changed, 790 insertions(+), 10 deletions(-) create mode 100644 app/src/main/java/kr/co/vividnext/sodalive/explorer/ExplorerAdapter.kt create mode 100644 app/src/main/java/kr/co/vividnext/sodalive/explorer/ExplorerApi.kt create mode 100644 app/src/main/java/kr/co/vividnext/sodalive/explorer/ExplorerRepository.kt create mode 100644 app/src/main/java/kr/co/vividnext/sodalive/explorer/ExplorerSectionAdapter.kt create mode 100644 app/src/main/java/kr/co/vividnext/sodalive/explorer/ExplorerViewModel.kt create mode 100644 app/src/main/java/kr/co/vividnext/sodalive/explorer/GetExplorerResponse.kt create mode 100644 app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/UserProfileActivity.kt create mode 100644 app/src/main/java/kr/co/vividnext/sodalive/message/MessageSelectRecipientAdapter.kt create mode 100644 app/src/main/res/drawable-xxhdpi/ic_title_search_black.png create mode 100644 app/src/main/res/drawable/bg_round_corner_6_7_222222_bbbbbb.xml create mode 100644 app/src/main/res/layout/activity_user_profile.xml create mode 100644 app/src/main/res/layout/item_explorer.xml create mode 100644 app/src/main/res/layout/item_explorer_section.xml create mode 100644 app/src/main/res/layout/item_select_recipient.xml 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 0000000000000000000000000000000000000000..3f5220a128db4e33b9d719ed6f31c6c4c2f87f6d GIT binary patch literal 1134 zcmV-!1d;oRP)t_MK)V2h1t?6AP5^xjK$xIm0pctGZ3iGW3(&Gaj=Sni{UAC? z{#5#V@1FZAThiaB&y$ltiy-2NE{9|Xa%JQHrIp)m$qKn%lP$@vk|Zf$8(@)Sr(_w~ z4c6z9Y)Y0wjEK-_LySuxNbl* z^3KV|5KcKF7xMS0Vuuh;PWG&`5uM*Cn$go2?$_ zd#pTrCwQ!+$0(S&0<+!R#26G<@PU$k4Us{6VZN}~E6XjZ^7x%#+YPsohfb}|KxNIq zf^DOk?G45N_v4>RdTLA1JRCc>O4_b1VMlqWfbC_^mY@mVW~}Q67Hn%8Da4JoqWW!1 z*k-}IN}9EQvta9dhE3x|-NBRb6D7T{C9q&^cyB-c+YsJB2)4Qvw0@}NVdfa(q&#=t zgt|Ip!Am9mMdgjg0ypPVOBz>p2`*4SVHiUs$bOi&hq zIT&x}P#jIuUZI-|jTagTuLvyz?XFf<72Hl2qf8DZLmy^!h2y + + + + + 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 @@ + + + + + + +