Browse Source

feat: 个人页交互完善

DoggyZhang 3 months ago
parent
commit
e658ad072c
19 changed files with 397 additions and 69 deletions
  1. 2 2
      app/dependencies/releaseRuntimeClasspath.txt
  2. 1 1
      app/src/main/java/com/adealink/weparty/commonui/imageview/FusionPreviewImageView.kt
  3. 5 4
      app/src/main/java/com/adealink/weparty/commonui/widget/CommonTabLayout.kt
  4. 5 3
      app/src/main/res/layout/layout_common_tab_layout.xml
  5. 1 1
      gradle/libs.versions.toml
  6. 0 10
      module/im/src/main/java/com/adealink/weparty/im/session/widget/MessageSoundPlayView.kt
  7. 28 3
      module/playmate/src/main/java/com/adealink/weparty/playmate/comment/CommentDialog.kt
  8. 20 0
      module/playmate/src/main/java/com/adealink/weparty/playmate/comment/data/CommentData.kt
  9. 80 0
      module/playmate/src/main/java/com/adealink/weparty/playmate/comment/viewmodel/CommentViewModel.kt
  10. 5 0
      module/playmate/src/main/java/com/adealink/weparty/playmate/datasource/remote/PlaymateHttpService.kt
  11. 5 1
      module/playmate/src/main/java/com/adealink/weparty/playmate/viewmodel/PlaymateViewModelFactory.kt
  12. 2 2
      module/playmate/src/main/res/layout/dialog_comment_list.xml
  13. 79 21
      module/profile/src/main/java/com/adealink/weparty/profile/ui/photowall/PhotoWallFragment.kt
  14. 91 10
      module/profile/src/main/java/com/adealink/weparty/profile/ui/photowall/adapter/PhotoItemViewBinder.kt
  15. 13 1
      module/profile/src/main/java/com/adealink/weparty/profile/ui/photowall/data/ProfilePhotoData.kt
  16. 33 0
      module/profile/src/main/java/com/adealink/weparty/profile/ui/photowall/itemdecoration/PhotoWallSpaceItemDecoration.kt
  17. 9 4
      module/profile/src/main/res/layout/activity_user_profile.xml
  18. 5 3
      module/profile/src/main/res/layout/fragment_photo_wall.xml
  19. 13 3
      module/profile/src/main/res/layout/layout_photo_wall_item.xml

+ 2 - 2
app/dependencies/releaseRuntimeClasspath.txt

@@ -232,7 +232,7 @@ com.wenext.android:frame-aab:6.0.3
 com.wenext.android:frame-apm:6.0.1
 com.wenext.android:frame-audio:6.0.0
 com.wenext.android:frame-base:6.0.4
-com.wenext.android:frame-bom:6.1.5
+com.wenext.android:frame-bom:6.1.6
 com.wenext.android:frame-coroutine:6.0.0
 com.wenext.android:frame-crash:6.0.1
 com.wenext.android:frame-data:6.0.0
@@ -244,7 +244,7 @@ com.wenext.android:frame-effect:6.1.0
 com.wenext.android:frame-game:6.0.0
 com.wenext.android:frame-googleservice:6.1.0
 com.wenext.android:frame-guide:6.0.1
-com.wenext.android:frame-image:6.0.6
+com.wenext.android:frame-image:6.0.7
 com.wenext.android:frame-locale:6.0.4
 com.wenext.android:frame-log:6.0.3
 com.wenext.android:frame-media:6.0.5

+ 1 - 1
app/src/main/java/com/adealink/weparty/commonui/imageview/FusionPreviewImageView.kt

@@ -90,7 +90,7 @@ class FusionPreviewImageView : FrameLayout {
                             imageLoadedSuccess = false
                         }
 
-                        override fun onSuccess() {
+                        override fun onSuccess(imageInfo: ImageInfo?) {
                             imageLoadedSuccess = true
                         }
                     }

+ 5 - 4
app/src/main/java/com/adealink/weparty/commonui/widget/CommonTabLayout.kt

@@ -11,6 +11,7 @@ import android.view.LayoutInflater
 import android.view.MotionEvent
 import android.widget.LinearLayout
 import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.core.content.withStyledAttributes
 import androidx.core.view.updateLayoutParams
 import androidx.viewpager2.widget.ViewPager2
 import com.adealink.frame.aab.util.getCompatColor
