Переглянути джерело

feat: 消息列表红点刷新

DoggyZhang 3 місяців тому
батько
коміт
1807d2083d
17 змінених файлів з 284 додано та 60 видалено
  1. 12 2
      app/src/main/java/com/adealink/weparty/commonui/ripple/RippleView.kt
  2. 40 0
      app/src/main/java/com/adealink/weparty/module/im/dot/MessageDot.kt
  3. 14 0
      app/src/main/java/com/adealink/weparty/ui/main/MainFragment.kt
  4. 27 4
      app/src/main/java/com/adealink/weparty/util/UIUtil.kt
  5. 8 1
      app/src/main/res/layout/layout_main_tab.xml
  6. 1 1
      app/src/main/res/values/attrs.xml
  7. 8 0
      app/src/main/res/values/styles.xml
  8. 1 1
      frame/tuikit/TUIConversation/tuiconversation/src/main/java/com/tencent/qcloud/tuikit/tuiconversation/presenter/ConversationPresenter.java
  9. 3 3
      module/im/src/main/java/com/adealink/weparty/im/list/SessionHomeListFragment.kt
  10. 24 31
      module/im/src/main/java/com/adealink/weparty/im/list/SessionListFragment.kt
  11. 16 2
      module/im/src/main/java/com/adealink/weparty/im/list/adapter/data/SessionListData.kt
  12. 25 2
      module/im/src/main/java/com/adealink/weparty/im/list/adapter/viewbinder/SessionListItemViewBinder.kt
  13. 40 3
      module/im/src/main/java/com/adealink/weparty/im/list/viewmodel/SessionListViewModel.kt
  14. 31 5
      module/im/src/main/res/layout/layout_session_list_item.xml
  15. 3 1
      module/playmate/src/main/java/com/adealink/weparty/playmate/data/PlaymateListData.kt
  16. 11 2
      module/playmate/src/main/java/com/adealink/weparty/playmate/list/adapter/PlaymateListItemViewBinder.kt
  17. 20 2
      module/playmate/src/main/res/layout/item_playmate_home_list.xml

+ 12 - 2
app/src/main/java/com/adealink/weparty/commonui/ripple/RippleView.kt

