Bläddra i källkod

feat: 陪玩详情

DoggyZhang 4 månader sedan
förälder
incheckning
6888f246e1
34 ändrade filer med 951 tillägg och 15 borttagningar
  1. 1 0
      app/src/main/java/com/adealink/weparty/module/playmate/Router.kt
  2. 9 4
      app/src/main/java/com/adealink/weparty/module/playmate/widget/PriceView.kt
  3. 5 1
      app/src/main/java/com/adealink/weparty/module/profile/widget/GenderView.kt
  4. 0 0
      app/src/main/res/drawable-xhdpi/common_create_order_button_bg.9.png
  5. BIN
      app/src/main/res/drawable-xhdpi/common_language_ic.png
  6. BIN
      app/src/main/res/drawable-xhdpi/common_location_ic.png
  7. 0 0
      app/src/main/res/drawable/common_create_order_price_bg.xml
  8. 1 0
      app/src/main/res/values/attrs.xml
  9. 1 0
      app/src/main/res/values/strings.xml
  10. 28 0
      module/playmate/src/main/java/com/adealink/weparty/playmate/data/PlaymateData.kt
  11. 5 0
      module/playmate/src/main/java/com/adealink/weparty/playmate/datasource/remote/PlaymateHttpService.kt
  12. 50 0
      module/playmate/src/main/java/com/adealink/weparty/playmate/detail/PlaymateDetailActivity.kt
  13. 109 0
      module/playmate/src/main/java/com/adealink/weparty/playmate/detail/PlaymateDetailFragment.kt
  14. 33 0
      module/playmate/src/main/java/com/adealink/weparty/playmate/detail/adapter/PlaymateLabelViewBinder.kt
  15. 52 0
      module/playmate/src/main/java/com/adealink/weparty/playmate/detail/comp/PlaymateDetailBottomComp.kt
  16. 56 0
      module/playmate/src/main/java/com/adealink/weparty/playmate/detail/comp/PlaymateDetailHeaderComp.kt
  17. 106 0
      module/playmate/src/main/java/com/adealink/weparty/playmate/detail/comp/PlaymateToolBarViewComp.kt
  18. 11 0
      module/playmate/src/main/java/com/adealink/weparty/playmate/detail/data/PlaymateDetailData.kt
  19. 53 0
      module/playmate/src/main/java/com/adealink/weparty/playmate/detail/viewmodel/PlaymateDetailViewModel.kt
  20. 14 3
      module/playmate/src/main/java/com/adealink/weparty/playmate/list/PlaymateListFragment.kt
  21. 11 1
      module/playmate/src/main/java/com/adealink/weparty/playmate/list/adapter/PlaymateListItemViewBinder.kt
  22. 4 0
      module/playmate/src/main/java/com/adealink/weparty/playmate/viewmodel/PlaymateViewModelFactory.kt
  23. BIN
      module/playmate/src/main/res/drawable-xhdpi/playmate_detail_profile_ic.png
  24. 15 0
      module/playmate/src/main/res/drawable/playmate_detail_label_item_bg.xml
  25. 11 0
      module/playmate/src/main/res/drawable/playmate_detail_tab_bg.xml
  26. 74 3
      module/playmate/src/main/res/layout/activity_playmate_detail.xml
  27. 71 0
      module/playmate/src/main/res/layout/fragment_playmate_detail.xml
  28. 6 0
      module/playmate/src/main/res/layout/layout_playmate_detail_banner_item.xml
  29. 61 0
      module/playmate/src/main/res/layout/layout_playmate_detail_bottom.xml
  30. 147 0
      module/playmate/src/main/res/layout/layout_playmate_detail_header.xml
  31. 14 0
      module/playmate/src/main/res/layout/layout_playmate_detail_label_item.xml
  32. 1 0
      module/playmate/src/main/res/values/dimens.xml
  33. 2 2
      module/profile/src/main/res/layout/dialog_create_order.xml
  34. 0 1
      module/profile/src/main/res/values/strings.xml

+ 1 - 0
app/src/main/java/com/adealink/weparty/module/playmate/Router.kt

