Skip to content

Commit

Permalink
add thread bar
Browse files Browse the repository at this point in the history
  • Loading branch information
5ec1cff committed Oct 19, 2023
1 parent 3f55b04 commit 900d10b
Show file tree
Hide file tree
Showing 8 changed files with 279 additions and 57 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ class CachedThread private constructor(val tid: Long) {
title = response.thread.title,
author = response.thread.author.toUser(),
content = listOf(),
replyNum = response.thread.replyNum,
replyNum = response.thread.replyNum - 1, // TODO: fix reply num
time = Date(response.thread.createTime.toLong() * 1000), // TODO: remove this useless date
postId = response.thread.postId,
isGood = response.thread.isGood == 1,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package io.github.a13e300.ro_tieba.misc

import android.content.Context
import android.util.AttributeSet
import android.view.View
import androidx.coordinatorlayout.widget.CoordinatorLayout
import io.github.a13e300.ro_tieba.R

class ContainerBehavior(context: Context, attrs: AttributeSet) :
CoordinatorLayout.Behavior<View>(context, attrs) {
override fun onMeasureChild(
parent: CoordinatorLayout,
child: View,
parentWidthMeasureSpec: Int,
widthUsed: Int,
parentHeightMeasureSpec: Int,
heightUsed: Int
): Boolean {
parent.onMeasureChild(
child,
parentWidthMeasureSpec,
widthUsed,
parentHeightMeasureSpec,
heightUsed
)
return true
}

override fun onLayoutChild(
parent: CoordinatorLayout,
child: View,
layoutDirection: Int
): Boolean {
parent.onLayoutChild(child, layoutDirection)
child.offsetTopAndBottom(parent.findViewById<View>(R.id.list).top)
return true
}
}
180 changes: 127 additions & 53 deletions app/src/main/java/io/github/a13e300/ro_tieba/ui/thread/ThreadFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import android.widget.MediaController
import androidx.appcompat.content.res.AppCompatResources
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.MenuProvider
import androidx.core.view.ViewCompat
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.core.widget.doAfterTextChanged
Expand All @@ -32,7 +33,6 @@ import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import androidx.paging.LoadState
import androidx.paging.LoadStateAdapter
import androidx.paging.PagingDataAdapter
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.LinearLayoutManager
Expand All @@ -47,12 +47,12 @@ import io.github.a13e300.ro_tieba.Logger
import io.github.a13e300.ro_tieba.MobileNavigationDirections
import io.github.a13e300.ro_tieba.R
import io.github.a13e300.ro_tieba.databinding.DialogJumpPageBinding
import io.github.a13e300.ro_tieba.databinding.FragmentThreadBarBinding
import io.github.a13e300.ro_tieba.databinding.FragmentThreadBinding
import io.github.a13e300.ro_tieba.databinding.FragmentThreadCommentPreviewBinding
import io.github.a13e300.ro_tieba.databinding.FragmentThreadHeaderBinding
import io.github.a13e300.ro_tieba.databinding.FragmentThreadPostItemBinding
import io.github.a13e300.ro_tieba.databinding.ImageContentBinding
import io.github.a13e300.ro_tieba.databinding.ThreadListFooterBinding
import io.github.a13e300.ro_tieba.databinding.VideoViewBinding
import io.github.a13e300.ro_tieba.db.EntryType
import io.github.a13e300.ro_tieba.db.HistoryEntry
Expand All @@ -75,6 +75,8 @@ import io.github.a13e300.ro_tieba.utils.configureDefaults
import io.github.a13e300.ro_tieba.utils.configureImageForContent
import io.github.a13e300.ro_tieba.utils.copyText
import io.github.a13e300.ro_tieba.utils.displayImageInList
import io.github.a13e300.ro_tieba.utils.firstOrNullFrom
import io.github.a13e300.ro_tieba.utils.indexOfFrom
import io.github.a13e300.ro_tieba.utils.setSelectedData
import io.github.a13e300.ro_tieba.utils.toSimpleString
import io.github.a13e300.ro_tieba.view.ContentTextView
Expand Down Expand Up @@ -172,6 +174,8 @@ class ThreadFragment : BaseFragment() {
}
if (idx != -1) {
scrollToPositionWithOffset(idx, request.offset)
if (request.offsetToBar)
prepareScrollOffsetToBar()
if (request.highlight)
mHighlightIdx = idx
} else {
Expand All @@ -191,15 +195,18 @@ class ThreadFragment : BaseFragment() {
)
)
addOnScrollListener(PauseLoadOnQuickScrollListener())
addOnScrollListener(mScrollListener)
}
viewModel.threadInfo.observe(viewLifecycleOwner) {
binding.toolbar.title = it.forum?.name
notifyBarUpdate()
if (!viewModel.historyAdded) {
updateHistory()
viewModel.historyAdded = true
}
}
setupToolbar(binding.toolbar)
bindBar(binding.includeThreadBar)
binding.toolbar.setOnClickListener {
binding.list.scrollToPosition(0)
}
Expand All @@ -215,18 +222,13 @@ class ThreadFragment : BaseFragment() {

R.id.sort -> {
val v = !menuItem.isChecked
menuItem.setChecked(v)
viewModel.threadConfig = viewModel.threadConfig.copy(reverse = v)
postAdapter.refresh()
setReverse(v)
true
}

R.id.see_lz -> {
val v = !menuItem.isChecked
menuItem.setChecked(v)
viewModel.threadConfig =
viewModel.threadConfig.copy(seeLz = v, pid = 0L, page = 0)
postAdapter.refresh()
setSeeLz(v)
true
}

Expand Down Expand Up @@ -276,6 +278,93 @@ class ThreadFragment : BaseFragment() {
return binding.root
}

private fun setSeeLz(v: Boolean) {
viewModel.threadConfig =
viewModel.threadConfig.copy(seeLz = v, pid = 0L, page = 0)
notifyBarUpdate()
postAdapter.refresh()
binding.toolbar.menu.findItem(R.id.see_lz).setChecked(viewModel.threadConfig.seeLz)
}

private fun setReverse(v: Boolean) {
viewModel.threadConfig = viewModel.threadConfig.copy(reverse = v)
notifyBarUpdate()
postAdapter.refresh()
binding.toolbar.menu.findItem(R.id.sort).setChecked(viewModel.threadConfig.reverse)
}

private fun notifyBarUpdate() {
val barIdx =
postAdapter.snapshot().items.indexOfFirst { it is ThreadViewModel.PostModel.Bar }
if (barIdx != -1)
postAdapter.notifyItemChanged(barIdx)
updateBar(binding.includeThreadBar)
}

private fun bindBar(bar: FragmentThreadBarBinding) {
bar.seeLzBtn.setOnClickListener {
setSeeLz(!bar.seeLzBtn.isChecked)
}
bar.sortBtn.setOnClickListener {
setReverse(!bar.sortBtn.isChecked)
}
bar.jumpBtn.setOnClickListener {
handleJumpPage()
}
}

private fun updateBar(bar: FragmentThreadBarBinding) {
val data = viewModel.threadInfo.value ?: return
val current = findCurrentPost()
bar.count.text = "${data.replyNum} 条回复"
bar.seeLzBtn.isChecked = viewModel.threadConfig.seeLz
bar.sortBtn.isChecked = viewModel.threadConfig.reverse
bar.jumpBtn.text = "${current?.page} / ${viewModel.totalPage}"
}

private var mScrollListener = object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
val pos = postLayoutManager.findFirstVisibleItemPosition()
if (pos == RecyclerView.NO_POSITION) return
val shouldHide = when (val item = postAdapter.snapshot()[pos]) {
is ThreadViewModel.PostModel.Header -> true
is ThreadViewModel.PostModel.Bar -> false
is ThreadViewModel.PostModel.Post -> {
item.post.floor == 1
}

null -> return
}
updateBar(binding.includeThreadBar)
ViewCompat.postOnAnimation(binding.mainStickyContainerLayout) {
binding.mainStickyContainerLayout.isGone = shouldHide
}
}
}