@@ -35,6 +35,7 @@ class RippleView : View {
 
     companion object {
         private const val MAX_ALPHA = 255
+        private const val ALPHA_RANGE = 200
     }
 
     private var paint = Paint()
@@ -80,7 +81,7 @@ class RippleView : View {
                     circleCount = array.getInt(indexedValue, 2)
                 }
 
-                R.styleable.RippleView_ripple_speed -> {
+                R.styleable.RippleView_ripple_circle_speed -> {
                     speed = array.getFloat(indexedValue, speed)
                 }
 
@@ -236,8 +237,17 @@ class RippleView : View {
                     if (it.radius > circleMaxRadius()) {
                         remove()
                     } else {
+                        var length = (circleMaxRadius().toFloat() - circleMinRadius)
+                        if (length <= 0) {
+                            length = 1f
+                        }
+                        var currentLength = it.radius - circleMinRadius
+                        if (currentLength <= 0) {
+                            currentLength = 0f
+                        }
+                        val percent = currentLength / length
                         it.alpha =
-                            (MAX_ALPHA - (it.radius / circleMaxRadius().toFloat()) * MAX_ALPHA).toInt()
+                            (MAX_ALPHA - (percent) * ALPHA_RANGE).toInt()
                     }
                 }
             }

+ 40 - 0
app/src/main/java/com/adealink/weparty/module/im/dot/MessageDot.kt

@@ -0,0 +1,40 @@
+package com.adealink.weparty.module.im.dot
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import androidx.localbroadcastmanager.content.LocalBroadcastManager
+import com.adealink.frame.dot.Dot
+import com.adealink.frame.dot.NumDot
+import com.adealink.frame.util.AppUtil
+import com.tencent.qcloud.tuicore.TUIConstants
+
+val messageDot by lazy {
+    Dot(
+        arrayListOf(
+            messageSessionDot,
+        ),
+        "MessageDot"
+    )
+}
+
+val messageSessionDot by lazy { MessageSessionDot() }
+
+class MessageSessionDot(children: ArrayList<Dot> = arrayListOf()) :
+    Dot(children, "MessageSessionDot") {
+
+    init {
+        val unreadCountReceiver = object : BroadcastReceiver() {
+            override fun onReceive(context: Context?, intent: Intent) {
+                val unreadCount = intent.getLongExtra(TUIConstants.UNREAD_COUNT_EXTRA, 0)
+                show(NumDot(unreadCount.toInt()))
+            }
+        }
+        val unreadCountFilter = IntentFilter()
+        unreadCountFilter.addAction(TUIConstants.CONVERSATION_UNREAD_COUNT_ACTION)
+        LocalBroadcastManager.getInstance(AppUtil.appContext)
+            .registerReceiver(unreadCountReceiver, unreadCountFilter)
+    }
+
+}

+ 14 - 0
app/src/main/java/com/adealink/weparty/ui/main/MainFragment.kt

@@ -25,8 +25,11 @@ import com.adealink.weparty.constant.TAG_TIME_APP_START
 import com.adealink.weparty.constant.logTime
 import com.adealink.weparty.databinding.FragmentMainBinding
 import com.adealink.weparty.databinding.LayoutMainTabBinding
+import com.adealink.weparty.module.im.dot.messageDot
 import com.adealink.weparty.ui.main.tab.ITabManager
 import com.adealink.weparty.ui.main.tab.MAIN_TABS
+import com.adealink.weparty.ui.main.tab.MESSAGE_TAB
+import com.adealink.weparty.ui.main.tab.Tab
 import com.adealink.weparty.ui.main.tab.TabManager
 import com.adealink.weparty.ui.main.viewmodel.MainBottomPage
 import com.adealink.weparty.ui.main.viewmodel.MainPageViewModel
@@ -97,6 +100,9 @@ class MainFragment : BaseFragment(R.layout.fragment_main), ITabManager by TabMan
         ) { tabLayout, position ->
             tabLayout.setCustomView(R.layout.layout_main_tab)
             tabLayout.customView?.let { customView ->
+                val tabBinding = LayoutMainTabBinding.bind(customView)
+                val tab = getTab(position) ?: return@let
+                onConfigureTab(tab, tabBinding)
                 updateTabView(
                     tabLayout,
                     0 == position,
@@ -122,6 +128,14 @@ class MainFragment : BaseFragment(R.layout.fragment_main), ITabManager by TabMan
         setDefaultTab(tabKey = arguments?.getString(AppModule.Main.EXTRA_MAIN_TAB))
     }
 
+    private fun onConfigureTab(tab: Tab, binding: LayoutMainTabBinding) {
+        when (tab) {
+            MESSAGE_TAB -> {
+                binding.vDot.observeDot(viewLifecycleOwner, messageDot)
+            }
+        }
+    }
+
     private fun initBlur() {
         binding.blurBar.setClipToOutline(true)
         binding.blurBar.outlineProvider = object : ViewOutlineProvider() {

+ 27 - 4
app/src/main/java/com/adealink/weparty/util/UIUtil.kt

@@ -54,7 +54,6 @@ fun formatNumberStr(number: Long): String {
     }
 }
 
-
 fun formatNumberStr(number: Double): String {
     return when {
         number < 1000 -> {
@@ -66,15 +65,39 @@ fun formatNumberStr(number: Double): String {
         }
 
         number < 1000_000 -> {
-            String.format(Locale.US, "%.2fK", (number.toFloat() / 1000))
+            String.format(Locale.US, "%.2fK", (number / 1000))
         }
 
         number < 1000_000_000 -> {
-            String.format(Locale.US, "%.2fM", (number.toFloat() / 1000_000))
+            String.format(Locale.US, "%.2fM", (number / 1000_000))
         }
 
         else -> {
-            String.format(Locale.US, "%.2fB", (number.toFloat() / 1000_000_000))
+            String.format(Locale.US, "%.2fB", (number / 1000_000_000))
+        }
+    }
+}
+
+fun formatDistanceStr(distance: Float): String {
+    return when {
+        distance < 1000 -> {
+            if (distance % 1f == 0f) {
+                "%.0fm".format(distance)
+            } else {
+                "%.2fm".format(distance)
+            }
+        }
+
+        distance < 1000_000 -> {
+            String.format(Locale.US, "%.2fKm", (distance / 1000))
+        }
+
+        distance < 1000_000_000 -> {
+            String.format(Locale.US, "%.2fMm", (distance / 1000_000))
+        }
+
+        else -> {
+            String.format(Locale.US, "%.2fBm", (distance / 1000_000_000))
         }
     }
 }

+ 8 - 1
app/src/main/res/layout/layout_main_tab.xml

@@ -12,6 +12,13 @@
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintTop_toTopOf="parent"
-        app:srcCompat="@drawable/common_home_main_tab_selected_ic" />
+        app:srcCompat="@drawable/main_home_select_ic" />
+
+    <com.adealink.frame.dot.DotView
+        android:id="@+id/v_dot"
+        style="@style/CommonRedDot"
+        android:visibility="gone"
+        app:layout_constraintEnd_toEndOf="@id/iv_tab_bg"
+        app:layout_constraintTop_toTopOf="@id/iv_tab_bg" />
 
 </androidx.constraintlayout.widget.ConstraintLayout>

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

@@ -454,7 +454,7 @@
         </attr>
         <attr name="ripple_circle_stroke_width" format="dimension" />
         <attr name="ripple_circle_start" format="boolean" />
-        <attr name="ripple_speed" />
+        <attr name="ripple_circle_speed" format="float" />
     </declare-styleable>
 
     <declare-styleable name="ShimmerFrameLayout">

+ 8 - 0
app/src/main/res/values/styles.xml

@@ -167,4 +167,12 @@
         <item name="android:thumb">@drawable/common_switch_thumb</item>
         <item name="track">@drawable/common_switch_track_sel</item>
     </style>
+
+    <style name="CommonOnlineRipple">
+        <item name="ripple_circle_color">@color/color_FF15E5E2</item>
+        <item name="ripple_circle_style">STROKE</item>
+        <item name="ripple_circle_speed">0.1</item>
+        <item name="ripple_circle_count">2</item>
+        <item name="ripple_circle_stroke_width">2dp</item>
+    </style>
 </resources>

+ 1 - 1
frame/tuikit/TUIConversation/tuiconversation/src/main/java/com/tencent/qcloud/tuikit/tuiconversation/presenter/ConversationPresenter.java

@@ -49,7 +49,7 @@ public class ConversationPresenter {
 
     protected int showType = SHOW_TYPE_CONVERSATION_LIST_WITH_FOLD;
 
-    protected static final int GET_CONVERSATION_COUNT = 20; //最近20
+    protected static final int GET_CONVERSATION_COUNT = 15; //最近15
     protected static final int REFRESH_UNREAD_COUNT_DELAY = 200;
 
     protected ConversationEventListener conversationEventListener;

+ 3 - 3
module/im/src/main/java/com/adealink/weparty/im/list/SessionHomeListFragment.kt

@@ -3,7 +3,7 @@ package com.adealink.weparty.im.list
 import android.os.Bundle
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.core.view.updateLayoutParams
-import androidx.fragment.app.viewModels
+import androidx.fragment.app.activityViewModels
 import com.adealink.frame.aab.util.getCompatDimension
 import com.adealink.frame.mvvm.view.viewBinding
 import com.adealink.frame.router.annotation.RouterUri
@@ -26,7 +26,7 @@ class SessionHomeListFragment : BaseFragment(R.layout.fragment_session_home_list
 
     private val binding by viewBinding(FragmentSessionHomeListBinding::bind)
 
-    private val viewModel by viewModels<SessionListViewModel> { IMViewModelFactory() }
+    private val sessionListViewModel by activityViewModels<SessionListViewModel> { IMViewModelFactory() }
 
     override fun initViews() {
         super.initViews()
@@ -53,7 +53,7 @@ class SessionHomeListFragment : BaseFragment(R.layout.fragment_session_home_list
     }
 
     private fun clearMessage() {
-        viewModel.clearMessage()
+        sessionListViewModel.clearMessage()
     }
 
 }

+ 24 - 31
module/im/src/main/java/com/adealink/weparty/im/list/SessionListFragment.kt

@@ -1,18 +1,13 @@
 package com.adealink.weparty.im.list
 
-import android.content.BroadcastReceiver
-import android.content.Context
-import android.content.Intent
-import android.content.IntentFilter
 import android.view.View
-import androidx.localbroadcastmanager.content.LocalBroadcastManager
+import androidx.fragment.app.activityViewModels
 import androidx.recyclerview.widget.LinearLayoutManager
 import androidx.recyclerview.widget.RecyclerView
 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.RouterUri
-import com.adealink.frame.util.AppUtil
 import com.adealink.weparty.commonui.BaseFragment
 import com.adealink.weparty.commonui.ext.dp
 import com.adealink.weparty.commonui.recycleview.itemdecoration.VerticalSpaceItemDecoration
@@ -24,13 +19,16 @@ import com.adealink.weparty.im.databinding.FragmentSessionListBinding
 import com.adealink.weparty.im.list.adapter.SessionListAdapter
 import com.adealink.weparty.im.list.adapter.viewbinder.OfficialListItemViewBinder
 import com.adealink.weparty.im.list.adapter.viewbinder.SessionListItemViewBinder
+import com.adealink.weparty.im.list.viewmodel.SessionListViewModel
+import com.adealink.weparty.im.viewmodel.IMViewModelFactory
 import com.adealink.weparty.module.im.IM
+import com.adealink.weparty.module.profile.Profile
 import com.tencent.imsdk.v2.V2TIMConversation
-import com.tencent.qcloud.tuicore.TUIConstants
 import com.tencent.qcloud.tuikit.tuiconversation.bean.ConversationInfo
 import com.tencent.qcloud.tuikit.tuiconversation.minimalistui.interfaces.OnConversationAdapterListener
 import com.tencent.qcloud.tuikit.tuiconversation.minimalistui.util.TUIConversationUtils
 import com.tencent.qcloud.tuikit.tuiconversation.presenter.ConversationPresenter
+import kotlin.getValue
 
 
 @RouterUri(
@@ -43,12 +41,11 @@ class SessionListFragment : BaseFragment(R.layout.fragment_session_list),
     private val binding by viewBinding(FragmentSessionListBinding::bind)
     private val presenter: ConversationPresenter by fastLazy { ConversationPresenter() }
     private val sessionAdapter: SessionListAdapter by fastLazy { SessionListAdapter() }
-    private lateinit var unreadCountReceiver: BroadcastReceiver
+    private val sessionListViewModel by activityViewModels<SessionListViewModel> { IMViewModelFactory() }
 
     override fun initViews() {
         super.initViews()
         initSessionList()
-        initUnreadCountReceiver()
     }
 
     override fun initComponents() {
@@ -65,7 +62,9 @@ class SessionListFragment : BaseFragment(R.layout.fragment_session_list),
         )
 
         sessionAdapter.register(OfficialListItemViewBinder(this))
-        sessionAdapter.register(SessionListItemViewBinder(this))
+        sessionAdapter.register(SessionListItemViewBinder(this, { uid ->
+            onAvatarClick(uid)
+        }))
         sessionAdapter.mOnConversationAdapterListener = this
 
         presenter.setAdapter(sessionAdapter)
@@ -73,24 +72,8 @@ class SessionListFragment : BaseFragment(R.layout.fragment_session_list),
         presenter.setShowType(ConversationPresenter.SHOW_TYPE_CONVERSATION_LIST_WITH_FOLD)
         binding.conversationLayout.adapter = sessionAdapter
         binding.conversationLayout.setPresenter(presenter)
-    }
-
-    private fun initUnreadCountReceiver() {
-        unreadCountReceiver = object : BroadcastReceiver() {
-            override fun onReceive(context: Context?, intent: Intent) {
-                val unreadCount = intent.getLongExtra(TUIConstants.UNREAD_COUNT_EXTRA, 0)
-                if (unreadCount > 0) {
-                    // TODO: 更新未读数
-//                    isShowReadAllButton = true
-                } else {
-//                    isShowReadAllButton = false
-                }
-            }
-        }
-        val unreadCountFilter = IntentFilter()
-        unreadCountFilter.addAction(TUIConstants.CONVERSATION_UNREAD_COUNT_ACTION)
-        LocalBroadcastManager.getInstance(AppUtil.appContext)
-            .registerReceiver(unreadCountReceiver, unreadCountFilter)
+        sessionListViewModel.setPresenter(presenter)
+        sessionListViewModel.setPresenter(presenter)
     }
 
     override fun onResume() {
@@ -102,12 +85,14 @@ class SessionListFragment : BaseFragment(R.layout.fragment_session_list),
 
     override fun onDestroyView() {
         super.onDestroyView()
-        unreadCountReceiver?.let { receiver ->
-            LocalBroadcastManager.getInstance(AppUtil.appContext).unregisterReceiver(receiver)
-        }
         presenter.destroy()
     }
 
+    override fun observeViewModel() {
+        super.observeViewModel()
+        sessionListViewModel
+    }
+
     override fun onItemClick(
         view: View?,
         viewType: Int,
@@ -156,6 +141,7 @@ class SessionListFragment : BaseFragment(R.layout.fragment_session_list),
     }
 
     override fun onConversationChanged(dataSource: List<ConversationInfo?>?) {
+        sessionListViewModel.loadConversationUserInfo(dataSource)
     }
 
     //标记为"未读"(无此功能)
@@ -186,4 +172,11 @@ class SessionListFragment : BaseFragment(R.layout.fragment_session_list),
 
     }
 
+    private fun onAvatarClick(uid: String) {
+        val act = activity ?: return
+        Router.build(act, Profile.UserProfile.PATH)
+            .putExtra(Profile.Common.EXTRA_UID, uid)
+            .start()
+    }
+
 }

+ 16 - 2
module/im/src/main/java/com/adealink/weparty/im/list/adapter/data/SessionListData.kt

@@ -1,6 +1,7 @@
 package com.adealink.weparty.im.list.adapter.data
 
 import com.adealink.weparty.commonui.recycleview.diffutil.BaseListItemData
+import com.adealink.weparty.module.profile.data.UserInfo
 import com.tencent.qcloud.tuikit.tuiconversation.bean.ConversationInfo
 
 sealed class SessionListItemData() : BaseListItemData
@@ -10,5 +11,18 @@ data class OfficialSessionListItem(
 ) : SessionListItemData()
 
 data class CommonSessionListItemData(
-    val data: ConversationInfo
-) : SessionListItemData()
+    val data: ConversationInfo,
+    var userInfo: UserInfo? = null,
+    var isOnline: Boolean = false
+) : SessionListItemData() {
+
+    override fun areItemsTheSame(newItem: Any): Boolean {
+        val other = (newItem as? CommonSessionListItemData) ?: return false
+        return data.conversationId == other.data.conversationId
+                && isOnline == other.isOnline
+    }
+
+    override fun areContentsTheSame(newItem: Any): Boolean {
+        return super.areContentsTheSame(newItem)
+    }
+}

+ 25 - 2
module/im/src/main/java/com/adealink/weparty/im/list/adapter/viewbinder/SessionListItemViewBinder.kt

@@ -6,6 +6,8 @@ import com.adealink.frame.base.AppBase
 import com.adealink.frame.data.json.froJsonErrorNull
 import com.adealink.frame.dot.NumDot
 import com.adealink.frame.util.onClick
+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.im.databinding.LayoutSessionListItemBinding
@@ -17,7 +19,8 @@ import com.tencent.qcloud.tuikit.tuiconversation.presenter.ConversationPresenter
 
 
 class SessionListItemViewBinder(
-    val listener: OnConversationAdapterListener
+    val listener: OnConversationAdapterListener,
+    val onAvatarClick: (uid: String) -> Unit
 ) :
     ItemViewBinder<CommonSessionListItemData, BindingViewHolder<LayoutSessionListItemBinding>>() {
 
@@ -28,10 +31,20 @@ class SessionListItemViewBinder(
         holder.binding.root.onClick {
             listener.onItemClick(it, item.data.type, item.data)
         }
+        holder.binding.ivAvatar.setImageUrl(item.userInfo?.avatar)
         holder.binding.ivAvatar.onClick {
+            onAvatarClick.invoke(item.data.id)
+        }
 
+        if (item.isOnline) {
+            holder.binding.vOnline.show()
+            holder.binding.vOnline.onStart()
+        } else {
+            holder.binding.vOnline.gone()
+            holder.binding.vOnline.onStop()
         }
-        holder.binding.tvTitle.text = item.data.title
+
+        holder.binding.tvTitle.text = item.userInfo?.uid ?: item.data.title
         setLastMessageAndStatus(holder, item)
 
         // TODO: 长按测试用
@@ -86,4 +99,14 @@ class SessionListItemViewBinder(
     ): BindingViewHolder<LayoutSessionListItemBinding> {
         return BindingViewHolder(LayoutSessionListItemBinding.inflate(inflater, parent, false))
     }
+
+    override fun onViewDetachedFromWindow(holder: BindingViewHolder<LayoutSessionListItemBinding>) {
+        super.onViewDetachedFromWindow(holder)
+        holder.binding.vOnline.onStop()
+    }
+
+    override fun onViewRecycled(holder: BindingViewHolder<LayoutSessionListItemBinding>) {
+        holder.binding.vOnline.onStop()
+        super.onViewRecycled(holder)
+    }
 }

+ 40 - 3
module/im/src/main/java/com/adealink/weparty/im/list/viewmodel/SessionListViewModel.kt

@@ -1,18 +1,55 @@
 package com.adealink.weparty.im.list.viewmodel
 
-import com.adealink.frame.base.fastLazy
+import com.adealink.frame.coroutine.dispatcher.Dispatcher
 import com.adealink.frame.mvvm.viewmodel.BaseViewModel
+import com.adealink.weparty.im.list.adapter.SessionListAdapter
+import com.adealink.weparty.module.profile.ProfileModule
+import com.adealink.weparty.module.profile.data.UserInfo
+import com.tencent.qcloud.tuikit.tuiconversation.bean.ConversationInfo
 import com.tencent.qcloud.tuikit.tuiconversation.presenter.ConversationPresenter
 import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
 
 class SessionListViewModel : BaseViewModel() {
 
-    private val presenter: ConversationPresenter by fastLazy { ConversationPresenter() }
+    private var presenter: ConversationPresenter? = null
+
+    private var adapter: SessionListAdapter? = null
+
+    fun setPresenter(presenter: ConversationPresenter?) {
+        this.presenter = presenter
+    }
+
+    fun setAdapter(adapter: SessionListAdapter) {
+        this.adapter = adapter
+    }
 
     fun clearMessage() {
         viewModelScope.launch {
-            presenter.clearAllUnreadMessage()
+            presenter?.clearAllUnreadMessage()
         }
     }
 
+    fun loadConversationUserInfo(dataSource: List<ConversationInfo?>?) {
+        dataSource?:return
+        viewModelScope.launch {
+            for (info in dataSource) {
+                info ?: continue
+            }
+            val dataSourceReqList = dataSource.mapNotNull { it }
+            queryUserInfo(dataSourceReqList)
+
+        }
+    }
+
+    private suspend fun queryUserInfo(dataSource: List<ConversationInfo>): Map<String, UserInfo>{
+        val userIds = dataSource.map { it.id }.distinct().toSet()
+        val rlt = ProfileModule.getUsersInfoByUid(userIds)
+        return emptyMap()
+    }
+
+    private suspend fun queryUserOnline(dataSource: List<ConversationInfo>): Map<String, UserInfo>{
+
+    }
+
 }

+ 31 - 5
module/im/src/main/res/layout/layout_session_list_item.xml

@@ -3,15 +3,41 @@
     xmlns:app="http://schemas.android.com/apk/res-auto"
     xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
-    android:layout_height="64dp"
-    android:paddingHorizontal="16dp">
+    android:layout_height="64dp">
+
+    <androidx.constraintlayout.widget.Guideline
+        android:id="@+id/v_start"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        app:layout_constraintGuide_begin="16dp" />
+
+    <androidx.constraintlayout.widget.Guideline
+        android:id="@+id/v_end"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="vertical"
+        app:layout_constraintGuide_end="16dp" />
+
+    <com.adealink.weparty.commonui.ripple.RippleView
+        android:id="@+id/v_online"
+        style="@style/CommonOnlineRipple"
+        android:layout_width="60dp"
+        android:layout_height="60dp"
+        android:visibility="gone"
+        app:layout_constraintBottom_toBottomOf="@id/iv_avatar"
+        app:layout_constraintEnd_toEndOf="@id/iv_avatar"
+        app:layout_constraintStart_toStartOf="@id/iv_avatar"
+        app:layout_constraintTop_toTopOf="@id/iv_avatar"
+        app:ripple_circle_min_radius="23dp"
+        tools:visibility="visible" />
 
     <com.adealink.weparty.commonui.imageview.AvatarView
         android:id="@+id/iv_avatar"
         android:layout_width="46dp"
         android:layout_height="46dp"
         app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintStart_toStartOf="@id/v_start"
         app:layout_constraintTop_toTopOf="parent" />
 
     <androidx.appcompat.widget.AppCompatTextView
@@ -69,7 +95,7 @@
         android:textColor="@color/color_FF86909C"
         android:textSize="12sp"
         app:layout_constraintBottom_toBottomOf="@id/tv_title"
-        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintEnd_toEndOf="@id/v_end"
         app:layout_constraintTop_toTopOf="@id/tv_title"
         tools:text="Now" />
 
@@ -77,7 +103,7 @@
         android:id="@+id/v_dot"
         style="@style/CommonRedDot"
         app:layout_constraintBottom_toBottomOf="@id/tv_desc"
-        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintEnd_toEndOf="@id/v_end"
         app:layout_constraintTop_toTopOf="@id/tv_desc"
         tools:text="Now" />
 

+ 3 - 1
module/playmate/src/main/java/com/adealink/weparty/playmate/data/PlaymateListData.kt

@@ -56,7 +56,9 @@ data class PlaymateListData(
     @SerializedName("labels") val labels: List<String>, //标签
     @SerializedName("categoryName") val categoryName: String, //品类名称
     @SerializedName("categoryIcon") val categoryIcon: String, //品类图标
-    @SerializedName("commentCount") val commentCount: Int, //评价数
+    @SerializedName("orderCount") val commentCount: Int, //评价数
+    @SerializedName("online") val online: Boolean, //是否在线
+    @SerializedName("distance") val distance: Float, //距离
 
 )
 

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

@@ -13,6 +13,7 @@ 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.ItemPlaymateHomeListBinding
+import com.adealink.weparty.util.formatDistanceStr
 import com.adealink.weparty.util.formatStar
 
 class PlaymateListItemViewBinder(val listener: OnPlaymateListListener) :
@@ -34,12 +35,18 @@ class PlaymateListItemViewBinder(val listener: OnPlaymateListListener) :
             holder.binding.vVoice.show()
             holder.binding.vVoice.setSoundUrl(item.data.voice)
         }
-
+        if (item.data.online) {
+            holder.binding.vOnline.show()
+            holder.binding.vOnline.onStart()
+        } else {
+            holder.binding.vOnline.gone()
+            holder.binding.vOnline.onStop()
+        }
 
         holder.binding.tvName.text = item.data.nickname
         holder.binding.vGender.setAge(item.data.age)
         holder.binding.vGender.setGender(item.data.gender)
-        holder.binding.tvLocation.text = item.data.area
+        holder.binding.tvLocation.text = formatDistanceStr(item.data.distance)
         holder.binding.tvStar.text = formatStar(item.data.star)
         holder.binding.tvComment.text =
             getCompatString(R.string.playmate_comment_count, item.data.commentCount.toString())
@@ -81,10 +88,12 @@ class PlaymateListItemViewBinder(val listener: OnPlaymateListListener) :
     override fun onViewDetachedFromWindow(holder: BindingViewHolder<ItemPlaymateHomeListBinding>) {
         super.onViewDetachedFromWindow(holder)
         holder.binding.vVoice.stopPlay()
+        holder.binding.vOnline.onStop()
     }
 
     override fun onViewRecycled(holder: BindingViewHolder<ItemPlaymateHomeListBinding>) {
         holder.binding.vVoice.stopPlay()
+        holder.binding.vOnline.onStop()
         super.onViewRecycled(holder)
     }
 

+ 20 - 2
module/playmate/src/main/res/layout/item_playmate_home_list.xml

@@ -4,13 +4,27 @@
     xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
-    android:background="@drawable/playmate_list_item_bg"
-    android:padding="12dp">
+    android:background="@drawable/playmate_list_item_bg">
+
+    <com.adealink.weparty.commonui.ripple.RippleView
+        android:id="@+id/v_online"
+        style="@style/CommonOnlineRipple"
+        android:layout_width="96dp"
+        android:layout_height="96dp"
+        android:visibility="gone"
+        app:layout_constraintBottom_toBottomOf="@id/iv_avatar"
+        app:layout_constraintEnd_toEndOf="@id/iv_avatar"
+        app:layout_constraintStart_toStartOf="@id/iv_avatar"
+        app:layout_constraintTop_toTopOf="@id/iv_avatar"
+        app:ripple_circle_min_radius="39dp"
+        tools:visibility="visible" />
 
     <com.adealink.weparty.commonui.imageview.AvatarView
         android:id="@+id/iv_avatar"
         android:layout_width="76dp"
         android:layout_height="76dp"
+        android:layout_marginStart="12dp"
+        android:layout_marginTop="12dp"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintTop_toTopOf="parent" />
 
@@ -18,6 +32,7 @@
         android:id="@+id/v_voice"
         android:layout_width="wrap_content"
         android:layout_height="26dp"
+        android:layout_marginBottom="-10dp"
         app:layout_constraintBottom_toBottomOf="@id/iv_avatar"
         app:layout_constraintEnd_toEndOf="@id/iv_avatar"
         app:layout_constraintStart_toStartOf="@id/iv_avatar"
@@ -66,6 +81,7 @@
         android:id="@+id/cl_location"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
+        android:layout_marginEnd="12dp"
         app:layout_constraintBottom_toBottomOf="@id/tv_name"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintTop_toTopOf="@id/tv_name">
@@ -184,6 +200,7 @@
         android:layout_height="wrap_content"
         android:layout_marginStart="12dp"
         android:layout_marginTop="3dp"
+        android:layout_marginEnd="12dp"
         android:background="@drawable/common_playmate_bubble_bg"
         android:ellipsize="end"
         android:includeFontPadding="false"
@@ -200,6 +217,7 @@
         android:layout_height="wrap_content"
         android:layout_marginStart="12dp"
         android:layout_marginTop="6dp"
+        android:layout_marginEnd="12dp"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintStart_toEndOf="@id/v_barrier_start"
         app:layout_constraintTop_toBottomOf="@id/tv_summary">