@@ -79,12 +80,12 @@ class CommonTabLayout @JvmOverloads constructor(
     }
 
     init {
-        val typedArray: TypedArray = context.obtainStyledAttributes(
+        context.withStyledAttributes(
             attrs,
             R.styleable.CommonTabLayout
-        )
-        initTypedArray(typedArray)
-        typedArray.recycle()
+        ) {
+            initTypedArray(this)
+        }
     }
 
     private fun initTypedArray(typedArray: TypedArray) {

+ 5 - 3
app/src/main/res/layout/layout_common_tab_layout.xml

@@ -1,13 +1,15 @@
 <?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="wrap_content"
-    android:layout_height="wrap_content">
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:layout_height="@dimen/common_top_bar_height">
 
     <com.google.android.material.tabs.TabLayout
         android:id="@+id/common_tab_layout"
         android:layout_width="match_parent"
-        android:layout_height="45dp"
+        android:layout_height="match_parent"
         app:layout_constraintEnd_toEndOf="parent"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintTop_toTopOf="parent"

+ 1 - 1
gradle/libs.versions.toml

@@ -157,7 +157,7 @@ appleAppauth = "0.11.1"
 tiktok = "2.3.0"
 
 # frame
-frameBom = "6.1.5"
+frameBom = "6.1.6"
 
 frameRouterCompiler = "6.0.0"
 frameTrace = "1.0.0"

+ 0 - 10
module/im/src/main/java/com/adealink/weparty/im/session/widget/MessageSoundPlayView.kt

@@ -8,7 +8,6 @@ import android.os.Parcelable
 import android.util.AttributeSet
 import android.view.View
 import androidx.core.graphics.toColorInt
-import com.adealink.frame.log.Log
 import com.adealink.weparty.im.R
 
 class MessageSoundPlayView @JvmOverloads constructor(
@@ -191,15 +190,6 @@ class MessageSoundPlayView @JvmOverloads constructor(
         playAnimator = ValueAnimator.ofFloat(playProgress, 1f)
         playAnimator.duration = ((allTime - duration) / speed).toLong()
 
-        Log.d(
-            "zhangfei",
-            "start() called with: allTime = $allTime, duration = $duration, speed = $speed"
-        )
-        Log.d(
-            "zhangfei",
-            "playProgress: $playProgress, duration:${((allTime - duration) / speed).toLong()}"
-        )
-
         playAnimator.addUpdateListener {
             playProgress = it.getAnimatedValue() as Float
             invalidate()

+ 28 - 3
module/playmate/src/main/java/com/adealink/weparty/playmate/comment/CommentDialog.kt

@@ -14,12 +14,14 @@ import com.adealink.weparty.commonui.recycleview.adapter.MultiTypeListAdapter
 import com.adealink.weparty.commonui.widget.BottomDialogFragment
 import com.adealink.weparty.module.playmate.Playmate
 import com.adealink.weparty.playmate.R
+import com.adealink.weparty.playmate.comment.viewmodel.CommentViewModel
 import com.adealink.weparty.playmate.databinding.DialogCommentListBinding
 import com.adealink.weparty.playmate.detail.adapter.PlaymateDetailCommentLabelViewBinder
 import com.adealink.weparty.playmate.detail.adapter.PlaymateDetailCommentViewBinder
 import com.adealink.weparty.playmate.detail.data.BasePlaymateDetailItem
-import com.adealink.weparty.playmate.detail.viewmodel.PlaymateDetailViewModel
 import com.adealink.weparty.playmate.viewmodel.PlaymateViewModelFactory
+import com.scwang.smart.refresh.layout.api.RefreshLayout
+import com.scwang.smart.refresh.layout.listener.OnRefreshLoadMoreListener
 
 @RouterUri(path = [Playmate.Comment.LIST_DIALOG], desc = "评价列表弹窗")
 class CommentDialog : BottomDialogFragment(R.layout.dialog_comment_list) {
@@ -27,7 +29,7 @@ class CommentDialog : BottomDialogFragment(R.layout.dialog_comment_list) {
     private val binding by viewBinding(DialogCommentListBinding::bind)
     private val adapter by fastLazy { MultiTypeListAdapter<BasePlaymateDetailItem>() }
 
-    private val detailViewModel by activityViewModels<PlaymateDetailViewModel> { PlaymateViewModelFactory() }
+    private val commentViewModel by activityViewModels<CommentViewModel> { PlaymateViewModelFactory() }
 
     override val height: Int = (DisplayUtil.getScreenHeight() * 0.85f).toInt()
     override fun onCreate(savedInstanceState: Bundle?) {
@@ -41,8 +43,19 @@ class CommentDialog : BottomDialogFragment(R.layout.dialog_comment_list) {
             dismiss()
         }
 
-        binding
+        binding.refreshLayout.setEnableRefresh(true)
+        binding.refreshLayout.setEnableLoadMore(true)
+        binding.refreshLayout.setEnableAutoLoadMore(true)
+        binding.refreshLayout.setOnRefreshLoadMoreListener(object : OnRefreshLoadMoreListener {
+            override fun onRefresh(refreshLayout: RefreshLayout) {
+                refreshComment()
+            }
 
+
+            override fun onLoadMore(refreshLayout: RefreshLayout) {
+                loadMoreComment()
+            }
+        })
         adapter.register(PlaymateDetailCommentLabelViewBinder())
         adapter.register(PlaymateDetailCommentViewBinder())
         binding.rvComment.adapter = adapter
@@ -51,10 +64,22 @@ class CommentDialog : BottomDialogFragment(R.layout.dialog_comment_list) {
 
     override fun loadData() {
         super.loadData()
+        refreshComment()
+    }
+
+    private fun refreshComment() {
+        commentViewModel.refreshList()
+    }
+
+    private fun loadMoreComment() {
+        commentViewModel.loadMoreList()
     }
 
     override fun observeViewModel() {
         super.observeViewModel()
+        commentViewModel.commentListLD.observe(viewLifecycleOwner) {
+
+        }
     }
 
 }

+ 20 - 0
module/playmate/src/main/java/com/adealink/weparty/playmate/comment/data/CommentData.kt

@@ -0,0 +1,20 @@
+package com.adealink.weparty.playmate.comment.data
+
+import com.adealink.frame.network.data.PageReq
+import com.adealink.weparty.playmate.data.PlaymateCommentData
+import com.google.gson.annotations.GsonNullable
+import com.google.gson.annotations.SerializedName
+
+data class PlaymateCommentReq(
+    @SerializedName("id") val uid: String, //用户ID
+    @SerializedName("categoryId") val categoryId: String, //陪玩商品ID
+
+    @SerializedName("page") val page: PageReq, //游标分页查询
+)
+
+
+data class PlaymateCommentRes(
+    @SerializedName("list") val list: List<PlaymateCommentData>,
+    @GsonNullable
+    @SerializedName("next") val next: String?
+)

+ 80 - 0
module/playmate/src/main/java/com/adealink/weparty/playmate/comment/viewmodel/CommentViewModel.kt

@@ -1,9 +1,89 @@
 package com.adealink.weparty.playmate.comment.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.frame.network.data.PageReq
+import com.adealink.weparty.App
+import com.adealink.weparty.network.data.NoMoreDataError
+import com.adealink.weparty.playmate.comment.data.PlaymateCommentReq
+import com.adealink.weparty.playmate.data.PlaymateCommentData
+import com.adealink.weparty.playmate.datasource.remote.PlaymateHttpService
+import com.adealink.weparty.playmate.detail.data.BasePlaymateDetailItem
+import com.adealink.weparty.playmate.detail.data.PlaymateCommentLabel
+import com.adealink.weparty.util.PageHandler
+import kotlinx.coroutines.launch
 
 class CommentViewModel : BaseViewModel() {
 
 
+    private val playmateHttpService by lazy {
+        App.instance.networkService.getHttpService(PlaymateHttpService::class.java)
+    }
+
+    val labelList = mutableListOf<PlaymateCommentLabel>()
+    val commentList = mutableListOf<PlaymateCommentData>()
+    val commentListLD = MutableLiveData<Rlt<List<BasePlaymateDetailItem>>>()
+    private val pageHandler = PageHandler(pageSize = 20)
+
+    private var uid: String? = null
+    private var categoryCode: String? = null
+
+    fun setPlaymate(uid: String?, categoryCode: String) {
+        this.uid = uid
+        this.categoryCode = categoryCode
+    }
+
+    fun refreshList() {
+        viewModelScope.launch {
+            pageHandler.reset()
+            commentList.clear()
+            pullList()
+        }
+    }
+
+    fun loadMoreList() {
+        viewModelScope.launch {
+            pullList()
+        }
+    }
+
+    private suspend fun pullList() {
+        val uid = uid
+        if (uid.isNullOrEmpty()) {
+            commentListLD.send(Rlt.Failed(CommonDataNullError()))
+            return
+        }
+        val categoryCode = categoryCode
+        if (categoryCode.isNullOrEmpty()) {
+            commentListLD.send(Rlt.Failed(CommonDataNullError()))
+            return
+        }
+        if (pageHandler.isEnd) {
+            commentListLD.send(Rlt.Failed(NoMoreDataError()))
+            return
+        }
+        val rlt = playmateHttpService.getPlaymateComment(
+            PlaymateCommentReq(
+                uid = uid,
+                categoryId = categoryCode,
+                page = PageReq(size = pageHandler.pageSize, next = pageHandler.currentPage)
+            )
+        )
+        when (rlt) {
+            is Rlt.Failed -> {
+//                listRltLD.send(rlt)
+            }
+
+            is Rlt.Success -> {
+//                pageHandler.nextPage(rlt.data.data?.next)
+//                playmateList.addAll(rlt.data.data?.list ?: emptyList())
+//                listRltLD.send(rlt)
+//                listLD.send(playmateList)
+            }
+        }
+    }
+
 
 }

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

@@ -2,6 +2,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.comment.data.PlaymateCommentReq
+import com.adealink.weparty.playmate.comment.data.PlaymateCommentRes
 import com.adealink.weparty.playmate.data.PlaymateCategoryRes
 import com.adealink.weparty.playmate.data.PlaymateDetailReq
 import com.adealink.weparty.playmate.data.PlaymateDetailRes
@@ -30,4 +32,7 @@ interface PlaymateHttpService {
     @POST("skill/user/goods")
     suspend fun getUserPlaymateCategory(@Body req: UserPlaymateCategoryReq): Rlt<Res<UserPlaymateCategoryRes>>
 
+    @POST("skill/user/goods")
+    suspend fun getPlaymateComment(@Body req: PlaymateCommentReq): Rlt<Res<PlaymateCommentRes>>
+
 }

+ 5 - 1
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.comment.viewmodel.CommentViewModel
 import com.adealink.weparty.playmate.detail.viewmodel.PlaymateDetailViewModel
 import com.adealink.weparty.playmate.list.viewmodel.GuestPlaymateListViewModel
 import com.adealink.weparty.playmate.list.viewmodel.PlaymateListViewModel
@@ -22,7 +23,10 @@ class PlaymateViewModelFactory : ViewModelProvider.NewInstanceFactory() {
                     PlaymateDetailViewModel()
 
                 isAssignableFrom(GuestPlaymateListViewModel::class.java) ->
-                    GuestPlaymateListViewModel()
+                   GuestPlaymateListViewModel()
+
+                isAssignableFrom(CommentViewModel::class.java) ->
+                    CommentViewModel()
 
                 else ->
                     throw IllegalArgumentException("Unknown ViewModel class: ${modelClass.name}")

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

@@ -38,7 +38,7 @@
             app:srcCompat="@drawable/common_close_ic" />
     </androidx.constraintlayout.widget.ConstraintLayout>
 
-    <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
+    <com.scwang.smart.refresh.layout.SmartRefreshLayout
         android:id="@+id/refresh_layout"
         android:layout_width="0dp"
         android:layout_height="0dp"
@@ -55,7 +55,7 @@
             android:layout_height="match_parent"
             tools:listitem="@layout/item_playmate_detail_comment" />
 
-    </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
+    </com.scwang.smart.refresh.layout.SmartRefreshLayout>
 
 
 </androidx.constraintlayout.widget.ConstraintLayout>

+ 79 - 21
module/profile/src/main/java/com/adealink/weparty/profile/ui/photowall/PhotoWallFragment.kt

@@ -5,44 +5,102 @@ import androidx.recyclerview.widget.StaggeredGridLayoutManager
 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.MultiTypeListAdapter
 import com.adealink.weparty.profile.R
 import com.adealink.weparty.profile.databinding.FragmentPhotoWallBinding
 import com.adealink.weparty.profile.ui.photowall.adapter.PhotoItemViewBinder
 import com.adealink.weparty.profile.ui.photowall.data.PhotoItemData
 import com.adealink.weparty.profile.ui.photowall.data.ProfilePhotoData
+import com.adealink.weparty.profile.ui.photowall.itemdecoration.SpacingItemDecoration
+import com.adealink.weparty.util.goImagePreviewActivity
 
 class PhotoWallFragment : BaseFragment(R.layout.fragment_photo_wall) {
 
+    companion object {
+        private const val SPAN_COUNT = 2
+
+        private const val PHOTO_CACHE_SIZE = 10
+    }
+
     private val binding by viewBinding(FragmentPhotoWallBinding::bind)
     private val listAdapter by fastLazy { MultiTypeListAdapter<PhotoItemData>() }
 
+    private val photos = mutableListOf<ProfilePhotoData>().apply {
+        // TODO: 测试用图 
+        addAll(
+            listOf(
+                ProfilePhotoData("https://im.sdk.qcloud.com/download/tuikit-resource/community-cover/community_cover_default.png"),
+                ProfilePhotoData("https://gd-hbimg-edge.huabanimg.com/18e1e5b28cf5ff377db4082be5886c6d7dc49653af22-qNNo8A_fw1200webp?auth_key=1765512000-9184c35b89a54462bd0d88946c7592c6-0-027aeec13b8ccaed2965fe664a8d7f43"),
+                ProfilePhotoData("https://gd-hbimg-edge.huabanimg.com/21789db55e9b888df6b79dacaa48b9f3cf47a42af13e-cngEKs_fw1200?auth_key=1765512000-9184c35b89a54462bd0d88946c7592c6-0-840223cb015b67e528f6f72a31269778"),
+                ProfilePhotoData("https://gd-hbimg-edge.huabanimg.com/a90e3901a7aeeed6c3b9d89fc570edafaaa9fab8b8b93-e7K0KX?auth_key=1765526400-17e71cd1259e4e38b2cbc1aeea532fab-0-e63eabe78de13504a0ec1d8c4683aaf9"),
+                ProfilePhotoData("https://gd-hbimg-edge.huabanimg.com/f8902bc0513d050c44f63fbe739d75dce674a65434605-iJXDh2_fw480webp?auth_key=1765512000-9184c35b89a54462bd0d88946c7592c6-0-80bfa288b5311adea8277f706059b2e5"),
+                ProfilePhotoData("https://gd-hbimg-edge.huabanimg.com/909ca587893946922aca6c048d54d117baf9d9d811fd4-MXbrq7_fw1200?auth_key=1765512000-9184c35b89a54462bd0d88946c7592c6-0-93ee8d59c411a70418e6d39e858d6770"),
+                ProfilePhotoData("https://gd-hbimg-edge.huabanimg.com/small/f439c4f1663ad24385c785414997112b1acbd9c2a1096-LjLsoi_fw480webp?auth_key=1765512000-9184c35b89a54462bd0d88946c7592c6-0-dea385ba1a8c64fab1746e8f9895e6fd"),
+                ProfilePhotoData("https://gd-hbimg-edge.huabanimg.com/af0dc405a177ca9726729613ab1a163e2fd4969e6d89e3-kjFZfs_fw480webp?auth_key=1765512000-9184c35b89a54462bd0d88946c7592c6-0-3ff4c3dd9da13d47974de75e4d8a0408"),
+                ProfilePhotoData("https://gd-hbimg-edge.huabanimg.com/e23678e11bd4284b786eb5bac871db1bc44fbd5f2db89a-tBkueA_fw480webp?auth_key=1765512000-9184c35b89a54462bd0d88946c7592c6-0-fedf31e0bad3ea8daa7a93d2d842f9d6"),
+                ProfilePhotoData("https://gd-hbimg-edge.huabanimg.com/8f2e1e1054c42a84974c62ae29a7caabfda81ba0d8496-HHWevQ_fw480webp?auth_key=1765512000-9184c35b89a54462bd0d88946c7592c6-0-b532835de02a97b48bb16878949bd2a6"),
+                ProfilePhotoData("https://gd-hbimg-edge.huabanimg.com/070beae99e92c4c862b2f91e920abc549f3402fd8c628-wEvP0F_fw480webp?auth_key=1765512000-9184c35b89a54462bd0d88946c7592c6-0-5c0818110169156467eb28bf7a44ecb5"),
+                ProfilePhotoData("https://gd-hbimg-edge.huabanimg.com/b754ef05ca83c5379bbb7c262632d7c05f7e350cd57c9a-LaYrTb_fw480webp?auth_key=1765512000-9184c35b89a54462bd0d88946c7592c6-0-27f287b1ac251189b1df733df47f7a4b"),
+                ProfilePhotoData("https://gd-hbimg-edge.huabanimg.com/5be7d808fd754e21e62aea6668379c333ae1cfcaa1df-mZuKL3_fw480webp?auth_key=1765512000-9184c35b89a54462bd0d88946c7592c6-0-f00230b4b9842e8076ed021b861b5e52"),
+                ProfilePhotoData("https://gd-hbimg-edge.huabanimg.com/dd65cc7f807797def11a224daebd1fc6e4b9603157cad-vNKOJa_fw480webp?auth_key=1765512000-9184c35b89a54462bd0d88946c7592c6-0-c4045bd6b85d4caa127f0f14810f7a9b"),
+                ProfilePhotoData("https://gd-hbimg-edge.huabanimg.com/4def6e640ea4737e1f54a08f3777e3d11cc081aa34e70-hV0bZJ_fw480webp?auth_key=1765526400-17e71cd1259e4e38b2cbc1aeea532fab-0-89ea2e60dbbe49330c96aa2bac32a799"),
+                ProfilePhotoData("https://gd-hbimg-edge.huabanimg.com/5b12b76272054c2a34f8a80aab464efaef3686bf10b20-qrmoDy_fw480webp?auth_key=1765526400-17e71cd1259e4e38b2cbc1aeea532fab-0-c53968e241a9749718d4b0a688a77c8a"),
+                ProfilePhotoData("https://gd-hbimg-edge.huabanimg.com/small/ef30be3f8517419a49f7bc8fe968677ecaa7eedd7142d-NdwnKY_fw480webp?auth_key=1765526400-17e71cd1259e4e38b2cbc1aeea532fab-0-f93953bac6e93223b5d6cd18b2f35e3c"),
+                ProfilePhotoData("https://gd-hbimg-edge.huabanimg.com/9f43114a86ce1c5d29cf3cf006bff83ade4f4ae6d4abc-RWXLlW_fw1200webp?auth_key=1765526400-17e71cd1259e4e38b2cbc1aeea532fab-0-66fa9397ddbff3820d6bfaf18a47a62f"),
+                ProfilePhotoData("https://gd-hbimg-edge.huabanimg.com/2b2aaf5d9fa2f038be14a7c42dae7a51f57c666f3bd7ea-BShVX5_fw480webp?auth_key=1765526400-17e71cd1259e4e38b2cbc1aeea532fab-0-8b4ec49b3848811ebcf79860296b570d"),
+                ProfilePhotoData("https://gd-hbimg-edge.huabanimg.com/eea81430eed8e1357186451114c13d0b7959b49a62e8-NOGOED_fw480webp?auth_key=1765526400-17e71cd1259e4e38b2cbc1aeea532fab-0-d8c04e8af77f5e450b20c334f37bbe85"),
+                ProfilePhotoData("https://gd-hbimg-edge.huabanimg.com/1f42990dfd2bceae6750b61e3e092eef3065509814774-BYU1dm_fw480webp?auth_key=1765512000-9184c35b89a54462bd0d88946c7592c6-0-5be113f9345f5dda8c638aab7953c874"),
+                ProfilePhotoData("https://gd-hbimg-edge.huabanimg.com/5ece5eb3c0bf1d282c637b1af4a8dbcff99d76d31de99-qGeye6_fw480webp?auth_key=1765512000-9184c35b89a54462bd0d88946c7592c6-0-8617e80f4e96e647376044795d5af934"),
+            )
+        )
+    }
+
     override fun initViews() {
         super.initViews()
-        listAdapter.register(PhotoItemViewBinder())
-        binding.rvPhotos.layoutManager = StaggeredGridLayoutManager(2, RecyclerView.VERTICAL)
-        binding.rvPhotos.adapter = listAdapter
+        listAdapter.register(PhotoItemViewBinder { data, position ->
+            goImgPreview(data, position)
+        })
+        binding.rvPhotos.layoutManager =
+            StaggeredGridLayoutManager(SPAN_COUNT, RecyclerView.VERTICAL)
 
-        listAdapter.submitList(
-            listOf(
-                PhotoItemData(ProfilePhotoData("https://im.sdk.qcloud.com/download/tuikit-resource/community-cover/community_cover_default.png")),
-                PhotoItemData(ProfilePhotoData("https://im.sdk.qcloud.com/download/tuikit-resource/community-cover/community_cover_default.png")),
-                PhotoItemData(ProfilePhotoData("https://im.sdk.qcloud.com/download/tuikit-resource/community-cover/community_cover_default.png")),
-                PhotoItemData(ProfilePhotoData("https://im.sdk.qcloud.com/download/tuikit-resource/community-cover/community_cover_default.png")),
-                PhotoItemData(ProfilePhotoData("https://im.sdk.qcloud.com/download/tuikit-resource/community-cover/community_cover_default.png")),
-                PhotoItemData(ProfilePhotoData("https://im.sdk.qcloud.com/download/tuikit-resource/community-cover/community_cover_default.png")),
-                PhotoItemData(ProfilePhotoData("https://im.sdk.qcloud.com/download/tuikit-resource/community-cover/community_cover_default.png")),
-                PhotoItemData(ProfilePhotoData("https://im.sdk.qcloud.com/download/tuikit-resource/community-cover/community_cover_default.png")),
-                PhotoItemData(ProfilePhotoData("https://im.sdk.qcloud.com/download/tuikit-resource/community-cover/community_cover_default.png")),
-                PhotoItemData(ProfilePhotoData("https://im.sdk.qcloud.com/download/tuikit-resource/community-cover/community_cover_default.png")),
-                PhotoItemData(ProfilePhotoData("https://im.sdk.qcloud.com/download/tuikit-resource/community-cover/community_cover_default.png")),
-                PhotoItemData(ProfilePhotoData("https://im.sdk.qcloud.com/download/tuikit-resource/community-cover/community_cover_default.png")),
-                PhotoItemData(ProfilePhotoData("https://im.sdk.qcloud.com/download/tuikit-resource/community-cover/community_cover_default.png")),
-                PhotoItemData(ProfilePhotoData("https://im.sdk.qcloud.com/download/tuikit-resource/community-cover/community_cover_default.png")),
-                PhotoItemData(ProfilePhotoData("https://im.sdk.qcloud.com/download/tuikit-resource/community-cover/community_cover_default.png")),
-                PhotoItemData(ProfilePhotoData("https://im.sdk.qcloud.com/download/tuikit-resource/community-cover/community_cover_default.png")),
+        binding.rvPhotos.setHasFixedSize(true)
+
+        binding.rvPhotos.itemAnimator = null
+        binding.rvPhotos.setItemViewCacheSize(PHOTO_CACHE_SIZE)
+        binding.rvPhotos.adapter = listAdapter
+        binding.rvPhotos.addItemDecoration(
+            SpacingItemDecoration(
+                spanCount = SPAN_COUNT,
+                spacingH = 9.dp(),
+                spacingV = 10.dp()
             )
         )
+
+        listAdapter.submitList(photos.map { PhotoItemData(it) })
+    }
+
+    override fun onPause() {
+        super.onPause()
+        // 应用进入后台时清理缓存
+        binding.rvPhotos.setItemViewCacheSize(0)
+    }
+
+    override fun onResume() {
+        super.onResume()
+        // 恢复缓存大小
+        binding.rvPhotos.setItemViewCacheSize(PHOTO_CACHE_SIZE)
+    }
+
+    private fun goImgPreview(item: PhotoItemData, position: Int) {
+        val act = activity ?: return
+        goImagePreviewActivity(
+            activity = act,
+            imageUriList = arrayListOf<String>().apply {
+                addAll(photos.map { it.url })
+            },
+            currentIndex = position
+        )
     }
 
 }

+ 91 - 10
module/profile/src/main/java/com/adealink/weparty/profile/ui/photowall/adapter/PhotoItemViewBinder.kt

@@ -2,25 +2,106 @@ package com.adealink.weparty.profile.ui.photowall.adapter
 
 import android.view.LayoutInflater
 import android.view.ViewGroup
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.core.view.updateLayoutParams
+import com.adealink.frame.image.view.NetworkImageView
+import com.adealink.frame.util.onClick
+import com.adealink.frame.util.runOnUiThread
+import com.adealink.weparty.commonui.ext.isViewValid
 import com.adealink.weparty.commonui.recycleview.adapter.BindingViewHolder
 import com.adealink.weparty.commonui.recycleview.adapter.multitype.ItemViewBinder
 import com.adealink.weparty.profile.databinding.LayoutPhotoWallItemBinding
 import com.adealink.weparty.profile.ui.photowall.data.PhotoItemData
+import com.facebook.imagepipeline.image.ImageInfo
 
-class PhotoItemViewBinder :
-    ItemViewBinder<PhotoItemData, BindingViewHolder<LayoutPhotoWallItemBinding>>() {
+class PhotoItemViewBinder(
+    val onClick: (item: PhotoItemData, position: Int) -> Unit
+) : ItemViewBinder<PhotoItemData, PhotoItemViewBinder.ViewHolder>() {
+
+    override fun onCreateViewHolder(
+        inflater: LayoutInflater,
+        parent: ViewGroup,
+    ): ViewHolder {
+        return ViewHolder(LayoutPhotoWallItemBinding.inflate(inflater, parent, false))
+    }
 
     override fun onBindViewHolder(
-        holder: BindingViewHolder<LayoutPhotoWallItemBinding>,
-        item: PhotoItemData,
+        holder: ViewHolder,
+        item: PhotoItemData
     ) {
-        holder.binding.root.setImageUrl(item.data.url)
+        holder.binding.root.onClick {
+            onClick.invoke(item, holder.layoutPosition)
+        }
+        holder.update(item)
     }
 
-    override fun onCreateViewHolder(
-        inflater: LayoutInflater,
-        parent: ViewGroup,
-    ): BindingViewHolder<LayoutPhotoWallItemBinding> {
-        return BindingViewHolder(LayoutPhotoWallItemBinding.inflate(inflater, parent, false))
+    class ViewHolder(
+        rootBinding: LayoutPhotoWallItemBinding,
+    ) : BindingViewHolder<LayoutPhotoWallItemBinding>(rootBinding) {
+
+        private var item: PhotoItemData? = null
+
+        inner class IImageLoadResult(
+            val item: PhotoItemData,
+            val onSuccessResult: (imageInfo: ImageInfo?) -> Unit
+        ) : NetworkImageView.IImageLoadResultListener {
+            override fun onFailed() {
+
+            }
+
+            override fun onSuccess(imageInfo: ImageInfo?) {
+                if (this@ViewHolder.item != item) {
+                    return
+                }
+                item.photoWidth = imageInfo?.width
+                item.photoHeight = imageInfo?.height
+                onSuccessResult.invoke(imageInfo)
+            }
+
+        }
+
+        fun update(item: PhotoItemData) {
+            this.item = item
+            val photoRatio = item.getPhotoRatio()
+            val imageView = binding.img
+            if (photoRatio.isNullOrEmpty()) {
+                //图片需要测量
+                imageView.setImageUrl(
+                    item.data.url,
+                    IImageLoadResult(item) { imageInfo ->
+                        runOnUiThread {
+                            if (isViewValid()) {
+                                updateImageViewLp(
+                                    imageView,
+                                    imageInfo?.width,
+                                    imageInfo?.height
+                                )
+                            }
+                        }
+                    })
+            } else {
+                //图片已经测量过
+                imageView.setImageLoadResultListener(null)
+                imageView.setImageUrl(item.data.url)
+                imageView.updateLayoutParams<ConstraintLayout.LayoutParams> {
+                    dimensionRatio = photoRatio
+                }
+            }
+        }
+
+        private fun updateImageViewLp(
+            imageView: NetworkImageView,
+            imageWidth: Int?,
+            imageHeight: Int?
+        ) {
+            val width = imageWidth ?: 1
+            val height = imageHeight ?: 1
+            val imageWidth = width.coerceIn(1, width)
+            val imageHeight = height.coerceIn(1, height)
+            imageView.updateLayoutParams<ConstraintLayout.LayoutParams> {
+                dimensionRatio = "${imageWidth}:${imageHeight}"
+            }
+        }
+
     }
 }

+ 13 - 1
module/profile/src/main/java/com/adealink/weparty/profile/ui/photowall/data/ProfilePhotoData.kt

@@ -7,4 +7,16 @@ data class ProfilePhotoData(
 
 data class PhotoItemData(
     val data: ProfilePhotoData
-)
+) {
+    var photoWidth: Int? = null
+    var photoHeight: Int? = null
+
+    fun getPhotoRatio(): String? {
+        val width = photoWidth ?: return null
+        val height = photoHeight ?: return null
+        if (width <= 0 || height <= 0) {
+            return null
+        }
+        return "${width}:${height}"
+    }
+}

+ 33 - 0
module/profile/src/main/java/com/adealink/weparty/profile/ui/photowall/itemdecoration/PhotoWallSpaceItemDecoration.kt

@@ -0,0 +1,33 @@
+package com.adealink.weparty.profile.ui.photowall.itemdecoration
+
+import android.graphics.Rect
+import android.view.View
+import androidx.recyclerview.widget.RecyclerView
+
+class SpacingItemDecoration(
+    var spanCount: Int,
+    var spacingH: Int,
+    private val spacingV: Int,
+) : RecyclerView.ItemDecoration() {
+
+    override fun getItemOffsets(
+        outRect: Rect,
+        view: View,
+        parent: RecyclerView,
+        state: RecyclerView.State
+    ) {
+        super.getItemOffsets(outRect, view, parent, state)
+
+        val position = parent.getChildAdapterPosition(view)
+
+        val column = position % spanCount
+
+        outRect.left = spacingH - column * spacingH / spanCount
+        outRect.right = (column + 1) * spacingH / spanCount
+
+        if (position < spanCount) {
+            outRect.top = spacingV
+        }
+        outRect.bottom = spacingV
+    }
+}

+ 9 - 4
module/profile/src/main/res/layout/activity_user_profile.xml

@@ -3,8 +3,7 @@
     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="@color/black">
+    android:layout_height="match_parent">
 
     <com.google.android.material.appbar.AppBarLayout
         android:id="@+id/appBarLayout"
@@ -40,19 +39,25 @@
             android:layout_width="match_parent"
             android:layout_height="50dp"
             app:indicator_color="@color/color_222222"
+            app:indicator_drawable="@drawable/profile_indicator"
+            app:indicator_height="2dp"
+            app:indicator_width_mode="match_parent"
             app:layout_constraintStart_toStartOf="parent"
             app:layout_constraintTop_toTopOf="parent"
             app:layout_scrollFlags="scroll|exitUntilCollapsed"
             app:normal_tab_color="@color/color_999999"
             app:normal_text_size="14sp"
-            app:indicator_drawable="@drawable/profile_indicator"
-            app:indicator_width_mode="match_parent"
             app:selected_tab_color="@color/color_222222"
             app:selected_text_size="14sp"
             app:tab_mode="fixed"
             app:tab_type="match_parent"
             tools:background="@drawable/profile_tab_bg" />
 
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="0.5dp"
+            android:background="@color/color_FFF2F3F5" />
+
     </com.google.android.material.appbar.AppBarLayout>
 
     <androidx.viewpager2.widget.ViewPager2

+ 5 - 3
module/profile/src/main/res/layout/fragment_photo_wall.xml

@@ -1,12 +1,14 @@
 <?xml version="1.0" encoding="utf-8"?>
 <androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:background="@color/color_18E3EB">
+    android:layout_height="match_parent">
 
     <androidx.recyclerview.widget.RecyclerView
         android:id="@+id/rv_photos"
         android:layout_width="match_parent"
-        android:layout_height="match_parent" />
+        android:layout_height="match_parent"
+        tools:itemCount="3"
+        tools:listitem="@layout/layout_photo_wall_item" />
 
 </androidx.coordinatorlayout.widget.CoordinatorLayout>

+ 13 - 3
module/profile/src/main/res/layout/layout_photo_wall_item.xml

@@ -1,6 +1,16 @@
 <?xml version="1.0" encoding="utf-8"?>
-<com.adealink.frame.image.view.NetworkImageView xmlns:android="http://schemas.android.com/apk/res/android"
+<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="match_parent"
-    app:roundedCornerRadius="12dp" />
+    android:layout_height="wrap_content">
+
+    <com.adealink.frame.image.view.NetworkImageView
+        android:id="@+id/img"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        app:layout_constraintDimensionRatio="1:1"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:roundedCornerRadius="12dp" />
+</androidx.constraintlayout.widget.ConstraintLayout>