Skip to content

Commit

Permalink
basic user reply in profile
Browse files Browse the repository at this point in the history
  • Loading branch information
5ec1cff committed Sep 26, 2023
1 parent 3df7923 commit e2bb378
Show file tree
Hide file tree
Showing 13 changed files with 402 additions and 8 deletions.
8 changes: 8 additions & 0 deletions app/src/main/java/io/github/a13e300/ro_tieba/Emotions.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.github.a13e300.ro_tieba

import java.util.concurrent.ConcurrentHashMap

data class Emotion(
val name: String,
val id: String,
Expand Down Expand Up @@ -1018,4 +1020,10 @@ object Emotions {
Emotion("#(白眼)", "image_emoticon124", R.drawable.emoji_image_emoticon124)
)
}
private val emotionNameMap = ConcurrentHashMap<String, Emotion?>()
fun getEmotionForName(name: String): Emotion? {
return emotionNameMap.computeIfAbsent(name) {
emotionMap.firstNotNullOfOrNull { (_, v) -> if (v.name == name) v else null }
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,13 @@ class TiebaClient(val account: Account = Account()) {
return result.data
}

suspend fun getUserPosts(uid: Long, pn: Int) =
jsonAPI.userPost(uid.toString(), pn, 0)

fun getUserPostsSync(uid: Long, pn: Int) = runBlocking {
getUserPosts(uid, pn)
}

// for debug

fun getThreadsSync(fname: String, pn: Int) = runBlocking {
Expand Down
15 changes: 15 additions & 0 deletions app/src/main/java/io/github/a13e300/ro_tieba/api/TiebaJsonAPI.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package io.github.a13e300.ro_tieba.api

import io.github.a13e300.ro_tieba.api.json.GetFollowForums
import io.github.a13e300.ro_tieba.api.json.SearchForumPost
import io.github.a13e300.ro_tieba.api.json.UserPostResponse
import io.github.a13e300.ro_tieba.utils.toHexString
import okhttp3.FormBody
import retrofit2.http.Field
Expand Down Expand Up @@ -29,6 +30,20 @@ interface TiebaJsonAPI {
@Field("rn") rn: Int,
@Field("sm") order: String
): SearchForumPost

@FormUrlEncoded
@POST("/c/u/feed/userpost")
suspend fun userPost(
@Field("uid") uid: String,
@Field("pn") page: Int = 1,
@Field("is_thread") isThread: Int,
@Field("rn") pageSize: Int = 20,
@Field("need_content") need_content: Int = 1,
// We must use lower version, so that we can get posts
@Field("_client_version") _client_version: String = "7.2.0.0",
@Field("_client_type") _client_type: String = "2",
@Field("subapp_type") _subapp_type: String = "mini"
): UserPostResponse
}

fun FormBody.toSignatureBody(bduss: String? = null): FormBody {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,17 @@ class IntStringAdapter : JsonDeserializer<Any?> {
context: JsonDeserializationContext
): Any? {
if (json is JsonPrimitive) {
if (json.isNumber)
return json.asInt
else if (json.isString)
return json.asString.toInt()
if (json.isNumber) {
if (typeOfT == Integer.TYPE || typeOfT == Integer::class.java)
return json.asInt
else if (typeOfT == java.lang.Long.TYPE || typeOfT == java.lang.Long::class.java)
return json.asLong
} else if (json.isString) {
if (typeOfT == Integer.TYPE || typeOfT == Integer::class.java)
return json.asString.toInt()
else if (typeOfT == java.lang.Long.TYPE || typeOfT == java.lang.Long::class.java)
return json.asString.toLong()
}
return context.deserialize(json, typeOfT)
}
return null
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package io.github.a13e300.ro_tieba.api.json

import com.google.gson.annotations.JsonAdapter
import com.google.gson.annotations.SerializedName
import io.github.a13e300.ro_tieba.api.adapters.IntBooleanAdapter
import io.github.a13e300.ro_tieba.api.adapters.IntStringAdapter

data class UserPostResponse(
@JsonAdapter(IntBooleanAdapter::class)
@SerializedName("hide_post")
val hidePost: Boolean,
@SerializedName("post_list")
val postList: List<Post> // post for each forum
) {
data class Post(
val content: List<Content>,
@SerializedName("forum_id")
@JsonAdapter(IntStringAdapter::class)
val forumId: Long,
@SerializedName("forum_name")
val forumName: String,
@JsonAdapter(IntStringAdapter::class)
@SerializedName("freq_num")
val freqNum: Int,
@JsonAdapter(IntBooleanAdapter::class)
@SerializedName("is_thread")
val isThread: Boolean,
@JsonAdapter(IntStringAdapter::class)
@SerializedName("thread_id")
val threadId: Long,
@JsonAdapter(IntStringAdapter::class)
@SerializedName("thread_type")
val threadType: Int,
val title: String,
@JsonAdapter(IntStringAdapter::class)
@SerializedName("reply_num")
val replyNum: Int,
@JsonAdapter(IntStringAdapter::class)
@SerializedName("create_time")
val createTime: Long,
val quota: Quota?
// user_id, user_name, user_portrait are useless
)

data class Content(
@JsonAdapter(IntStringAdapter::class)
@SerializedName("post_id")
val postId: Long,
@JsonAdapter(IntStringAdapter::class)
@SerializedName("post_type")
val postType: Int, // 0 -> floor, 1 -> floor in floor (has quota)
@SerializedName("post_content")
val postContent: List<PostContent>
)

data class PostContent(
val type: String,
val text: String,
@JsonAdapter(IntStringAdapter::class)
val uid: Long?,
val bsize: String?, // w,h
val src: String?
)

data class Quota(
val content: String,
@JsonAdapter(IntStringAdapter::class)
@SerializedName("post_id")
val postId: Long,
@JsonAdapter(IntStringAdapter::class)
@SerializedName("user_id")
val userId: Long,
@SerializedName("user_name")
val userName: String
)

data class PrivSets(
@JsonAdapter(IntStringAdapter::class)
val like: Int?,
@JsonAdapter(IntStringAdapter::class)
val location: Int?,
@JsonAdapter(IntStringAdapter::class)
val post: Int?
)
}
22 changes: 22 additions & 0 deletions app/src/main/java/io/github/a13e300/ro_tieba/models/Reply.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package io.github.a13e300.ro_tieba.models

import java.util.Date

data class Reply(
val content: List<Content>,
val pid: Long,
val comment: Boolean,
val quota: QuotaInfo?,
val threadId: Long,
val threadTitle: String,
val time: Date,
val forumId: Long,
val forumName: String
) {
data class QuotaInfo(
val pid: Long,
val uid: Long,
val username: String,
val content: String
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -114,21 +114,23 @@ class ProfileFragment : BaseFragment() {
}
binding.profileViewPager.adapter = object : FragmentStateAdapter(this) {
override fun getItemCount(): Int {
return 2
return 3
}

override fun createFragment(position: Int): Fragment {
return when (position) {
0 -> ProfileThreadsFragment()
1 -> ProfileForumsFragment()
1 -> ProfileReplyFragment()
2 -> ProfileForumsFragment()
else -> throw IllegalArgumentException("unknown position")
}
}
}
TabLayoutMediator(binding.profileTabLayout, binding.profileViewPager) { tab, position ->
tab.text = when (position) {
0 -> "帖子"
1 -> "关注的吧"
1 -> "回复"
2 -> "关注的吧"
else -> null
}
}.attach()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package io.github.a13e300.ro_tieba.ui.profile

import android.os.Bundle
import android.text.SpannableStringBuilder
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import androidx.paging.LoadState
import androidx.paging.PagingDataAdapter
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import io.github.a13e300.ro_tieba.MobileNavigationDirections
import io.github.a13e300.ro_tieba.R
import io.github.a13e300.ro_tieba.databinding.FragmentProfileReplyBinding
import io.github.a13e300.ro_tieba.databinding.FragmentProfileReplyItemBinding
import io.github.a13e300.ro_tieba.models.Reply
import io.github.a13e300.ro_tieba.utils.appendSimpleContent
import io.github.a13e300.ro_tieba.utils.toSimpleString
import kotlinx.coroutines.launch

class ProfileReplyFragment : Fragment() {
private val viewModel: ProfileViewModel by viewModels({ requireParentFragment() })
private lateinit var binding: FragmentProfileReplyBinding

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentProfileReplyBinding.inflate(inflater, container, false)
val replyAdapter = ReplyAdapter(ReplyComparator)
replyAdapter.addLoadStateListener { state ->
binding.resultTips.isVisible =
state.append is LoadState.NotLoading && state.append.endOfPaginationReached && replyAdapter.itemCount == 0
binding.resultTips.text =
if (viewModel.postHidden) getString(R.string.user_replies_hidden)
else getString(R.string.user_replies_empty)
}
binding.threadList.adapter = replyAdapter
binding.threadList.layoutManager = LinearLayoutManager(requireContext())
lifecycleScope.launch {
viewModel.repliesFlow.collect {
replyAdapter.submitData(it)
}
}
return binding.root
}

inner class ReplyAdapter(
diffCallback: DiffUtil.ItemCallback<Reply>
) : PagingDataAdapter<Reply, ReplyAdapter.ReplyViewHolder>(diffCallback) {
inner class ReplyViewHolder(val binding: FragmentProfileReplyItemBinding) :
RecyclerView.ViewHolder(binding.root)

override fun onBindViewHolder(holder: ReplyViewHolder, position: Int) {
val t = getItem(position) ?: return
holder.binding.threadTitle.text = t.threadTitle
holder.binding.threadInfo.text = "${t.time.toSimpleString()} ${t.forumName}"
holder.binding.threadContent.text =
SpannableStringBuilder().appendSimpleContent(t.content, requireContext())
holder.binding.root.setOnClickListener {
val pid = if (t.comment) {
t.quota?.pid ?: 0L
} else t.pid
findNavController().navigate(
MobileNavigationDirections.goToThread(t.threadId).setPid(pid)
)
}
}

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ReplyViewHolder {
return ReplyViewHolder(
FragmentProfileReplyItemBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
}
}
}

object ReplyComparator : DiffUtil.ItemCallback<Reply>() {
override fun areItemsTheSame(oldItem: Reply, newItem: Reply): Boolean {
return oldItem.pid == newItem.pid
}

override fun areContentsTheSame(oldItem: Reply, newItem: Reply): Boolean {
return oldItem == newItem
}
}
Loading

0 comments on commit e2bb378

Please sign in to comment.