private val mScrollOffsetToBarListener =
object : View.OnLayoutChangeListener {
override fun onLayoutChange(
view: View,
p1: Int,
p2: Int,
p3: Int,
p4: Int,
p5: Int,
p6: Int,
p7: Int,
p8: Int
) {
val h = view.height
binding.list.scrollBy(0, -h)
view.removeOnLayoutChangeListener(this)
}
}

private fun prepareScrollOffsetToBar() {
binding.mainStickyContainerLayout.addOnLayoutChangeListener(mScrollOffsetToBarListener)
}

private fun handleJumpPage() {
val totalPage = viewModel.totalPage
val page = findCurrentPost()?.page ?: 0
Expand Down Expand Up @@ -334,25 +423,16 @@ class ThreadFragment : BaseFragment() {
}

private fun saveLastSeenInfo() {
val pos = postLayoutManager.findFirstVisibleItemPosition()
if (pos == RecyclerView.NO_POSITION) return
val off = postLayoutManager.findViewByPosition(pos)?.top ?: return
val item = postAdapter.snapshot().items.getOrNull(pos) ?: return
val page: Int
val floor: Int
when (item) {
is ThreadViewModel.PostModel.Header -> {
page =
(postAdapter.snapshot().items.getOrNull(pos + 1) as? ThreadViewModel.PostModel.Post)?.post?.page
?: 0
floor = -1
}

is ThreadViewModel.PostModel.Post -> {
page = item.post.page
floor = item.post.floor
}
val pos = postLayoutManager.findFirstVisibleItemPosition().let {
if (it == RecyclerView.NO_POSITION) return
postAdapter.snapshot().items.indexOfFrom(it) { item -> item is ThreadViewModel.PostModel.Post }
}
if (pos == -1) return
val off = postLayoutManager.findViewByPosition(pos)?.top ?: return
val item = postAdapter.snapshot().items[pos] as ThreadViewModel.PostModel.Post
val page = item.post.page
val floor = item.post.floor
// TODO: remove floor = -1
sLastSeenThreadInfo = LastSeenThreadInfo(
args.tid, args.pid,
page, floor, off, viewModel.threadConfig.seeLz, viewModel.threadConfig.reverse
Expand All @@ -362,13 +442,8 @@ class ThreadFragment : BaseFragment() {
private fun findCurrentPost() = postLayoutManager.findFirstVisibleItemPosition().let {
if (it == RecyclerView.NO_POSITION) null
else {
postAdapter.snapshot().items.let { items ->
(items[it].let { a ->
if (a is ThreadViewModel.PostModel.Header) {
items.getOrNull(it + 1)?.let { b -> (b as? ThreadViewModel.PostModel.Post) }
} else (a as? ThreadViewModel.PostModel.Post)
})?.post
}
(postAdapter.snapshot().items.firstOrNullFrom(it) { item -> item is ThreadViewModel.PostModel.Post }
as? ThreadViewModel.PostModel.Post)?.post
}
}

Expand Down Expand Up @@ -401,43 +476,32 @@ class ThreadFragment : BaseFragment() {
saveLastSeenInfo()
}

class MyItemDecoration(private val mMargin: Int) : ItemDecoration() {
inner class MyItemDecoration(private val mMargin: Int) : ItemDecoration() {
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State
) {
val pos = parent.getChildAdapterPosition(view)
if (pos >= 2) {
if (pos == RecyclerView.NO_POSITION) return
val item = postAdapter.snapshot().items[pos]
val minMarginFloor = if (viewModel.threadConfig.reverse) 0 else 2
if (item is ThreadViewModel.PostModel.Post && item.post.floor > minMarginFloor) {
outRect.set(0, mMargin, 0, 0)
}
}
}

class FooterHolder(val binding: ThreadListFooterBinding) : ViewHolder(binding.root)

inner class FooterAdapter : LoadStateAdapter<FooterHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, loadState: LoadState): FooterHolder {
return FooterHolder(ThreadListFooterBinding.inflate(layoutInflater, parent, false))
}

override fun onBindViewHolder(holder: FooterHolder, loadState: LoadState) {
val isErr = loadState is LoadState.Error
holder.binding.retryButton.isVisible = isErr
holder.binding.errorMessage.isVisible = isErr
if (loadState is LoadState.Error) {
holder.binding.errorMessage.text = loadState.error.message
}
}
}

class PostViewHolder(val binding: FragmentThreadPostItemBinding) :
ViewHolder(binding.root)

class HeaderViewHolder(val binding: FragmentThreadHeaderBinding) :
ViewHolder(binding.root)

class BarViewHolder(val binding: FragmentThreadBarBinding) :
ViewHolder(binding.root)


inner class PostAdapter(diffCallback: DiffUtil.ItemCallback<ThreadViewModel.PostModel>) :
PagingDataAdapter<ThreadViewModel.PostModel, ViewHolder>(
Expand All @@ -448,6 +512,7 @@ class ThreadFragment : BaseFragment() {
return when (peek(position)) {
is ThreadViewModel.PostModel.Post -> R.layout.fragment_thread_post_item
is ThreadViewModel.PostModel.Header -> R.layout.fragment_thread_header
is ThreadViewModel.PostModel.Bar -> R.layout.fragment_thread_bar
else -> throw IllegalStateException("unknown item")
}
}
Expand All @@ -462,6 +527,9 @@ class ThreadFragment : BaseFragment() {
}
} else if (holder is HeaderViewHolder) {
bindForHeader(holder)
} else if (holder is BarViewHolder) {
bindBar(holder.binding)
updateBar(holder.binding)
}
}

Expand Down Expand Up @@ -790,6 +858,12 @@ class ThreadFragment : BaseFragment() {
)
)

R.layout.fragment_thread_bar -> BarViewHolder(
FragmentThreadBarBinding.inflate(
LayoutInflater.from(parent.context), parent, false
)
)

else -> throw IllegalStateException("unknown type $viewType")
}
}
Expand Down
Loading

0 comments on commit 900d10b

Please sign in to comment.