@@ -39,6 +39,7 @@ interface Playmate {
     interface Detail {
         companion object {
             const val PATH = "${Common.PATH}/detail"
+            const val EXTRA_PLAYMATE_ID = "extra_playmate_id"
         }
     }
 

+ 9 - 4
app/src/main/java/com/adealink/weparty/module/playmate/widget/PriceView.kt

@@ -16,7 +16,8 @@ class PriceView @JvmOverloads constructor(
 
     private val binding = LayoutPriceViewBinding.inflate(LayoutInflater.from(context), this)
 
-    private var textSize = 12f
+    private var priceTextSize = 12f
+    private var unitTextSize = 12f
 
     init {
         if (isInEditMode) {
@@ -35,14 +36,18 @@ class PriceView @JvmOverloads constructor(
             attrs,
             R.styleable.PriceView
         ) {
-            textSize = getFloat(
+            priceTextSize = getFloat(
                 R.styleable.PriceView_price_text_size,
                 12f
             )
+            unitTextSize = getFloat(
+                R.styleable.PriceView_unit_text_size,
+                12f
+            )
         }
 
-        binding.tvPrice.textSize = textSize
-        binding.tvUnit.textSize = textSize
+        binding.tvPrice.textSize = priceTextSize
+        binding.tvUnit.textSize = unitTextSize
     }
 
     fun setPrice(price: Float, unit: String) {

+ 5 - 1
app/src/main/java/com/adealink/weparty/module/profile/widget/GenderView.kt

@@ -44,7 +44,11 @@ class GenderView @JvmOverloads constructor(
     }
 
     fun setAge(age: Int) {
-        binding.tvAge.text = age.toString()
+        if (age <= 0) {
+            binding.tvAge.text = null
+        } else {
+            binding.tvAge.text = age.toString()
+        }
     }
 
 }

+ 0 - 0
module/profile/src/main/res/drawable-xhdpi/profile_create_order_button_bg.9.png → app/src/main/res/drawable-xhdpi/common_create_order_button_bg.9.png


BIN
app/src/main/res/drawable-xhdpi/common_language_ic.png


BIN
app/src/main/res/drawable-xhdpi/common_location_ic.png


+ 0 - 0
module/profile/src/main/res/drawable/profile_create_order_price_bg.xml → app/src/main/res/drawable/common_create_order_price_bg.xml


+ 1 - 0
app/src/main/res/values/attrs.xml

@@ -589,5 +589,6 @@
 
     <declare-styleable name="PriceView">
         <attr name="price_text_size" format="float" />
+        <attr name="unit_text_size" format="float" />
     </declare-styleable>
 </resources>

+ 1 - 0
app/src/main/res/values/strings.xml

@@ -257,4 +257,5 @@
     <string name="toast_storage_permission_no_granted">The storage permission has not been obtained, and the picture cannot be saved. Go to [System Settings] to open it.</string>
     <string name="common_error_no_more_data">No more data</string>
     <string name="common_playmate_price">%1$s/%2$s</string>
+    <string name="profile_create_order">Order</string>
 </resources>

+ 28 - 0
module/playmate/src/main/java/com/adealink/weparty/playmate/data/PlaymateData.kt

@@ -0,0 +1,28 @@
+package com.adealink.weparty.playmate.data
+
+import com.google.gson.annotations.SerializedName
+
+data class PlaymateDetailReq(
+    @SerializedName("id") val id: String, //技能/商品ID
+)
+
+data class PlaymateDetailRes(
+    @SerializedName("id") val id: String, //技能/商品ID
+
+    @SerializedName("userNo") val uid: String, //用户ID
+    @SerializedName("avatar") val avatar: String, //头像
+    @SerializedName("nickname") val nickname: String, //昵称
+    @SerializedName("age") val age: Int, //年龄
+    @SerializedName("gender") val gender: Int, //性别
+    @SerializedName("star") val star: Float, //评分
+    @SerializedName("area") val area: String, //所在地区
+    @SerializedName("cover") val cover: String, //用户封面图片
+    @SerializedName("summary") val summary: String, //摘要
+    @SerializedName("images") val images: List<String>, //图片列表
+    @SerializedName("voiceBar") val voice: String, //图片列表
+    @SerializedName("price") val price: Float, //价格
+    @SerializedName("unit") val unit: String, //价格单位
+    @SerializedName("labels") val labels: List<String>, //标签
+    @SerializedName("categoryName") val categoryName: String, //品类名称
+    @SerializedName("categoryIcon") val categoryIcon: String, //品类图标
+)

+ 5 - 0
module/playmate/src/main/java/com/adealink/weparty/playmate/datasource/remote/PlaymateHttpService.kt

@@ -3,6 +3,8 @@ package com.adealink.weparty.playmate.datasource.remote
 import com.adealink.frame.base.Rlt
 import com.adealink.frame.network.data.Res
 import com.adealink.weparty.playmate.data.PlaymateCategoryRes
+import com.adealink.weparty.playmate.data.PlaymateDetailReq
+import com.adealink.weparty.playmate.data.PlaymateDetailRes
 import com.adealink.weparty.playmate.data.PlaymateListReq
 import com.adealink.weparty.playmate.data.PlaymateListRes
 import retrofit2.http.Body
@@ -20,4 +22,7 @@ interface PlaymateHttpService {
     @POST("skill/list")
     suspend fun getPlaymateList(@Body req: PlaymateListReq): Rlt<Res<PlaymateListRes>>
 
+    @POST("skill/detail")
+    suspend fun getPlaymateDetail(@Body req: PlaymateDetailReq): Rlt<Res<PlaymateDetailRes>>
+
 }

+ 50 - 0
module/playmate/src/main/java/com/adealink/weparty/playmate/detail/PlaymateDetailActivity.kt

@@ -1,10 +1,20 @@
 package com.adealink.weparty.playmate.detail
 
+import androidx.activity.viewModels
+import com.adealink.frame.base.fastLazy
 import com.adealink.frame.mvvm.view.viewBinding
+import com.adealink.frame.router.Router
+import com.adealink.frame.router.annotation.BindExtra
 import com.adealink.frame.router.annotation.RouterUri
 import com.adealink.weparty.commonui.BaseActivity
+import com.adealink.weparty.commonui.toast.util.showFailedToast
 import com.adealink.weparty.module.playmate.Playmate
 import com.adealink.weparty.playmate.databinding.ActivityPlaymateDetailBinding
+import com.adealink.weparty.playmate.detail.comp.PlaymateDetailBottomComp
+import com.adealink.weparty.playmate.detail.comp.PlaymateDetailHeaderComp
+import com.adealink.weparty.playmate.detail.comp.PlaymateToolBarViewComp
+import com.adealink.weparty.playmate.detail.viewmodel.PlaymateDetailViewModel
+import com.adealink.weparty.playmate.viewmodel.PlaymateViewModelFactory
 
 @RouterUri(
     path = [Playmate.Detail.PATH],
@@ -12,9 +22,49 @@ import com.adealink.weparty.playmate.databinding.ActivityPlaymateDetailBinding
 )
 class PlaymateDetailActivity : BaseActivity() {
 
+    @BindExtra(Playmate.Detail.EXTRA_PLAYMATE_ID)
+    var playmateID: String? = null
+
     private val binding by viewBinding(ActivityPlaymateDetailBinding::inflate)
+    private val detailViewModel by viewModels<PlaymateDetailViewModel> { PlaymateViewModelFactory() }
+    private val detailFragment by fastLazy { PlaymateDetailFragment() }
+
+    override fun onBeforeCreate() {
+        super.onBeforeCreate()
+        Router.bind(this)
+    }
+
     override fun initViews() {
         super.initViews()
         setContentView(binding.root)
+        inflateDetailFragment()
+    }
+
+    override fun initComponents() {
+        super.initComponents()
+        PlaymateToolBarViewComp(this, binding).attach()
+        PlaymateDetailHeaderComp(this, binding.headerLayout).attach()
+        PlaymateDetailBottomComp(this, binding.vBottom).attach()
+    }
+
+    override fun loadData() {
+        super.loadData()
+        detailViewModel.loadPlaymateDetail(playmateID)
+    }
+
+    override fun observeViewModel() {
+        super.observeViewModel()
+        detailViewModel.detailRltLD.observe(this) { rlt ->
+            showFailedToast(rlt)
+        }
+    }
+
+    private fun inflateDetailFragment() {
+        if (detailFragment.isAdded) {
+            return
+        }
+        supportFragmentManager.beginTransaction()
+            .replace(binding.flContent.id, detailFragment, Playmate.Detail.PATH)
+            .commitAllowingStateLoss()
     }
 }

+ 109 - 0
module/playmate/src/main/java/com/adealink/weparty/playmate/detail/PlaymateDetailFragment.kt

@@ -0,0 +1,109 @@
+package com.adealink.weparty.playmate.detail
+
+import android.view.Gravity
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.fragment.app.activityViewModels
+import androidx.recyclerview.widget.OrientationHelper
+import com.adealink.frame.base.fastLazy
+import com.adealink.frame.mvvm.view.viewBinding
+import com.adealink.weparty.commonui.BaseFragment
+import com.adealink.weparty.commonui.ext.dp
+import com.adealink.weparty.commonui.recycleview.adapter.BindingViewHolder
+import com.adealink.weparty.commonui.recycleview.adapter.MultiTypeListAdapter
+import com.adealink.weparty.commonui.recycleview.layoutmanager.FlowLayoutManager
+import com.adealink.weparty.commonui.widget.banner.adapter.BannerAdapter
+import com.adealink.weparty.playmate.R
+import com.adealink.weparty.playmate.data.PlaymateDetailRes
+import com.adealink.weparty.playmate.databinding.FragmentPlaymateDetailBinding
+import com.adealink.weparty.playmate.databinding.LayoutPlaymateDetailBannerItemBinding
+import com.adealink.weparty.playmate.detail.adapter.PlaymateLabelViewBinder
+import com.adealink.weparty.playmate.detail.data.PlaymateDetailImg
+import com.adealink.weparty.playmate.detail.data.PlaymateDetailLabel
+import com.adealink.weparty.playmate.detail.viewmodel.PlaymateDetailViewModel
+import com.adealink.weparty.playmate.viewmodel.PlaymateViewModelFactory
+
+class PlaymateDetailFragment : BaseFragment(R.layout.fragment_playmate_detail) {
+
+    private val binding by viewBinding(FragmentPlaymateDetailBinding::bind)
+    private val detailViewModel by activityViewModels<PlaymateDetailViewModel> { PlaymateViewModelFactory() }
+
+    private val labelAdapter by fastLazy { MultiTypeListAdapter<PlaymateDetailLabel>() }
+    private lateinit var imgAdapter: BannerImgAdapter
+    override fun initViews() {
+        super.initViews()
+        labelAdapter.register(PlaymateLabelViewBinder())
+        binding.rvLabel.adapter = labelAdapter
+        binding.rvLabel.layoutManager =
+            FlowLayoutManager(OrientationHelper.HORIZONTAL, Gravity.START, 10.dp(), 6.dp())
+
+        imgAdapter = BannerImgAdapter()
+        binding.bannerImg.apply {
+            setLoopTime(6 * 1000)
+            addBannerLifecycleObserver(viewLifecycleOwner)
+            setAdapter(imgAdapter)
+            setOnBannerListener { data, _ ->
+                val imgData = data as? PlaymateDetailImg ?: return@setOnBannerListener
+
+            }
+        }
+    }
+
+    override fun observeViewModel() {
+        super.observeViewModel()
+        detailViewModel.detailLD.observe(viewLifecycleOwner) {
+            updateDetail(it)
+        }
+    }
+
+    private fun updateDetail(data: PlaymateDetailRes?) {
+        binding.tvTitle.text = data?.categoryName
+        binding.tvSummary.text = data?.summary
+
+        labelAdapter.submitList(
+            data?.labels?.map {
+                PlaymateDetailLabel(it)
+            } ?: emptyList()
+        )
+
+        imgAdapter.datas = data?.images?.map {
+            PlaymateDetailImg(it)
+        } ?: emptyList()
+    }
+
+    inner class BannerImgAdapter() :
+        BannerAdapter<PlaymateDetailImg, BannerImgItemViewHolder>(emptyList()) {
+        override fun onCreateHolder(
+            parent: ViewGroup,
+            viewType: Int,
+        ): BannerImgItemViewHolder {
+            val inflater = LayoutInflater.from(parent.context)
+            return BannerImgItemViewHolder(
+                LayoutPlaymateDetailBannerItemBinding.inflate(
+                    inflater,
+                    parent,
+                    false
+                )
+            )
+        }
+
+        override fun onBindView(
+            holder: BannerImgItemViewHolder,
+            data: PlaymateDetailImg,
+            position: Int,
+            size: Int
+        ) {
+            holder.update(data)
+        }
+
+    }
+
+    inner class BannerImgItemViewHolder(binding: LayoutPlaymateDetailBannerItemBinding) :
+        BindingViewHolder<LayoutPlaymateDetailBannerItemBinding>(binding) {
+
+        fun update(data: PlaymateDetailImg) {
+            binding.root.setImageUrl(data.url)
+        }
+    }
+
+}

+ 33 - 0
module/playmate/src/main/java/com/adealink/weparty/playmate/detail/adapter/PlaymateLabelViewBinder.kt

@@ -0,0 +1,33 @@
+package com.adealink.weparty.playmate.detail.adapter
+
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import com.adealink.weparty.commonui.recycleview.adapter.BindingViewHolder
+import com.adealink.weparty.commonui.recycleview.adapter.multitype.ItemViewBinder
+import com.adealink.weparty.playmate.databinding.LayoutPlaymateDetailLabelItemBinding
+import com.adealink.weparty.playmate.detail.data.PlaymateDetailLabel
+
+class PlaymateLabelViewBinder :
+    ItemViewBinder<PlaymateDetailLabel, BindingViewHolder<LayoutPlaymateDetailLabelItemBinding>>() {
+
+    override fun onBindViewHolder(
+        holder: BindingViewHolder<LayoutPlaymateDetailLabelItemBinding>,
+        item: PlaymateDetailLabel,
+    ) {
+        holder.binding.root.text = item.label
+    }
+
+    override fun onCreateViewHolder(
+        inflater: LayoutInflater,
+        parent: ViewGroup,
+    ): BindingViewHolder<LayoutPlaymateDetailLabelItemBinding> {
+        return BindingViewHolder(
+            LayoutPlaymateDetailLabelItemBinding.inflate(
+                inflater,
+                parent,
+                false
+            )
+        )
+    }
+
+}

+ 52 - 0
module/playmate/src/main/java/com/adealink/weparty/playmate/detail/comp/PlaymateDetailBottomComp.kt

@@ -0,0 +1,52 @@
+package com.adealink.weparty.playmate.detail.comp
+
+import androidx.lifecycle.LifecycleOwner
+import com.adealink.frame.mvvm.view.ViewComponent
+import com.adealink.frame.mvvm.viewmodel.activityViewModels
+import com.adealink.frame.util.onClick
+import com.adealink.weparty.commonui.ext.gone
+import com.adealink.weparty.commonui.ext.show
+import com.adealink.weparty.playmate.data.PlaymateDetailRes
+import com.adealink.weparty.playmate.databinding.LayoutPlaymateDetailBottomBinding
+import com.adealink.weparty.playmate.detail.viewmodel.PlaymateDetailViewModel
+import com.adealink.weparty.playmate.viewmodel.PlaymateViewModelFactory
+
+class PlaymateDetailBottomComp(
+    lifecycleOwner: LifecycleOwner,
+    private val binding: LayoutPlaymateDetailBottomBinding,
+) : ViewComponent(lifecycleOwner) {
+
+    private val detailViewModel by activityViewModels<PlaymateDetailViewModel> { PlaymateViewModelFactory() }
+
+    override fun onCreate() {
+        super.onCreate()
+        initView()
+        observeViewModel()
+    }
+
+    private fun initView() {
+        binding.btnOrder.onClick {
+            createOrder()
+        }
+    }
+
+    private fun observeViewModel() {
+        detailViewModel.detailLD.observe(viewLifecycleOwner) {
+            updateBottom(it)
+        }
+    }
+
+    private fun updateBottom(data: PlaymateDetailRes?) {
+        if (data == null) {
+            binding.vPrice.gone()
+        } else {
+            binding.vPrice.show()
+            binding.vPrice.setPrice(data.price, data.unit)
+        }
+    }
+
+    private fun createOrder() {
+
+    }
+
+}

+ 56 - 0
module/playmate/src/main/java/com/adealink/weparty/playmate/detail/comp/PlaymateDetailHeaderComp.kt

@@ -0,0 +1,56 @@
+package com.adealink.weparty.playmate.detail.comp
+
+import android.annotation.SuppressLint
+import androidx.lifecycle.LifecycleOwner
+import com.adealink.frame.mvvm.view.ViewComponent
+import com.adealink.frame.mvvm.viewmodel.activityViewModels
+import com.adealink.weparty.playmate.data.PlaymateDetailRes
+import com.adealink.weparty.playmate.databinding.LayoutPlaymateDetailHeaderBinding
+import com.adealink.weparty.playmate.detail.viewmodel.PlaymateDetailViewModel
+import com.adealink.weparty.playmate.viewmodel.PlaymateViewModelFactory
+
+class PlaymateDetailHeaderComp(
+    lifecycleOwner: LifecycleOwner,
+    private val binding: LayoutPlaymateDetailHeaderBinding,
+) : ViewComponent(lifecycleOwner) {
+
+    private val detailViewModel by activityViewModels<PlaymateDetailViewModel> { PlaymateViewModelFactory() }
+
+    override fun onCreate() {
+        super.onCreate()
+        initView()
+        observeViewModel()
+    }
+
+    private fun initView() {
+        binding.tvEvaluate.setOnClickListener {
+            showEvaluateDialog()
+        }
+    }
+
+    private fun observeViewModel() {
+        detailViewModel.detailLD.observe(viewLifecycleOwner) { data ->
+            updateHeader(data)
+        }
+    }
+
+    @SuppressLint("SetTextI18n")
+    private fun updateHeader(data: PlaymateDetailRes?) {
+        binding.ivBg.setImageUrl(data?.cover)
+
+        binding.tvName.text = data?.nickname
+        binding.tvUserId.text = "ID ${data?.uid ?: ""}"
+        binding.vGender.setGender(data?.gender)
+        binding.vGender.setAge(data?.age ?: 0)
+        // TODO: 位置信息,语言从用户数据里获取
+        binding.tvLocation.text = data?.nickname
+        binding.tvLanauge.text = data?.nickname
+
+        binding.tvEvaluate.text = data?.star?.toString() ?: ""
+    }
+
+    private fun showEvaluateDialog() {
+
+    }
+
+}

+ 106 - 0
module/playmate/src/main/java/com/adealink/weparty/playmate/detail/comp/PlaymateToolBarViewComp.kt

@@ -0,0 +1,106 @@
+package com.adealink.weparty.playmate.detail.comp
+
+import android.animation.ArgbEvaluator
+import android.graphics.Color
+import android.view.ViewGroup.MarginLayoutParams
+import androidx.core.view.updateLayoutParams
+import androidx.lifecycle.LifecycleOwner
+import com.adealink.frame.aab.util.getCompatDimension
+import com.adealink.frame.aab.util.getCompatDrawable
+import com.adealink.frame.log.Log
+import com.adealink.frame.mvvm.view.ViewComponent
+import com.adealink.frame.mvvm.viewmodel.activityViewModels
+import com.adealink.frame.router.Router
+import com.adealink.frame.util.DisplayUtil
+import com.adealink.frame.util.isActivityInValid
+import com.adealink.frame.util.onClick
+import com.adealink.weparty.module.profile.Profile
+import com.adealink.weparty.playmate.databinding.ActivityPlaymateDetailBinding
+import com.adealink.weparty.playmate.detail.viewmodel.PlaymateDetailViewModel
+import com.adealink.weparty.playmate.viewmodel.PlaymateViewModelFactory
+import com.google.android.material.appbar.AppBarLayout
+import com.qmuiteam.qmui.widget.util.QMUIStatusBarHelper
+import kotlin.math.abs
+import com.adealink.weparty.R as APP_R
+
+class PlaymateToolBarViewComp(
+    lifecycleOwner: LifecycleOwner,
+    private val binding: ActivityPlaymateDetailBinding
+) : ViewComponent(lifecycleOwner) {
+
+    private val detailViewModel by activityViewModels<PlaymateDetailViewModel> { PlaymateViewModelFactory() }
+    private var uid: String? = null
+    override fun onCreate() {
+        super.onCreate()
+        initView()
+        observeViewModel()
+    }
+
+    private fun initView() {
+        val activity = activity ?: return
+        val statusBarHeight = DisplayUtil.getStatusBarHeight(activity)
+        val topbarHeight =
+            statusBarHeight + getCompatDimension(APP_R.dimen.common_top_bar_height).toInt()
+        binding.topBarContainer.updateLayoutParams<MarginLayoutParams> {
+            height = topbarHeight
+        }
+        binding.collapsingToolbarLayout.minimumHeight = topbarHeight
+
+        binding.appBarLayout.addOnOffsetChangedListener(AppBarLayout.OnOffsetChangedListener { appBarLayout, verticalOffset ->
+            if (isActivityInValid(activity)) {
+                return@OnOffsetChangedListener
+            }
+            val appBarHeight = appBarLayout?.height ?: return@OnOffsetChangedListener
+            Log.d(TAG, "appBarHeight = $appBarHeight, verticalOffset = $verticalOffset")
+            val percent =
+                (abs(verticalOffset) * 1f / (appBarLayout.totalScrollRange)).coerceIn(0F, 1F)
+            binding.topBarContainer.alpha = percent
+
+            val fromColor = Color.WHITE  // 起始颜色:白色
+            val toColor = Color.BLACK    // 目标颜色:黑色
+            val color = ArgbEvaluator().evaluate(percent, fromColor, toColor) as Int
+            val backDrawable = getCompatDrawable(APP_R.drawable.commonui_back_black_48_ic).mutate()
+            backDrawable.setTint(color)
+            binding.ivBack.setBackgroundDrawable(backDrawable)
+            binding.ivProfile.drawable?.setTint(color)
+
+            if (percent == 1f) {
+                QMUIStatusBarHelper.setStatusBarLightMode(activity)
+            } else {
+                QMUIStatusBarHelper.setStatusBarDarkMode(activity)
+            }
+
+        })
+
+        binding.ivBack.onClick {
+            activity.finish()
+        }
+        binding.ivProfile.onClick {
+            goProfile()
+        }
+
+    }
+
+    private fun goProfile() {
+        val uid = this@PlaymateToolBarViewComp.uid
+        if (uid.isNullOrEmpty()) {
+            return
+        }
+        activity?.let { act ->
+            Router.build(act, Profile.UserProfile.PATH)
+                .putExtra(Profile.Common.EXTRA_UID, uid)
+                .start()
+        }
+    }
+
+    private fun observeViewModel() {
+        detailViewModel.detailLD.observe(viewLifecycleOwner) {
+            this@PlaymateToolBarViewComp.uid = it?.uid
+        }
+    }
+
+    companion object {
+        const val TAG = "PlaymateToolBarViewComp"
+    }
+
+}

+ 11 - 0
module/playmate/src/main/java/com/adealink/weparty/playmate/detail/data/PlaymateDetailData.kt

@@ -0,0 +1,11 @@
+package com.adealink.weparty.playmate.detail.data
+
+import com.adealink.weparty.commonui.recycleview.diffutil.BaseListItemData
+
+data class PlaymateDetailLabel(
+    val label: String
+) : BaseListItemData
+
+data class PlaymateDetailImg(
+    val url: String
+)

+ 53 - 0
module/playmate/src/main/java/com/adealink/weparty/playmate/detail/viewmodel/PlaymateDetailViewModel.kt

@@ -0,0 +1,53 @@
+package com.adealink.weparty.playmate.detail.viewmodel
+
+import androidx.lifecycle.MutableLiveData
+import com.adealink.frame.base.CommonDataNullError
+import com.adealink.frame.base.Rlt
+import com.adealink.frame.mvvm.viewmodel.BaseViewModel
+import com.adealink.weparty.App
+import com.adealink.weparty.playmate.data.PlaymateDetailReq
+import com.adealink.weparty.playmate.data.PlaymateDetailRes
+import com.adealink.weparty.playmate.datasource.remote.PlaymateHttpService
+import kotlinx.coroutines.launch
+
+class PlaymateDetailViewModel : BaseViewModel() {
+
+    val detailRltLD = MutableLiveData<Rlt<Any>>()
+    val detailLD = MutableLiveData<PlaymateDetailRes?>()
+
+    private val playmateHttpService by lazy {
+        App.instance.networkService.getHttpService(PlaymateHttpService::class.java)
+    }
+
+    fun loadPlaymateDetail(id: String?) {
+        if (id.isNullOrEmpty()) {
+            detailRltLD.send(Rlt.Failed(CommonDataNullError()))
+            detailLD.send(null)
+            return
+        }
+        viewModelScope.launch {
+            val rlt = playmateHttpService.getPlaymateDetail(PlaymateDetailReq(id))
+            when (rlt) {
+                is Rlt.Failed -> {
+                    detailRltLD.send(rlt)
+                }
+
+                is Rlt.Success -> {
+                    val data = rlt.data.data
+                    if (data == null) {
+                        detailRltLD.send(Rlt.Failed(CommonDataNullError()))
+                    } else {
+                        detailLD.send(data)
+                        detailRltLD.send(Rlt.Success(data))
+                    }
+
+                }
+            }
+        }
+    }
+
+    fun getPlaymateDetail(): PlaymateDetailRes? {
+        return detailLD.value
+    }
+
+}

+ 14 - 3
module/playmate/src/main/java/com/adealink/weparty/playmate/list/PlaymateListFragment.kt

@@ -21,6 +21,7 @@ import com.adealink.weparty.module.playmate.Playmate
 import com.adealink.weparty.module.playmate.data.PlaymateCategoryData
 import com.adealink.weparty.module.playmate.data.TAG_PLAYMATE_LIST
 import com.adealink.weparty.playmate.R
+import com.adealink.weparty.playmate.data.PlaymateListData
 import com.adealink.weparty.playmate.data.PlaymateListItemData
 import com.adealink.weparty.playmate.databinding.FragmentPlaymateListBinding
 import com.adealink.weparty.playmate.findpartner.FindPartnerDialog
@@ -31,7 +32,8 @@ import com.scwang.smart.refresh.layout.api.RefreshLayout
 import com.scwang.smart.refresh.layout.listener.OnRefreshLoadMoreListener
 
 @RouterUri(path = [Playmate.List.PATH], desc = "陪玩列表")
-class PlaymateListFragment : BaseFragment(R.layout.fragment_playmate_list) {
+class PlaymateListFragment : BaseFragment(R.layout.fragment_playmate_list),
+    PlaymateListItemViewBinder.OnPlaymateListListener {
 
     override val isLazyInit: Boolean = true
     override val isLazyLoad: Boolean = true
@@ -71,7 +73,7 @@ class PlaymateListFragment : BaseFragment(R.layout.fragment_playmate_list) {
             }
         })
 
-        listAdapter.register(PlaymateListItemViewBinder())
+        listAdapter.register(PlaymateListItemViewBinder(this))
         binding.vList.adapter = listAdapter
         binding.vList.layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false)
         binding.vList.addItemDecoration(VerticalSpaceItemDecoration(10.dp()))
@@ -81,7 +83,7 @@ class PlaymateListFragment : BaseFragment(R.layout.fragment_playmate_list) {
         FindPartnerDialog().show(childFragmentManager, Playmate.FindPartner.PATH)
     }
 
-    fun updateCategory(category: PlaymateCategoryData?){
+    fun updateCategory(category: PlaymateCategoryData?) {
         if (!isViewBindingValid()) {
             return
         }
@@ -117,4 +119,13 @@ class PlaymateListFragment : BaseFragment(R.layout.fragment_playmate_list) {
         }
     }
 
+    override fun goCategoryDetail(data: PlaymateListData) {
+        activity?.let { act ->
+            Router.build(act, Playmate.Detail.PATH)
+                .putExtra(Playmate.Detail.EXTRA_PLAYMATE_ID, data.id)
+                .start()
+        }
+    }
+
+
 }

+ 11 - 1
module/playmate/src/main/java/com/adealink/weparty/playmate/list/adapter/PlaymateListItemViewBinder.kt

@@ -6,17 +6,23 @@ import com.adealink.weparty.commonui.ext.gone
 import com.adealink.weparty.commonui.ext.show
 import com.adealink.weparty.commonui.recycleview.adapter.BindingViewHolder
 import com.adealink.weparty.commonui.recycleview.adapter.multitype.ItemViewBinder
+import com.adealink.weparty.playmate.data.PlaymateListData
 import com.adealink.weparty.playmate.data.PlaymateListItemData
 import com.adealink.weparty.playmate.databinding.ItemPlaymateHomeListBinding
 
-class PlaymateListItemViewBinder :
+class PlaymateListItemViewBinder(val listener: OnPlaymateListListener) :
     ItemViewBinder<PlaymateListItemData, BindingViewHolder<ItemPlaymateHomeListBinding>>() {
 
     override fun onBindViewHolder(
         holder: BindingViewHolder<ItemPlaymateHomeListBinding>,
         item: PlaymateListItemData,
     ) {
+        holder.binding.root.setOnClickListener {
+            listener.goCategoryDetail(item.data)
+        }
+
         holder.binding.ivAvatar.setImageUrl(item.data.avatar)
+
         holder.binding.tvName.text = item.data.nickname
         holder.binding.vGender.setAge(item.data.age)
         holder.binding.vGender.setGender(item.data.gender)
@@ -56,4 +62,8 @@ class PlaymateListItemViewBinder :
     ): BindingViewHolder<ItemPlaymateHomeListBinding> {
         return BindingViewHolder(ItemPlaymateHomeListBinding.inflate(inflater, parent, false))
     }
+
+    interface OnPlaymateListListener {
+        fun goCategoryDetail(data: PlaymateListData)
+    }
 }

+ 4 - 0
module/playmate/src/main/java/com/adealink/weparty/playmate/viewmodel/PlaymateViewModelFactory.kt

@@ -2,6 +2,7 @@ package com.adealink.weparty.playmate.viewmodel
 
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.ViewModelProvider
+import com.adealink.weparty.playmate.detail.viewmodel.PlaymateDetailViewModel
 import com.adealink.weparty.playmate.list.viewmodel.PlaymateListViewModel
 
 @Suppress("UNCHECKED_CAST")
@@ -16,6 +17,9 @@ class PlaymateViewModelFactory : ViewModelProvider.NewInstanceFactory() {
                 isAssignableFrom(PlaymateListViewModel::class.java) ->
                     PlaymateListViewModel()
 
+                isAssignableFrom(PlaymateDetailViewModel::class.java) ->
+                    PlaymateDetailViewModel()
+
                 else ->
                     throw IllegalArgumentException("Unknown ViewModel class: ${modelClass.name}")
             } as T

BIN
module/playmate/src/main/res/drawable-xhdpi/playmate_detail_profile_ic.png


+ 15 - 0
module/playmate/src/main/res/drawable/playmate_detail_label_item_bg.xml

@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+
+    <corners
+        android:topLeftRadius="30dp"
+        android:topRightRadius="30dp" />
+
+    <solid android:color="@color/white" />
+
+    <stroke
+        android:width="1dp"
+        android:color="#C9CDD4" />
+
+</shape>

+ 11 - 0
module/playmate/src/main/res/drawable/playmate_detail_tab_bg.xml

@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+
+    <corners
+        android:topLeftRadius="20dp"
+        android:topRightRadius="20dp" />
+
+    <solid android:color="@color/white" />
+
+</shape>

+ 74 - 3
module/playmate/src/main/res/layout/activity_playmate_detail.xml

@@ -1,7 +1,78 @@
 <?xml version="1.0" encoding="utf-8"?>
-<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
-    android:layout_height="match_parent">
+    android:layout_height="match_parent"
+    android:background="@color/black">
 
+    <com.google.android.material.appbar.AppBarLayout
+        android:id="@+id/appBarLayout"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:background="@color/transparent"
+        app:elevation="0dp">
 
-</androidx.constraintlayout.widget.ConstraintLayout>
+        <com.google.android.material.appbar.CollapsingToolbarLayout
+            android:id="@+id/collapsing_toolbar_layout"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            app:layout_scrollFlags="scroll|exitUntilCollapsed">
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="vertical">
+
+                <include
+                    android:id="@+id/header_layout"
+                    layout="@layout/layout_playmate_detail_header"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:minHeight="93dp"
+                    app:layout_scrollFlags="scroll" />
+            </LinearLayout>
+
+        </com.google.android.material.appbar.CollapsingToolbarLayout>
+
+    </com.google.android.material.appbar.AppBarLayout>
+
+    <androidx.fragment.app.FragmentContainerView
+        android:id="@+id/fl_content"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        app:layout_behavior="@string/appbar_scrolling_view_behavior" />
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:id="@+id/topBarContainer"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/common_top_bar_height"
+        android:paddingHorizontal="16dp">
+
+        <androidx.appcompat.widget.AppCompatImageView
+            android:id="@+id/iv_back"
+            android:layout_width="24dp"
+            android:layout_height="24dp"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            app:srcCompat="@drawable/commonui_back_white_48_ic" />
+
+        <androidx.appcompat.widget.AppCompatImageView
+            android:id="@+id/iv_profile"
+            android:layout_width="24dp"
+            android:layout_height="24dp"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            app:srcCompat="@drawable/playmate_detail_profile_ic" />
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+
+    <include
+        android:id="@+id/v_bottom"
+        layout="@layout/layout_playmate_detail_bottom"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="bottom" />
+
+</androidx.coordinatorlayout.widget.CoordinatorLayout>

+ 71 - 0
module/playmate/src/main/res/layout/fragment_playmate_detail.xml

@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="utf-8"?>
+<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"
+    android:background="@drawable/common_bottom_dialog_bg">
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:padding="16dp">
+
+        <androidx.appcompat.widget.AppCompatTextView
+            android:id="@+id/tv_title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:fontFamily="@font/poppins_semibold"
+            android:includeFontPadding="false"
+            android:textColor="@color/color_FF4E5969"
+            android:textSize="16sp"
+            app:layout_constrainedWidth="true"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintHorizontal_bias="0"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            tools:text="LOL Mobile" />
+
+        <View
+            android:layout_width="0dp"
+            android:layout_height="1dp"
+            android:layout_marginTop="10dp"
+            android:background="@color/color_FFF2F3F5"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toBottomOf="@id/tv_title" />
+
+        <androidx.appcompat.widget.AppCompatTextView
+            android:id="@+id/tv_summary"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="20dp"
+            android:includeFontPadding="false"
+            android:textColor="@color/color_FF4E5969"
+            android:textSize="16sp"
+            app:layout_constrainedWidth="true"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintHorizontal_bias="0"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toBottomOf="@id/tv_title"
+            tools:text="Hello brother, multiple seasons of S, online,seasons of S, online multiple seasons" />
+
+        <androidx.recyclerview.widget.RecyclerView
+            android:id="@+id/rv_label"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="14dp"
+            app:layout_constraintTop_toBottomOf="@id/tv_summary" />
+
+        <com.adealink.weparty.commonui.widget.banner.Banner
+            android:id="@+id/banner_img"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:layout_marginTop="14dp"
+            app:layout_constraintDimensionRatio="343:193"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toBottomOf="@id/rv_label" />
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+</androidx.core.widget.NestedScrollView>

+ 6 - 0
module/playmate/src/main/res/layout/layout_playmate_detail_banner_item.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<com.adealink.frame.image.view.NetworkImageView xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    app:actualImageScaleType="centerCrop" />

+ 61 - 0
module/playmate/src/main/res/layout/layout_playmate_detail_bottom.xml

@@ -0,0 +1,61 @@
+<?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"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:paddingHorizontal="16dp"
+    android:paddingTop="16dp"
+    android:paddingBottom="36dp">
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:layout_width="match_parent"
+        android:layout_height="48dp"
+        android:layout_marginTop="16dp"
+        app:layout_constraintBottom_toBottomOf="parent">
+
+        <androidx.constraintlayout.widget.ConstraintLayout
+            android:id="@+id/cl_price"
+            android:layout_width="wrap_content"
+            android:layout_height="0dp"
+            android:background="@drawable/common_create_order_price_bg"
+            android:paddingStart="20dp"
+            android:paddingEnd="60dp"
+            app:layout_constraintBottom_toBottomOf="@id/btn_order"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="@id/btn_order">
+
+            <com.adealink.weparty.module.playmate.widget.PriceView
+                android:id="@+id/v_price"
+                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"
+                app:layout_constraintTop_toTopOf="parent"
+                app:price_text_size="18"
+                app:unit_text_size="12" />
+
+        </androidx.constraintlayout.widget.ConstraintLayout>
+
+        <androidx.appcompat.widget.AppCompatTextView
+            android:id="@+id/btn_order"
+            android:layout_width="0dp"
+            android:layout_height="48dp"
+            android:layout_marginStart="-24dp"
+            android:background="@drawable/common_create_order_button_bg"
+            android:fontFamily="@font/poppins_semibold"
+            android:gravity="center"
+            android:includeFontPadding="false"
+            android:text="@string/profile_create_order"
+            android:textColor="@color/white"
+            android:textSize="16sp"
+            app:layout_constrainedHeight="true"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toEndOf="@id/cl_price"
+            app:layout_constraintTop_toTopOf="parent" />
+
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 147 - 0
module/playmate/src/main/res/layout/layout_playmate_detail_header.xml

@@ -0,0 +1,147 @@
+<?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"
+    tools:background="@color/black">
+
+    <com.adealink.frame.image.view.NetworkImageView
+        android:id="@+id/iv_bg"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/playmate_detail_header_height"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <!--基础信息 -->
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:id="@+id/cl_user_info"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="12dp"
+        android:paddingHorizontal="16dp"
+        app:layout_constraintBottom_toBottomOf="@id/iv_bg"
+        app:layout_constraintStart_toStartOf="@id/iv_bg">
+
+        <androidx.appcompat.widget.AppCompatTextView
+            android:id="@+id/tv_name"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:ellipsize="end"
+            android:fontFamily="@font/poppins_semibold"
+            android:includeFontPadding="false"
+            android:singleLine="true"
+            android:textColor="@color/white"
+            android:textSize="18sp"
+            app:layout_constrainedWidth="true"
+            app:layout_constraintEnd_toStartOf="@id/v_gender"
+            app:layout_constraintHorizontal_bias="0"
+            app:layout_constraintHorizontal_chainStyle="packed"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            tools:text="Super Beautiful Girl Super Beautiful Girl Super Beautiful Girl" />
+
+        <com.adealink.weparty.module.profile.widget.GenderView
+            android:id="@+id/v_gender"
+            style="@style/CommonGenderView"
+            android:layout_marginEnd="24dp"
+            app:layout_constraintBottom_toBottomOf="@id/tv_name"
+            app:layout_constraintEnd_toStartOf="@id/v_barrier_end"
+            app:layout_constraintStart_toEndOf="@id/tv_name"
+            app:layout_constraintTop_toTopOf="@id/tv_name" />
+
+        <!--用户ID-->
+        <androidx.appcompat.widget.AppCompatTextView
+            android:id="@+id/tv_user_id"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="2dp"
+            android:fontFamily="@font/poppins_semibold"
+            android:includeFontPadding="false"
+            android:textColor="@color/color_FFC9CDD4"
+            android:textSize="12sp"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toBottomOf="@id/tv_name"
+            tools:text="ID 12345678" />
+
+        <!--位置-->
+        <androidx.appcompat.widget.AppCompatImageView
+            android:id="@+id/iv_location"
+            android:layout_width="16dp"
+            android:layout_height="16dp"
+            android:layout_marginStart="12dp"
+            app:layout_constraintBottom_toBottomOf="@id/tv_user_id"
+            app:layout_constraintStart_toEndOf="@id/tv_user_id"
+            app:layout_constraintTop_toTopOf="@id/tv_user_id"
+            app:srcCompat="@drawable/common_location_ic" />
+
+        <androidx.appcompat.widget.AppCompatTextView
+            android:id="@+id/tv_location"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:fontFamily="@font/poppins_semibold"
+            android:includeFontPadding="false"
+            android:textColor="@color/color_FFC9CDD4"
+            android:textSize="12sp"
+            app:layout_constraintBottom_toBottomOf="@id/iv_location"
+            app:layout_constraintStart_toEndOf="@id/iv_location"
+            app:layout_constraintTop_toTopOf="@id/iv_location"
+            tools:text="Jakarta Timur" />
+
+        <!--语言-->
+        <androidx.appcompat.widget.AppCompatImageView
+            android:id="@+id/iv_language"
+            android:layout_width="16dp"
+            android:layout_height="16dp"
+            android:layout_marginTop="2dp"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toBottomOf="@id/tv_user_id"
+            app:srcCompat="@drawable/common_language_ic" />
+
+        <androidx.appcompat.widget.AppCompatTextView
+            android:id="@+id/tv_lanauge"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="4dp"
+            android:fontFamily="@font/poppins_semibold"
+            android:includeFontPadding="false"
+            android:textColor="@color/color_FFC9CDD4"
+            android:textSize="12sp"
+            app:layout_constraintBottom_toBottomOf="@id/iv_language"
+            app:layout_constraintStart_toEndOf="@id/iv_language"
+            app:layout_constraintTop_toTopOf="@id/iv_language"
+            tools:text="Indonesia/English/中文" />
+
+        <androidx.constraintlayout.widget.Barrier
+            android:id="@+id/v_barrier_end"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            app:barrierDirection="start"
+            app:constraint_referenced_ids="iv_evaluate_star" />
+
+        <androidx.appcompat.widget.AppCompatImageView
+            android:id="@+id/iv_evaluate_star"
+            android:layout_width="18dp"
+            android:layout_height="18dp"
+            app:layout_constraintBottom_toBottomOf="@id/tv_evaluate"
+            app:layout_constraintEnd_toStartOf="@id/tv_evaluate"
+            app:layout_constraintTop_toTopOf="@id/tv_evaluate"
+            app:srcCompat="@drawable/common_evaluate_star_selected_ic" />
+
+        <androidx.appcompat.widget.AppCompatTextView
+            android:id="@+id/tv_evaluate"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:fontFamily="@font/poppins_semibold"
+            android:includeFontPadding="false"
+            android:textColor="@color/white"
+            android:textSize="18sp"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintTop_toTopOf="parent"
+            tools:text="4.2" />
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 14 - 0
module/playmate/src/main/res/layout/layout_playmate_detail_label_item.xml

@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.appcompat.widget.AppCompatTextView 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="wrap_content"
+    android:layout_height="wrap_content"
+    android:background="@drawable/playmate_detail_label_item_bg"
+    android:includeFontPadding="false"
+    android:paddingHorizontal="10dp"
+    android:paddingVertical="5dp"
+    android:textColor="@color/color_FF4E5969"
+    android:textSize="12sp"
+    app:actualImageScaleType="centerCrop"
+    tools:text="Rank: Ace" />

+ 1 - 0
module/playmate/src/main/res/values/dimens.xml

@@ -1,3 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
 <resources>
+    <dimen name="playmate_detail_header_height">360dp</dimen>
 </resources>

+ 2 - 2
module/profile/src/main/res/layout/dialog_create_order.xml

@@ -58,7 +58,7 @@
             android:id="@+id/cl_price"
             android:layout_width="wrap_content"
             android:layout_height="0dp"
-            android:background="@drawable/profile_create_order_price_bg"
+            android:background="@drawable/common_create_order_price_bg"
             android:paddingStart="20dp"
             android:paddingEnd="60dp"
             app:layout_constraintBottom_toBottomOf="@id/btn_order"
@@ -95,7 +95,7 @@
             android:layout_width="0dp"
             android:layout_height="wrap_content"
             android:layout_marginStart="-24dp"
-            android:background="@drawable/profile_create_order_button_bg"
+            android:background="@drawable/common_create_order_button_bg"
             android:fontFamily="@font/poppins_semibold"
             android:gravity="center"
             android:includeFontPadding="false"

+ 0 - 1
module/profile/src/main/res/values/strings.xml

@@ -5,7 +5,6 @@
     <string name="profile_pay_with_her">Pay with her</string>
     <string name="profile_information">Information</string>
     <string name="profile_skill">Skill</string>
-    <string name="profile_create_order">Order</string>
     <string name="profile_evaluate_title">你对Ta的好感度</string>
     <string name="profile_go_profile"><![CDATA[个人页 >]]></string>
     <string name="profile_generate_qr_code">Generate QR code [IMG]</string>