|
|
@@ -1,16 +1,36 @@
|
|
|
package com.narutohuo.xindazhou.community.ui
|
|
|
|
|
|
+import android.Manifest
|
|
|
+import android.content.Intent
|
|
|
+import android.content.pm.PackageManager
|
|
|
import android.os.Bundle
|
|
|
import android.view.LayoutInflater
|
|
|
import android.view.View
|
|
|
import android.view.ViewGroup
|
|
|
-import android.widget.Button
|
|
|
-import android.widget.LinearLayout
|
|
|
-import android.widget.TextView
|
|
|
import android.widget.Toast
|
|
|
+import androidx.activity.result.contract.ActivityResultContracts
|
|
|
+import androidx.core.content.ContextCompat
|
|
|
+import androidx.fragment.app.viewModels
|
|
|
+import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
|
|
+import androidx.lifecycle.lifecycleScope
|
|
|
+import androidx.recyclerview.widget.LinearLayoutManager
|
|
|
+import com.narutohuo.xindazhou.community.model.Post
|
|
|
+import com.narutohuo.xindazhou.community.model.Topic
|
|
|
+import com.narutohuo.xindazhou.community.ui.adapter.PostAdapter
|
|
|
+import com.narutohuo.xindazhou.community.ui.adapter.TopicAdapter
|
|
|
+import com.narutohuo.xindazhou.community.ui.content.PostDetailActivity
|
|
|
+import com.narutohuo.xindazhou.community.ui.dialog.CityPickerDialog
|
|
|
+import com.narutohuo.xindazhou.community.ui.viewmodel.CommunityState
|
|
|
+import com.narutohuo.xindazhou.community.ui.viewmodel.CommunityViewModel
|
|
|
+import com.narutohuo.xindazhou.community.ui.viewmodel.CommunityViewModelFactory
|
|
|
+import com.narutohuo.xindazhou.community.util.CityCacheHelper
|
|
|
+import com.narutohuo.xindazhou.community.util.LocationHelper
|
|
|
import com.narutohuo.xindazhou.common.ui.BaseFragment
|
|
|
import com.narutohuo.xindazhou.databinding.FragmentCommunityBinding
|
|
|
-import com.narutohuo.xindazhou.qrcode.factory.QRCodeManagerFactory
|
|
|
+import com.narutohuo.xindazhou.share.ShareKit
|
|
|
+import com.narutohuo.xindazhou.share.model.ShareContent
|
|
|
+import com.narutohuo.xindazhou.core.log.ILog
|
|
|
+import kotlinx.coroutines.launch
|
|
|
|
|
|
/**
|
|
|
* 社区 Fragment
|
|
|
@@ -19,75 +39,315 @@ import com.narutohuo.xindazhou.qrcode.factory.QRCodeManagerFactory
|
|
|
*/
|
|
|
class CommunityFragment : BaseFragment<FragmentCommunityBinding>() {
|
|
|
|
|
|
- private val qrCodeManager = QRCodeManagerFactory.getInstance()
|
|
|
+ private val viewModel: CommunityViewModel by viewModels {
|
|
|
+ CommunityViewModelFactory(requireActivity().application)
|
|
|
+ }
|
|
|
+
|
|
|
+ private lateinit var postAdapter: PostAdapter
|
|
|
+ private lateinit var topicAdapter: TopicAdapter
|
|
|
+
|
|
|
+ private val locationHelper by lazy { LocationHelper(requireContext()) }
|
|
|
+ private val cityCacheHelper by lazy { CityCacheHelper(requireContext()) }
|
|
|
+
|
|
|
+ // 定位权限请求
|
|
|
+ private val locationPermissionLauncher = registerForActivityResult(
|
|
|
+ ActivityResultContracts.RequestMultiplePermissions()
|
|
|
+ ) { permissions ->
|
|
|
+ if (permissions[Manifest.permission.ACCESS_FINE_LOCATION] == true ||
|
|
|
+ permissions[Manifest.permission.ACCESS_COARSE_LOCATION] == true) {
|
|
|
+ // 权限已授予,获取定位
|
|
|
+ getCurrentLocation()
|
|
|
+ } else {
|
|
|
+ // 权限被拒绝,使用缓存或默认城市
|
|
|
+ loadCachedCity()
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 用于接收帖子详情页返回的评论数量更新
|
|
|
+ private val postDetailLauncher = registerForActivityResult(
|
|
|
+ ActivityResultContracts.StartActivityForResult()
|
|
|
+ ) { result ->
|
|
|
+ if (result.resultCode == android.app.Activity.RESULT_OK) {
|
|
|
+ val postId = result.data?.getLongExtra("postId", 0L) ?: 0L
|
|
|
+ val commentCount = result.data?.getIntExtra("commentCount", 0) ?: 0
|
|
|
+ if (postId > 0 && commentCount > 0) {
|
|
|
+ // 通过 ViewModel 更新帖子评论数量(符合 MVVM 架构)
|
|
|
+ viewModel.updatePostCommentCount(postId, commentCount)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
override fun getViewBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentCommunityBinding {
|
|
|
return FragmentCommunityBinding.inflate(inflater, container, false)
|
|
|
}
|
|
|
|
|
|
override fun initView() {
|
|
|
- // 创建布局
|
|
|
- val layout = LinearLayout(requireContext()).apply {
|
|
|
- orientation = LinearLayout.VERTICAL
|
|
|
- gravity = android.view.Gravity.CENTER
|
|
|
- setPadding(32, 32, 32, 32)
|
|
|
+ // 初始化帖子列表
|
|
|
+ postAdapter = PostAdapter(
|
|
|
+ onItemClick = { post ->
|
|
|
+ val intent = Intent(requireContext(), PostDetailActivity::class.java)
|
|
|
+ intent.putExtra("postId", post.id)
|
|
|
+ postDetailLauncher.launch(intent)
|
|
|
+ },
|
|
|
+ onLikeClick = { post ->
|
|
|
+ // 点赞功能已移除,保留参数以兼容
|
|
|
+ },
|
|
|
+ onCommentClick = { post ->
|
|
|
+ val intent = Intent(requireContext(), PostDetailActivity::class.java)
|
|
|
+ intent.putExtra("postId", post.id)
|
|
|
+ postDetailLauncher.launch(intent)
|
|
|
+ },
|
|
|
+ onCollectClick = { post ->
|
|
|
+ viewModel.toggleCollect(post)
|
|
|
+ },
|
|
|
+ onShareClick = { post ->
|
|
|
+ sharePost(post)
|
|
|
+ },
|
|
|
+ onFollowClick = { post ->
|
|
|
+ viewModel.toggleFollow(post)
|
|
|
+ }
|
|
|
+ )
|
|
|
+
|
|
|
+ binding.rvPosts.layoutManager = LinearLayoutManager(requireContext())
|
|
|
+ binding.rvPosts.adapter = postAdapter
|
|
|
+
|
|
|
+ // 初始化话题列表
|
|
|
+ topicAdapter = TopicAdapter { topic ->
|
|
|
+ viewModel.selectTopic(topic.id)
|
|
|
+ topicAdapter.setSelectedTopic(topic.id)
|
|
|
}
|
|
|
-
|
|
|
- // 标题
|
|
|
- val textView = TextView(requireContext()).apply {
|
|
|
- text = "社区页面"
|
|
|
- textSize = 18f
|
|
|
- gravity = android.view.Gravity.CENTER
|
|
|
- layoutParams = LinearLayout.LayoutParams(
|
|
|
- LinearLayout.LayoutParams.MATCH_PARENT,
|
|
|
- LinearLayout.LayoutParams.WRAP_CONTENT
|
|
|
- ).apply {
|
|
|
- setMargins(0, 0, 0, 32)
|
|
|
+ binding.rvTopics.layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false)
|
|
|
+ binding.rvTopics.adapter = topicAdapter
|
|
|
+
|
|
|
+ // 下拉刷新
|
|
|
+ binding.swipeRefresh.setOnRefreshListener {
|
|
|
+ viewModel.refresh()
|
|
|
+ }
|
|
|
+ binding.swipeRefresh.setColorSchemeResources(com.narutohuo.xindazhou.R.color.accent_primary)
|
|
|
+
|
|
|
+ // 设置点击事件
|
|
|
+ binding.ivMessage.setOnClickListener {
|
|
|
+ Toast.makeText(requireContext(), "消息功能待实现", Toast.LENGTH_SHORT).show()
|
|
|
+ }
|
|
|
+
|
|
|
+ binding.ivPost.setOnClickListener {
|
|
|
+ val intent = Intent(requireContext(), com.narutohuo.xindazhou.community.ui.content.CreatePostActivity::class.java)
|
|
|
+ startActivity(intent)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 标签切换:推荐、地区、关注
|
|
|
+ binding.tvTabRecommended.setOnClickListener {
|
|
|
+ switchTab(0, "recommended", null)
|
|
|
+ }
|
|
|
+ binding.tvTabCity.setOnClickListener {
|
|
|
+ // 点击地区标签,打开城市选择器
|
|
|
+ showCityPicker()
|
|
|
+ }
|
|
|
+ binding.tvTabFollow.setOnClickListener {
|
|
|
+ // 关注:需要传递当前用户ID,暂时使用null
|
|
|
+ switchTab(2, null, null)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 搜索
|
|
|
+ binding.etSearch.setOnEditorActionListener { _, _, _ ->
|
|
|
+ val keyword = binding.etSearch.text.toString().trim()
|
|
|
+ if (keyword.isNotEmpty()) {
|
|
|
+ viewModel.search(keyword)
|
|
|
}
|
|
|
+ true
|
|
|
}
|
|
|
- layout.addView(textView)
|
|
|
-
|
|
|
- // 扫码按钮
|
|
|
- val scanButton = Button(requireContext()).apply {
|
|
|
- text = "扫码"
|
|
|
- layoutParams = LinearLayout.LayoutParams(
|
|
|
- LinearLayout.LayoutParams.WRAP_CONTENT,
|
|
|
- LinearLayout.LayoutParams.WRAP_CONTENT
|
|
|
- )
|
|
|
- setOnClickListener {
|
|
|
- startScanQRCode()
|
|
|
+
|
|
|
+ // 观察状态
|
|
|
+ observeState()
|
|
|
+
|
|
|
+ // 初始化城市(从缓存或定位获取)
|
|
|
+ initCity()
|
|
|
+
|
|
|
+ // 初始加载
|
|
|
+ viewModel.loadPosts()
|
|
|
+ }
|
|
|
+
|
|
|
+ private fun switchTab(index: Int, category: String?, city: String?) {
|
|
|
+ val tabs = listOf(binding.tvTabRecommended, binding.tvTabCity, binding.tvTabFollow)
|
|
|
+ tabs.forEachIndexed { i, tab ->
|
|
|
+ if (i == index) {
|
|
|
+ tab.setTextColor(requireContext().getColor(com.narutohuo.xindazhou.R.color.accent_primary))
|
|
|
+ tab.setTypeface(null, android.graphics.Typeface.BOLD)
|
|
|
+ tab.background = requireContext().getDrawable(com.narutohuo.xindazhou.R.drawable.bg_tab_selected)
|
|
|
+ } else {
|
|
|
+ tab.setTextColor(requireContext().getColor(com.narutohuo.xindazhou.R.color.text_tertiary))
|
|
|
+ tab.setTypeface(null, android.graphics.Typeface.NORMAL)
|
|
|
+ tab.background = requireContext().getDrawable(com.narutohuo.xindazhou.R.drawable.bg_tab_unselected)
|
|
|
}
|
|
|
}
|
|
|
- layout.addView(scanButton)
|
|
|
|
|
|
- // 将内容添加到布局中
|
|
|
- binding.contentContainer.removeAllViews()
|
|
|
- binding.contentContainer.addView(layout)
|
|
|
+ // 切换分类或城市
|
|
|
+ if (category != null) {
|
|
|
+ viewModel.switchCategory(category)
|
|
|
+ } else if (city != null) {
|
|
|
+ viewModel.setCity(city)
|
|
|
+ } else {
|
|
|
+ // 关注:暂时使用null,后续需要实现关注用户筛选
|
|
|
+ viewModel.switchCategory(null)
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 开始扫码
|
|
|
+ * 初始化城市
|
|
|
*/
|
|
|
- private fun startScanQRCode() {
|
|
|
- qrCodeManager.scanQRCode(requireActivity()) { response ->
|
|
|
- if (response.success) {
|
|
|
- val qrCodeContent = response.data
|
|
|
- Toast.makeText(
|
|
|
- requireContext(),
|
|
|
- "扫码成功:$qrCodeContent",
|
|
|
- Toast.LENGTH_SHORT
|
|
|
- ).show()
|
|
|
- // TODO: 处理扫码结果
|
|
|
- } else {
|
|
|
- val error = response.errorMessage
|
|
|
- if (error != null) {
|
|
|
- Toast.makeText(
|
|
|
- requireContext(),
|
|
|
- "扫码失败:$error",
|
|
|
- Toast.LENGTH_SHORT
|
|
|
- ).show()
|
|
|
+ private fun initCity() {
|
|
|
+ // 先尝试从缓存获取
|
|
|
+ val cachedCity = cityCacheHelper.getCurrentCity() ?: cityCacheHelper.getCachedLocationCity()
|
|
|
+ if (cachedCity != null) {
|
|
|
+ viewModel.setCity(cachedCity)
|
|
|
+ updateCityTabText(cachedCity)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果没有缓存,尝试定位
|
|
|
+ if (locationHelper.hasLocationPermission()) {
|
|
|
+ getCurrentLocation()
|
|
|
+ } else {
|
|
|
+ // 没有权限,显示默认
|
|
|
+ updateCityTabText("全国")
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取当前位置
|
|
|
+ */
|
|
|
+ private fun getCurrentLocation() {
|
|
|
+ lifecycleScope.launch {
|
|
|
+ try {
|
|
|
+ val city = locationHelper.getCurrentCity()
|
|
|
+ if (city != null) {
|
|
|
+ // 保存到缓存
|
|
|
+ cityCacheHelper.saveLocationCity(city)
|
|
|
+ cityCacheHelper.saveCurrentCity(city)
|
|
|
+ viewModel.setCity(city)
|
|
|
+ updateCityTabText(city)
|
|
|
+ } else {
|
|
|
+ // 定位失败,使用默认
|
|
|
+ updateCityTabText("全国")
|
|
|
+ }
|
|
|
+ } catch (e: Exception) {
|
|
|
+ ILog.e("CommunityFragment", "获取定位失败", e)
|
|
|
+ updateCityTabText("全国")
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 加载缓存的城市
|
|
|
+ */
|
|
|
+ private fun loadCachedCity() {
|
|
|
+ val cachedCity = cityCacheHelper.getCurrentCity() ?: cityCacheHelper.getCachedLocationCity()
|
|
|
+ if (cachedCity != null) {
|
|
|
+ viewModel.setCity(cachedCity)
|
|
|
+ updateCityTabText(cachedCity)
|
|
|
+ } else {
|
|
|
+ updateCityTabText("全国")
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 显示城市选择器
|
|
|
+ */
|
|
|
+ private fun showCityPicker() {
|
|
|
+ val dialog = CityPickerDialog.newInstance { city ->
|
|
|
+ cityCacheHelper.saveCurrentCity(city)
|
|
|
+ viewModel.setCity(city)
|
|
|
+ updateCityTabText(city)
|
|
|
+ switchTab(1, null, city)
|
|
|
+ }
|
|
|
+ dialog.show(parentFragmentManager, "CityPickerDialog")
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 更新地区标签文本
|
|
|
+ */
|
|
|
+ private fun updateCityTabText(city: String) {
|
|
|
+ binding.tvTabCity.text = city
|
|
|
+ }
|
|
|
+
|
|
|
+ private fun observeState() {
|
|
|
+ // 观察帖子列表状态
|
|
|
+ lifecycleScope.launch {
|
|
|
+ viewModel.communityState.collect { state ->
|
|
|
+ when (state) {
|
|
|
+ is CommunityState.Idle -> {
|
|
|
+ // 初始状态
|
|
|
+ }
|
|
|
+ is CommunityState.Loading -> {
|
|
|
+ showLoading()
|
|
|
+ binding.swipeRefresh.isRefreshing = true
|
|
|
+ }
|
|
|
+ is CommunityState.Success -> {
|
|
|
+ hideLoading()
|
|
|
+ binding.swipeRefresh.isRefreshing = false
|
|
|
+ postAdapter.submitList(state.posts)
|
|
|
+ }
|
|
|
+ is CommunityState.Error -> {
|
|
|
+ hideLoading()
|
|
|
+ binding.swipeRefresh.isRefreshing = false
|
|
|
+ showError(state.message)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 观察话题列表
|
|
|
+ lifecycleScope.launch {
|
|
|
+ viewModel.topicsState.collect { topics ->
|
|
|
+ topicAdapter.submitList(topics)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 观察当前城市
|
|
|
+ lifecycleScope.launch {
|
|
|
+ viewModel.currentCity.collect { city ->
|
|
|
+ if (city != null) {
|
|
|
+ updateCityTabText(city)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 分享帖子
|
|
|
+ */
|
|
|
+ private fun sharePost(post: Post) {
|
|
|
+ try {
|
|
|
+ val shareService = ShareKit.getInstance()
|
|
|
+
|
|
|
+ // 构建分享链接(TODO: 使用实际的帖子详情页链接)
|
|
|
+ val postUrl = "https://www.xindazhou.com/community/post/${post.id}"
|
|
|
+
|
|
|
+ // 创建分享内容
|
|
|
+ val shareContent = ShareContent.builder()
|
|
|
+ .setTitle(post.title)
|
|
|
+ .setDescription(post.content ?: "")
|
|
|
+ .setUrl(postUrl)
|
|
|
+ .setImageUrl(post.mediaList?.firstOrNull()?.mediaUrl) // 使用第一张图片作为分享图片
|
|
|
+ .build()
|
|
|
+
|
|
|
+ // 调用分享(不指定平台,会显示分享弹窗)
|
|
|
+ // ShareProxyActivity 会自动处理回调并关闭,回调已经在主线程
|
|
|
+ shareService.share(requireActivity(), shareContent) { response ->
|
|
|
+ if (response.success) {
|
|
|
+ val platformName = response.data?.name ?: "未知平台"
|
|
|
+ Toast.makeText(requireContext(), "分享成功:$platformName", Toast.LENGTH_SHORT).show()
|
|
|
+ } else {
|
|
|
+ // 用户取消分享时不显示 Toast
|
|
|
+ if (response.errorMessage != "用户取消分享") {
|
|
|
+ val errorMsg = response.errorMessage ?: "分享失败"
|
|
|
+ Toast.makeText(requireContext(), errorMsg, Toast.LENGTH_SHORT).show()
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
+ } catch (e: Exception) {
|
|
|
+ ILog.e("CommunityFragment", "分享功能异常", e)
|
|
|
+ Toast.makeText(requireContext(), "分享功能异常:${e.message}", Toast.LENGTH_SHORT).show()
|
|
|
}
|
|
|
}
|
|
|
}
|