fix: code cleanup | reply/edit stability
This commit is contained in:
parent
a74b9e985d
commit
ba7c665a9d
3 changed files with 336 additions and 353 deletions
|
@ -194,10 +194,23 @@ object CommentsAPI {
|
||||||
429 -> "Rate limited. :("
|
429 -> "Rate limited. :("
|
||||||
else -> "Failed to connect"
|
else -> "Failed to connect"
|
||||||
}
|
}
|
||||||
snackString("Error $code: ${reason ?: error}")
|
val parsed = try {
|
||||||
|
Json.decodeFromString<ErrorResponse>(reason!!)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
val message = parsed?.message ?: reason ?: error
|
||||||
|
|
||||||
|
snackString("Error $code: $message")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ErrorResponse(
|
||||||
|
@SerialName("message")
|
||||||
|
val message: String
|
||||||
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class AuthResponse(
|
data class AuthResponse(
|
||||||
@SerialName("authToken")
|
@SerialName("authToken")
|
||||||
|
|
|
@ -26,13 +26,14 @@ import java.util.TimeZone
|
||||||
class CommentItem(val comment: Comment,
|
class CommentItem(val comment: Comment,
|
||||||
private val markwon: Markwon,
|
private val markwon: Markwon,
|
||||||
private val section: Section,
|
private val section: Section,
|
||||||
private val editCallback: (CommentItem) -> Unit,
|
private val commentsActivity: CommentsActivity,
|
||||||
private val viewReplyCallback: (CommentItem) -> Unit,
|
|
||||||
private val replyCallback: (CommentItem) -> Unit
|
|
||||||
) : BindableItem<ItemCommentsBinding>() {
|
) : BindableItem<ItemCommentsBinding>() {
|
||||||
var binding: ItemCommentsBinding? = null
|
var binding: ItemCommentsBinding? = null
|
||||||
val adapter = GroupieAdapter()
|
val adapter = GroupieAdapter()
|
||||||
val repliesSection = Section()
|
val repliesSection = Section()
|
||||||
|
private var isEditing = false
|
||||||
|
private var isReplying = false
|
||||||
|
private var repliesVisible = false
|
||||||
|
|
||||||
init {
|
init {
|
||||||
adapter.add(repliesSection)
|
adapter.add(repliesSection)
|
||||||
|
@ -58,37 +59,25 @@ class CommentItem(val comment: Comment,
|
||||||
}
|
}
|
||||||
viewBinding.commentReply.visibility = View.VISIBLE
|
viewBinding.commentReply.visibility = View.VISIBLE
|
||||||
viewBinding.commentTotalReplies.setOnClickListener {
|
viewBinding.commentTotalReplies.setOnClickListener {
|
||||||
viewBinding.commentTotalReplies.visibility = View.GONE
|
if (repliesVisible) {
|
||||||
viewReplyCallback(this)
|
repliesSection.clear()
|
||||||
}
|
viewBinding.commentTotalReplies.text = "View ${comment.replyCount} repl${if (comment.replyCount == 1) "y" else "ies"}"
|
||||||
var isEditing = false
|
repliesVisible = false
|
||||||
var isReplying = false
|
|
||||||
viewBinding.commentEdit.setOnClickListener {
|
|
||||||
if (!isEditing) {
|
|
||||||
viewBinding.commentEdit.text = currActivity()!!.getString(R.string.cancel)
|
|
||||||
isEditing = true
|
|
||||||
isReplying = false
|
|
||||||
viewBinding.commentReply.text = "Reply"
|
|
||||||
editCallback(this)
|
|
||||||
} else {
|
} else {
|
||||||
viewBinding.commentEdit.text = currActivity()!!.getString(R.string.edit)
|
viewBinding.commentTotalReplies.text = "Hide Replies"
|
||||||
isEditing = false
|
repliesSection.clear()
|
||||||
editCallback(this)
|
commentsActivity.viewReplyCallback(this)
|
||||||
|
repliesVisible = true
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
viewBinding.commentEdit.setOnClickListener {
|
||||||
|
editing(!isEditing)
|
||||||
|
commentsActivity.editCallback(this)
|
||||||
}
|
}
|
||||||
viewBinding.commentReply.setOnClickListener {
|
viewBinding.commentReply.setOnClickListener {
|
||||||
if (!isReplying) {
|
replying(!isReplying)
|
||||||
viewBinding.commentReply.text = currActivity()!!.getString(R.string.cancel)
|
commentsActivity.replyCallback(this)
|
||||||
isReplying = true
|
|
||||||
isEditing = false
|
|
||||||
viewBinding.commentEdit.text = currActivity()!!.getString(R.string.edit)
|
|
||||||
replyCallback(this)
|
|
||||||
} else {
|
|
||||||
viewBinding.commentReply.text = "Reply"
|
|
||||||
isReplying = false
|
|
||||||
replyCallback(this)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
viewBinding.modBadge.visibility = if (comment.isMod == true) View.VISIBLE else View.GONE
|
viewBinding.modBadge.visibility = if (comment.isMod == true) View.VISIBLE else View.GONE
|
||||||
viewBinding.adminBadge.visibility = if (comment.isAdmin == true) View.VISIBLE else View.GONE
|
viewBinding.adminBadge.visibility = if (comment.isAdmin == true) View.VISIBLE else View.GONE
|
||||||
|
@ -165,6 +154,16 @@ class CommentItem(val comment: Comment,
|
||||||
return ItemCommentsBinding.bind(view)
|
return ItemCommentsBinding.bind(view)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun replying(isReplying: Boolean) {
|
||||||
|
binding?.commentReply?.text = if (isReplying) currActivity()!!.getString(R.string.cancel) else "Reply"
|
||||||
|
this.isReplying = isReplying
|
||||||
|
}
|
||||||
|
|
||||||
|
fun editing(isEditing: Boolean) {
|
||||||
|
binding?.commentEdit?.text = if (isEditing) currActivity()!!.getString(R.string.cancel) else currActivity()!!.getString(R.string.edit)
|
||||||
|
this.isEditing = isEditing
|
||||||
|
}
|
||||||
|
|
||||||
private fun setVoteButtons(viewBinding: ItemCommentsBinding) {
|
private fun setVoteButtons(viewBinding: ItemCommentsBinding) {
|
||||||
when (comment.userVoteType) {
|
when (comment.userVoteType) {
|
||||||
1 -> {
|
1 -> {
|
||||||
|
|
|
@ -11,6 +11,8 @@ import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
import ani.dantotsu.connections.anilist.Anilist
|
import ani.dantotsu.connections.anilist.Anilist
|
||||||
|
import ani.dantotsu.connections.comments.Comment
|
||||||
|
import ani.dantotsu.connections.comments.CommentResponse
|
||||||
import ani.dantotsu.connections.comments.CommentsAPI
|
import ani.dantotsu.connections.comments.CommentsAPI
|
||||||
import ani.dantotsu.databinding.ActivityCommentsBinding
|
import ani.dantotsu.databinding.ActivityCommentsBinding
|
||||||
import ani.dantotsu.initActivity
|
import ani.dantotsu.initActivity
|
||||||
|
@ -43,10 +45,18 @@ import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Locale
|
||||||
import java.util.TimeZone
|
import java.util.TimeZone
|
||||||
|
|
||||||
class CommentsActivity : AppCompatActivity() {
|
class CommentsActivity : AppCompatActivity() {
|
||||||
lateinit var binding: ActivityCommentsBinding
|
lateinit var binding: ActivityCommentsBinding
|
||||||
|
private var interactionState = InteractionState.NONE
|
||||||
|
private var commentWithInteraction: CommentItem? = null
|
||||||
|
private val section = Section()
|
||||||
|
private val adapter = GroupieAdapter()
|
||||||
|
private var mediaId: Int = -1
|
||||||
|
var pagesLoaded = 1
|
||||||
|
var totalPages = 1
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
@ -60,9 +70,8 @@ class CommentsActivity : AppCompatActivity() {
|
||||||
snackString("Invalid Media ID")
|
snackString("Invalid Media ID")
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
|
this.mediaId = mediaId
|
||||||
|
|
||||||
val adapter = GroupieAdapter()
|
|
||||||
val section = Section()
|
|
||||||
val markwon = buildMarkwon()
|
val markwon = buildMarkwon()
|
||||||
|
|
||||||
binding.commentUserAvatar.loadImage(Anilist.avatar)
|
binding.commentUserAvatar.loadImage(Anilist.avatar)
|
||||||
|
@ -74,250 +83,88 @@ class CommentsActivity : AppCompatActivity() {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
binding.commentReplyToContainer.visibility = View.GONE //TODO: implement reply
|
binding.commentReplyToContainer.visibility = View.GONE //TODO: implement reply
|
||||||
var editing = false
|
|
||||||
var editingCommentId = -1
|
|
||||||
var replying = false
|
|
||||||
var replyingComment: CommentItem? = null
|
|
||||||
fun editCallback(comment: CommentItem) {
|
|
||||||
replying = false
|
|
||||||
replyingComment = null
|
|
||||||
if (editingCommentId == comment.comment.commentId) {
|
|
||||||
editing = false
|
|
||||||
editingCommentId = -1
|
|
||||||
binding.commentInput.setText("")
|
|
||||||
val imm = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
|
|
||||||
imm.hideSoftInputFromWindow(binding.commentInput.windowToken, 0)
|
|
||||||
} else {
|
|
||||||
editing = true
|
|
||||||
editingCommentId = comment.comment.commentId
|
|
||||||
binding.commentInput.setText(comment.comment.content)
|
|
||||||
binding.commentInput.requestFocus()
|
|
||||||
binding.commentInput.setSelection(binding.commentInput.text.length)
|
|
||||||
val imm = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
|
|
||||||
imm.showSoftInput(binding.commentInput, InputMethodManager.SHOW_IMPLICIT)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
fun replyCallback(comment: CommentItem) {
|
|
||||||
editing = false
|
|
||||||
editingCommentId = -1
|
|
||||||
if (replyingComment?.comment?.commentId == comment.comment.commentId) {
|
|
||||||
replying = false
|
|
||||||
replyingComment = null
|
|
||||||
binding.commentInput.setText("")
|
|
||||||
val imm = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
|
|
||||||
imm.hideSoftInputFromWindow(binding.commentInput.windowToken, 0)
|
|
||||||
} else {
|
|
||||||
replying = true
|
|
||||||
replyingComment = comment
|
|
||||||
binding.commentInput.requestFocus()
|
|
||||||
binding.commentInput.setSelection(binding.commentInput.text.length)
|
|
||||||
val imm = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
|
|
||||||
imm.showSoftInput(binding.commentInput, InputMethodManager.SHOW_IMPLICIT)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun viewReplyCallback(comment: CommentItem) {
|
|
||||||
lifecycleScope.launch {
|
|
||||||
val replies = withContext(Dispatchers.IO) {
|
|
||||||
CommentsAPI.getRepliesFromId(comment.comment.commentId)
|
|
||||||
}
|
|
||||||
replies?.comments?.forEach {
|
|
||||||
comment.repliesSection.add(CommentItem(it, buildMarkwon(), comment.repliesSection, { comment ->
|
|
||||||
editCallback(comment)
|
|
||||||
}, { comment ->
|
|
||||||
viewReplyCallback(comment)
|
|
||||||
}, { comment ->
|
|
||||||
replyCallback(comment)
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
binding.commentsRefresh.setOnRefreshListener {
|
binding.commentsRefresh.setOnRefreshListener {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
binding.commentsList.visibility = View.GONE
|
loadAndDisplayComments()
|
||||||
adapter.clear()
|
|
||||||
section.clear()
|
|
||||||
withContext(Dispatchers.IO) {
|
|
||||||
val comments = CommentsAPI.getCommentsForId(mediaId)
|
|
||||||
val sorted = when (PrefManager.getVal(PrefName.CommentSortOrder, "newest")) {
|
|
||||||
"newest" -> comments?.comments?.sortedByDescending { comment ->
|
|
||||||
CommentItem.timestampToMillis(comment.timestamp)
|
|
||||||
}
|
|
||||||
|
|
||||||
"oldest" -> comments?.comments?.sortedBy { comment ->
|
|
||||||
CommentItem.timestampToMillis(comment.timestamp)
|
|
||||||
}
|
|
||||||
|
|
||||||
"highest_rated" -> comments?.comments?.sortedByDescending { comment ->
|
|
||||||
comment.upvotes - comment.downvotes
|
|
||||||
}
|
|
||||||
|
|
||||||
"lowest_rated" -> comments?.comments?.sortedBy { comment ->
|
|
||||||
comment.upvotes - comment.downvotes
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> comments?.comments
|
|
||||||
}
|
|
||||||
sorted?.forEach {
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
section.add(CommentItem(it, buildMarkwon(), section, { comment ->
|
|
||||||
editCallback(comment)
|
|
||||||
}, { comment ->
|
|
||||||
viewReplyCallback(comment)
|
|
||||||
}, { comment ->
|
|
||||||
replyCallback(comment)
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
adapter.add(section)
|
|
||||||
binding.commentsList.visibility = View.VISIBLE
|
|
||||||
binding.commentsRefresh.isRefreshing = false
|
binding.commentsRefresh.isRefreshing = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var pagesLoaded = 1
|
|
||||||
var totalPages = 1
|
|
||||||
binding.commentsList.adapter = adapter
|
binding.commentsList.adapter = adapter
|
||||||
binding.commentsList.layoutManager = LinearLayoutManager(this)
|
binding.commentsList.layoutManager = LinearLayoutManager(this)
|
||||||
|
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
binding.commentsProgressBar.visibility = View.VISIBLE
|
loadAndDisplayComments()
|
||||||
binding.commentsList.visibility = View.GONE
|
|
||||||
withContext(Dispatchers.IO) {
|
|
||||||
val comments = CommentsAPI.getCommentsForId(mediaId)
|
|
||||||
val sorted = when (PrefManager.getVal(PrefName.CommentSortOrder, "newest")) {
|
|
||||||
"newest" -> comments?.comments?.sortedByDescending { comment ->
|
|
||||||
CommentItem.timestampToMillis(comment.timestamp)
|
|
||||||
}
|
|
||||||
|
|
||||||
"oldest" -> comments?.comments?.sortedBy { comment ->
|
|
||||||
CommentItem.timestampToMillis(comment.timestamp)
|
|
||||||
}
|
|
||||||
|
|
||||||
"highest_rated" -> comments?.comments?.sortedByDescending { comment ->
|
|
||||||
comment.upvotes - comment.downvotes
|
|
||||||
}
|
|
||||||
|
|
||||||
"lowest_rated" -> comments?.comments?.sortedBy { comment ->
|
|
||||||
comment.upvotes - comment.downvotes
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> comments?.comments
|
|
||||||
}
|
|
||||||
sorted?.forEach {
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
section.add(CommentItem(it, buildMarkwon(), section, { comment ->
|
|
||||||
editCallback(comment)
|
|
||||||
}, { comment ->
|
|
||||||
viewReplyCallback(comment)
|
|
||||||
}, { comment ->
|
|
||||||
replyCallback(comment)
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
totalPages = comments?.totalPages ?: 1
|
|
||||||
}
|
|
||||||
binding.commentsProgressBar.visibility = View.GONE
|
|
||||||
binding.commentsList.visibility = View.VISIBLE
|
|
||||||
adapter.add(section)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.commentSort.setOnClickListener {
|
binding.commentSort.setOnClickListener { view ->
|
||||||
val popup = PopupMenu(this, it)
|
fun sortComments(sortOrder: String) {
|
||||||
popup.setOnMenuItemClickListener { item ->
|
val groups = section.groups
|
||||||
when (item.itemId) {
|
when (sortOrder) {
|
||||||
R.id.comment_sort_newest -> {
|
"newest" -> groups.sortByDescending { CommentItem.timestampToMillis((it as CommentItem).comment.timestamp) }
|
||||||
PrefManager.setVal(PrefName.CommentSortOrder, "newest")
|
"oldest" -> groups.sortBy { CommentItem.timestampToMillis((it as CommentItem).comment.timestamp) }
|
||||||
val groups = section.groups
|
"highest_rated" -> groups.sortByDescending { (it as CommentItem).comment.upvotes - it.comment.downvotes }
|
||||||
groups.sortByDescending { comment ->
|
"lowest_rated" -> groups.sortBy { (it as CommentItem).comment.upvotes - it.comment.downvotes }
|
||||||
comment as CommentItem
|
|
||||||
CommentItem.timestampToMillis(comment.comment.timestamp)
|
|
||||||
}
|
|
||||||
section.update(groups)
|
|
||||||
binding.commentsList.scrollToPosition(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
R.id.comment_sort_oldest -> {
|
|
||||||
PrefManager.setVal(PrefName.CommentSortOrder, "oldest")
|
|
||||||
val groups = section.groups
|
|
||||||
groups.sortBy { comment ->
|
|
||||||
comment as CommentItem
|
|
||||||
CommentItem.timestampToMillis(comment.comment.timestamp)
|
|
||||||
}
|
|
||||||
section.update(groups)
|
|
||||||
binding.commentsList.scrollToPosition(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
R.id.comment_sort_highest_rated -> {
|
|
||||||
PrefManager.setVal(PrefName.CommentSortOrder, "highest_rated")
|
|
||||||
val groups = section.groups
|
|
||||||
groups.sortByDescending { comment ->
|
|
||||||
comment as CommentItem
|
|
||||||
comment.comment.upvotes - comment.comment.downvotes
|
|
||||||
}
|
|
||||||
section.update(groups)
|
|
||||||
binding.commentsList.scrollToPosition(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
R.id.comment_sort_lowest_rated -> {
|
|
||||||
PrefManager.setVal(PrefName.CommentSortOrder, "lowest_rated")
|
|
||||||
val groups = section.groups
|
|
||||||
groups.sortBy { comment ->
|
|
||||||
comment as CommentItem
|
|
||||||
comment.comment.upvotes - comment.comment.downvotes
|
|
||||||
}
|
|
||||||
section.update(groups)
|
|
||||||
binding.commentsList.scrollToPosition(0)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
section.update(groups)
|
||||||
|
}
|
||||||
|
|
||||||
|
val popup = PopupMenu(this, view)
|
||||||
|
popup.setOnMenuItemClickListener { item ->
|
||||||
|
val sortOrder = when (item.itemId) {
|
||||||
|
R.id.comment_sort_newest -> "newest"
|
||||||
|
R.id.comment_sort_oldest -> "oldest"
|
||||||
|
R.id.comment_sort_highest_rated -> "highest_rated"
|
||||||
|
R.id.comment_sort_lowest_rated -> "lowest_rated"
|
||||||
|
else -> return@setOnMenuItemClickListener false
|
||||||
|
}
|
||||||
|
|
||||||
|
PrefManager.setVal(PrefName.CommentSortOrder, sortOrder)
|
||||||
|
sortComments(sortOrder)
|
||||||
|
binding.commentsList.scrollToPosition(0)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
popup.inflate(R.menu.comments_sort_menu)
|
popup.inflate(R.menu.comments_sort_menu)
|
||||||
popup.show()
|
popup.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
var fetching = false
|
var isFetching = false
|
||||||
//if we have scrolled to the bottom of the list, load more comments
|
//if we have scrolled to the bottom of the list, load more comments
|
||||||
binding.commentsList.addOnScrollListener(object :
|
binding.commentsList.addOnScrollListener(object : androidx.recyclerview.widget.RecyclerView.OnScrollListener() {
|
||||||
androidx.recyclerview.widget.RecyclerView.OnScrollListener() {
|
override fun onScrolled(recyclerView: androidx.recyclerview.widget.RecyclerView, dx: Int, dy: Int) {
|
||||||
override fun onScrolled(
|
|
||||||
recyclerView: androidx.recyclerview.widget.RecyclerView,
|
|
||||||
dx: Int,
|
|
||||||
dy: Int
|
|
||||||
) {
|
|
||||||
super.onScrolled(recyclerView, dx, dy)
|
super.onScrolled(recyclerView, dx, dy)
|
||||||
if (!recyclerView.canScrollVertically(1)) {
|
if (shouldLoadMoreComments(recyclerView)) {
|
||||||
if (pagesLoaded < totalPages && !fetching) {
|
loadMoreComments()
|
||||||
fetching = true
|
}
|
||||||
lifecycleScope.launch {
|
}
|
||||||
withContext(Dispatchers.IO) {
|
|
||||||
val comments =
|
private fun shouldLoadMoreComments(recyclerView: androidx.recyclerview.widget.RecyclerView): Boolean {
|
||||||
CommentsAPI.getCommentsForId(mediaId, pagesLoaded + 1)
|
return !recyclerView.canScrollVertically(1) && pagesLoaded < totalPages && !isFetching
|
||||||
comments?.comments?.forEach {
|
}
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
section.add(
|
private fun loadMoreComments() {
|
||||||
CommentItem(
|
isFetching = true
|
||||||
it,
|
lifecycleScope.launch {
|
||||||
buildMarkwon(),
|
val comments = fetchComments()
|
||||||
section, { comment ->
|
comments?.comments?.forEach { comment ->
|
||||||
editCallback(comment)
|
updateUIWithComment(comment)
|
||||||
}, { comment ->
|
|
||||||
viewReplyCallback(comment)
|
|
||||||
}, { comment ->
|
|
||||||
replyCallback(comment)
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
totalPages = comments?.totalPages ?: 1
|
|
||||||
}
|
|
||||||
pagesLoaded++
|
|
||||||
fetching = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
totalPages = comments?.totalPages ?: 1
|
||||||
|
pagesLoaded++
|
||||||
|
isFetching = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun fetchComments(): CommentResponse? {
|
||||||
|
return withContext(Dispatchers.IO) {
|
||||||
|
CommentsAPI.getCommentsForId(mediaId, pagesLoaded + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun updateUIWithComment(comment: Comment) {
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
section.add(CommentItem(comment, buildMarkwon(), section, this@CommentsActivity))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -342,111 +189,235 @@ class CommentsActivity : AppCompatActivity() {
|
||||||
snackString("You are banned from commenting :(")
|
snackString("You are banned from commenting :(")
|
||||||
return@setOnClickListener
|
return@setOnClickListener
|
||||||
}
|
}
|
||||||
fun onSuccess() {
|
|
||||||
binding.commentInput.text.toString().let {
|
|
||||||
if (it.isNotEmpty()) {
|
|
||||||
binding.commentInput.text.clear()
|
|
||||||
lifecycleScope.launch {
|
|
||||||
if (editing) {
|
|
||||||
val success = withContext(Dispatchers.IO) {
|
|
||||||
CommentsAPI.editComment(editingCommentId, it)
|
|
||||||
}
|
|
||||||
if (success) {
|
|
||||||
val groups = section.groups
|
|
||||||
groups.forEach { item ->
|
|
||||||
if (item is CommentItem) {
|
|
||||||
if (item.comment.commentId == editingCommentId) {
|
|
||||||
item.comment.content = it
|
|
||||||
val dateFormat =
|
|
||||||
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
|
|
||||||
dateFormat.timeZone =
|
|
||||||
TimeZone.getTimeZone("UTC")
|
|
||||||
item.comment.timestamp =
|
|
||||||
dateFormat.format(System.currentTimeMillis())
|
|
||||||
item.notifyChanged()
|
|
||||||
snackString("Comment edited")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
val success = withContext(Dispatchers.IO) {
|
|
||||||
CommentsAPI.comment(mediaId, if (replying) replyingComment!!.comment.commentId else null, it)
|
|
||||||
}
|
|
||||||
if (success != null)
|
|
||||||
if (replying) {
|
|
||||||
replyingComment?.repliesSection?.add(
|
|
||||||
0,
|
|
||||||
CommentItem(
|
|
||||||
success,
|
|
||||||
buildMarkwon(),
|
|
||||||
replyingComment?.repliesSection!!, { comment ->
|
|
||||||
editCallback(comment)
|
|
||||||
}, { comment ->
|
|
||||||
viewReplyCallback(comment)
|
|
||||||
}, { comment ->
|
|
||||||
replyCallback(comment)
|
|
||||||
})
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
section.add(
|
|
||||||
0,
|
|
||||||
CommentItem(
|
|
||||||
success,
|
|
||||||
buildMarkwon(),
|
|
||||||
section, { comment ->
|
|
||||||
editCallback(comment)
|
|
||||||
}, { comment ->
|
|
||||||
viewReplyCallback(comment)
|
|
||||||
}, { comment ->
|
|
||||||
replyCallback(comment)
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
snackString("Comment cannot be empty")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val firstComment = PrefManager.getVal<Boolean>(PrefName.FirstComment)
|
|
||||||
if (firstComment) {
|
|
||||||
//show a dialog explaining the rules
|
|
||||||
val alertDialog = android.app.AlertDialog.Builder(this, R.style.MyPopup)
|
|
||||||
.setTitle("Commenting Rules")
|
|
||||||
.setMessage(
|
|
||||||
"I WILL BAN YOU WITHOUT HESITATION\n" +
|
|
||||||
"1. No racism\n" +
|
|
||||||
"2. No hate speech\n" +
|
|
||||||
"3. No spam\n" +
|
|
||||||
"4. No NSFW content\n" +
|
|
||||||
"6. ENGLISH ONLY\n" +
|
|
||||||
"7. No self promotion\n" +
|
|
||||||
"8. No impersonation\n" +
|
|
||||||
"9. No harassment\n" +
|
|
||||||
"10. No illegal content\n" +
|
|
||||||
"11. Anything you know you shouldn't comment\n"
|
|
||||||
)
|
|
||||||
.setPositiveButton("I Understand") { dialog, _ ->
|
|
||||||
dialog.dismiss()
|
|
||||||
PrefManager.setVal(PrefName.FirstComment, false)
|
|
||||||
onSuccess()
|
|
||||||
}
|
|
||||||
.setNegativeButton("Cancel") { dialog, _ ->
|
|
||||||
dialog.dismiss()
|
|
||||||
}
|
|
||||||
val dialog = alertDialog.show()
|
|
||||||
dialog?.window?.setDimAmount(0.8f)
|
|
||||||
|
|
||||||
|
if (PrefManager.getVal(PrefName.FirstComment)) {
|
||||||
|
showCommentRulesDialog()
|
||||||
} else {
|
} else {
|
||||||
onSuccess()
|
processComment()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum class InteractionState {
|
||||||
|
NONE, EDIT, REPLY
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun loadAndDisplayComments() {
|
||||||
|
binding.commentsProgressBar.visibility = View.VISIBLE
|
||||||
|
binding.commentsList.visibility = View.GONE
|
||||||
|
adapter.clear()
|
||||||
|
section.clear()
|
||||||
|
|
||||||
|
val comments = withContext(Dispatchers.IO) {
|
||||||
|
CommentsAPI.getCommentsForId(mediaId)
|
||||||
|
}
|
||||||
|
|
||||||
|
val sortedComments = sortComments(comments?.comments)
|
||||||
|
sortedComments.forEach {
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
section.add(CommentItem(it, buildMarkwon(), section, this@CommentsActivity))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
totalPages = comments?.totalPages ?: 1
|
||||||
|
binding.commentsProgressBar.visibility = View.GONE
|
||||||
|
binding.commentsList.visibility = View.VISIBLE
|
||||||
|
adapter.add(section)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun sortComments(comments: List<Comment>?): List<Comment> {
|
||||||
|
if (comments == null) return emptyList()
|
||||||
|
val sortOrder = PrefManager.getVal(PrefName.CommentSortOrder, "newest")
|
||||||
|
return when (sortOrder) {
|
||||||
|
"newest" -> comments.sortedByDescending { CommentItem.timestampToMillis(it.timestamp) }
|
||||||
|
"oldest" -> comments.sortedBy { CommentItem.timestampToMillis(it.timestamp) }
|
||||||
|
"highest_rated" -> comments.sortedByDescending { it.upvotes - it.downvotes }
|
||||||
|
"lowest_rated" -> comments.sortedBy { it.upvotes - it.downvotes }
|
||||||
|
else -> comments
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets the old state of the comment input
|
||||||
|
* @return the old state
|
||||||
|
*/
|
||||||
|
private fun resetOldState(): InteractionState {
|
||||||
|
val oldState = interactionState
|
||||||
|
interactionState = InteractionState.NONE
|
||||||
|
return when (oldState) {
|
||||||
|
InteractionState.EDIT -> {
|
||||||
|
binding.commentInput.setText("")
|
||||||
|
val imm = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
|
||||||
|
imm.hideSoftInputFromWindow(binding.commentInput.windowToken, 0)
|
||||||
|
commentWithInteraction?.editing(false)
|
||||||
|
InteractionState.EDIT
|
||||||
|
}
|
||||||
|
|
||||||
|
InteractionState.REPLY -> {
|
||||||
|
binding.commentInput.setText("")
|
||||||
|
val imm = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
|
||||||
|
imm.hideSoftInputFromWindow(binding.commentInput.windowToken, 0)
|
||||||
|
commentWithInteraction?.replying(false)
|
||||||
|
InteractionState.REPLY
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
InteractionState.NONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback from the comment item to edit the comment
|
||||||
|
* Called every time the edit button is clicked
|
||||||
|
* @param comment the comment to edit
|
||||||
|
*/
|
||||||
|
fun editCallback(comment: CommentItem) {
|
||||||
|
if (resetOldState() == InteractionState.EDIT) return
|
||||||
|
commentWithInteraction = comment
|
||||||
|
binding.commentInput.setText(comment.comment.content)
|
||||||
|
binding.commentInput.requestFocus()
|
||||||
|
binding.commentInput.setSelection(binding.commentInput.text.length)
|
||||||
|
val imm = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
|
||||||
|
imm.showSoftInput(binding.commentInput, InputMethodManager.SHOW_IMPLICIT)
|
||||||
|
interactionState = InteractionState.EDIT
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback from the comment item to reply to the comment
|
||||||
|
* Called every time the reply button is clicked
|
||||||
|
* @param comment the comment to reply to
|
||||||
|
*/
|
||||||
|
fun replyCallback(comment: CommentItem) {
|
||||||
|
if (resetOldState() == InteractionState.REPLY) return
|
||||||
|
commentWithInteraction = comment
|
||||||
|
binding.commentInput.requestFocus()
|
||||||
|
binding.commentInput.setSelection(binding.commentInput.text.length)
|
||||||
|
val imm = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
|
||||||
|
imm.showSoftInput(binding.commentInput, InputMethodManager.SHOW_IMPLICIT)
|
||||||
|
interactionState = InteractionState.REPLY
|
||||||
|
}
|
||||||
|
|
||||||
|
fun viewReplyCallback(comment: CommentItem) {
|
||||||
|
lifecycleScope.launch {
|
||||||
|
val replies = withContext(Dispatchers.IO) {
|
||||||
|
CommentsAPI.getRepliesFromId(comment.comment.commentId)
|
||||||
|
}
|
||||||
|
replies?.comments?.forEach {
|
||||||
|
comment.repliesSection.add(
|
||||||
|
CommentItem(
|
||||||
|
it,
|
||||||
|
buildMarkwon(),
|
||||||
|
comment.repliesSection,
|
||||||
|
this@CommentsActivity)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows the comment rules dialog
|
||||||
|
* Called when the user tries to comment for the first time
|
||||||
|
*/
|
||||||
|
private fun showCommentRulesDialog() {
|
||||||
|
val alertDialog = android.app.AlertDialog.Builder(this, R.style.MyPopup)
|
||||||
|
.setTitle("Commenting Rules")
|
||||||
|
.setMessage(
|
||||||
|
"I WILL BAN YOU WITHOUT HESITATION\n" +
|
||||||
|
"1. No racism\n" +
|
||||||
|
"2. No hate speech\n" +
|
||||||
|
"3. No spam\n" +
|
||||||
|
"4. No NSFW content\n" +
|
||||||
|
"6. ENGLISH ONLY\n" +
|
||||||
|
"7. No self promotion\n" +
|
||||||
|
"8. No impersonation\n" +
|
||||||
|
"9. No harassment\n" +
|
||||||
|
"10. No illegal content\n" +
|
||||||
|
"11. Anything you know you shouldn't comment\n"
|
||||||
|
)
|
||||||
|
.setPositiveButton("I Understand") { dialog, _ ->
|
||||||
|
dialog.dismiss()
|
||||||
|
PrefManager.setVal(PrefName.FirstComment, false)
|
||||||
|
processComment()
|
||||||
|
}
|
||||||
|
.setNegativeButton("Cancel") { dialog, _ ->
|
||||||
|
dialog.dismiss()
|
||||||
|
}
|
||||||
|
val dialog = alertDialog.show()
|
||||||
|
dialog?.window?.setDimAmount(0.8f)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun processComment() {
|
||||||
|
val commentText = binding.commentInput.text.toString()
|
||||||
|
if (commentText.isEmpty()) {
|
||||||
|
snackString("Comment cannot be empty")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.commentInput.text.clear()
|
||||||
|
lifecycleScope.launch {
|
||||||
|
if (interactionState == InteractionState.EDIT) {
|
||||||
|
handleEditComment(commentText)
|
||||||
|
} else {
|
||||||
|
handleNewComment(commentText)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun handleEditComment(commentText: String) {
|
||||||
|
val success = withContext(Dispatchers.IO) {
|
||||||
|
CommentsAPI.editComment(commentWithInteraction?.comment?.commentId ?: return@withContext false, commentText)
|
||||||
|
}
|
||||||
|
if (success) {
|
||||||
|
updateCommentInSection(commentText)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateCommentInSection(commentText: String) {
|
||||||
|
val groups = section.groups
|
||||||
|
groups.forEach { item ->
|
||||||
|
if (item is CommentItem && item.comment.commentId == commentWithInteraction?.comment?.commentId) {
|
||||||
|
updateCommentItem(item, commentText)
|
||||||
|
snackString("Comment edited")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateCommentItem(item: CommentItem, commentText: String) {
|
||||||
|
item.comment.content = commentText
|
||||||
|
val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault())
|
||||||
|
dateFormat.timeZone = TimeZone.getTimeZone("UTC")
|
||||||
|
item.comment.timestamp = dateFormat.format(System.currentTimeMillis())
|
||||||
|
item.notifyChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun handleNewComment(commentText: String) {
|
||||||
|
val success = withContext(Dispatchers.IO) {
|
||||||
|
CommentsAPI.comment(
|
||||||
|
mediaId,
|
||||||
|
if (interactionState == InteractionState.REPLY) commentWithInteraction?.comment?.commentId else null,
|
||||||
|
commentText
|
||||||
|
)
|
||||||
|
}
|
||||||
|
success?.let {
|
||||||
|
if (interactionState == InteractionState.REPLY) {
|
||||||
|
commentWithInteraction?.repliesSection?.add(
|
||||||
|
0,
|
||||||
|
CommentItem(it, buildMarkwon(), commentWithInteraction!!.repliesSection, this@CommentsActivity)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
section.add(
|
||||||
|
0,
|
||||||
|
CommentItem(it, buildMarkwon(), section, this@CommentsActivity)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds the markwon instance with all the plugins
|
||||||
|
* @return the markwon instance
|
||||||
|
*/
|
||||||
private fun buildMarkwon(): Markwon {
|
private fun buildMarkwon(): Markwon {
|
||||||
val markwon = Markwon.builder(this)
|
val markwon = Markwon.builder(this)
|
||||||
.usePlugin(SoftBreakAddsNewLinePlugin.create())
|
.usePlugin(SoftBreakAddsNewLinePlugin.create())
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue