feat: comments targeted at database
This commit is contained in:
parent
1694a1cb24
commit
a73c4cd678
17 changed files with 544 additions and 228 deletions
|
@ -48,7 +48,7 @@ android {
|
||||||
buildTypes {
|
buildTypes {
|
||||||
alpha {
|
alpha {
|
||||||
applicationIdSuffix ".beta" // keep as beta by popular request
|
applicationIdSuffix ".beta" // keep as beta by popular request
|
||||||
versionNameSuffix "-alpha01"
|
versionNameSuffix "-alpha02"
|
||||||
manifestPlaceholders = [icon_placeholder: "@mipmap/ic_launcher_alpha", icon_placeholder_round: "@mipmap/ic_launcher_alpha_round"]
|
manifestPlaceholders = [icon_placeholder: "@mipmap/ic_launcher_alpha", icon_placeholder_round: "@mipmap/ic_launcher_alpha_round"]
|
||||||
debuggable System.getenv("CI") == null
|
debuggable System.getenv("CI") == null
|
||||||
isDefault true
|
isDefault true
|
||||||
|
|
|
@ -109,7 +109,8 @@
|
||||||
android:name=".others.imagesearch.ImageSearchActivity"
|
android:name=".others.imagesearch.ImageSearchActivity"
|
||||||
android:parentActivityName=".MainActivity" />
|
android:parentActivityName=".MainActivity" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".media.comments.CommentsFragment" />
|
android:name=".media.comments.CommentsActivity"
|
||||||
|
android:windowSoftInputMode="adjustResize|stateHidden" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".media.SearchActivity"
|
android:name=".media.SearchActivity"
|
||||||
android:parentActivityName=".MainActivity" />
|
android:parentActivityName=".MainActivity" />
|
||||||
|
|
|
@ -20,6 +20,7 @@ import ani.dantotsu.others.MalScraper
|
||||||
import ani.dantotsu.settings.saving.PrefManager
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
import ani.dantotsu.settings.saving.PrefName
|
import ani.dantotsu.settings.saving.PrefName
|
||||||
import ani.dantotsu.snackString
|
import ani.dantotsu.snackString
|
||||||
|
import ani.dantotsu.toast
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.awaitAll
|
import kotlinx.coroutines.awaitAll
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
|
|
|
@ -9,8 +9,6 @@ import ani.dantotsu.settings.saving.PrefName
|
||||||
import ani.dantotsu.snackString
|
import ani.dantotsu.snackString
|
||||||
import com.lagradost.nicehttp.Requests
|
import com.lagradost.nicehttp.Requests
|
||||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import kotlinx.serialization.KSerializer
|
import kotlinx.serialization.KSerializer
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
@ -27,7 +25,7 @@ import javax.crypto.Cipher
|
||||||
import javax.crypto.spec.SecretKeySpec
|
import javax.crypto.spec.SecretKeySpec
|
||||||
|
|
||||||
object CommentsAPI {
|
object CommentsAPI {
|
||||||
val address: String = "http://10.0.2.2:8081"
|
val address: String = "https://1224665.xyz:443"
|
||||||
val appSecret = BuildConfig.APP_SECRET
|
val appSecret = BuildConfig.APP_SECRET
|
||||||
var authToken: String? = null
|
var authToken: String? = null
|
||||||
var userId: String? = null
|
var userId: String? = null
|
||||||
|
@ -55,7 +53,7 @@ object CommentsAPI {
|
||||||
val json = request.post(url)
|
val json = request.post(url)
|
||||||
val res = json.code == 200
|
val res = json.code == 200
|
||||||
if (!res) {
|
if (!res) {
|
||||||
errorReason(json.code)
|
errorReason(json.code, json.text)
|
||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
@ -73,7 +71,7 @@ object CommentsAPI {
|
||||||
val json = request.post(url, requestBody = body.build())
|
val json = request.post(url, requestBody = body.build())
|
||||||
val res = json.code == 200
|
val res = json.code == 200
|
||||||
if (!res) {
|
if (!res) {
|
||||||
errorReason(json.code)
|
errorReason(json.code, json.text)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
val parsed = try {
|
val parsed = try {
|
||||||
|
@ -105,7 +103,32 @@ object CommentsAPI {
|
||||||
val json = request.delete(url)
|
val json = request.delete(url)
|
||||||
val res = json.code == 200
|
val res = json.code == 200
|
||||||
if (!res) {
|
if (!res) {
|
||||||
errorReason(json.code)
|
errorReason(json.code, json.text)
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun editComment(commentId: Int, content: String): Boolean {
|
||||||
|
val url = "$address/comments/$commentId"
|
||||||
|
val body = FormBody.Builder()
|
||||||
|
.add("content", content)
|
||||||
|
.build()
|
||||||
|
val request = requestBuilder()
|
||||||
|
val json = request.put(url, requestBody = body)
|
||||||
|
val res = json.code == 200
|
||||||
|
if (!res) {
|
||||||
|
errorReason(json.code, json.text)
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun banUser(userId: String): Boolean {
|
||||||
|
val url = "$address/ban/$userId"
|
||||||
|
val request = requestBuilder()
|
||||||
|
val json = request.post(url)
|
||||||
|
val res = json.code == 200
|
||||||
|
if (!res) {
|
||||||
|
errorReason(json.code, json.text)
|
||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
@ -125,7 +148,7 @@ object CommentsAPI {
|
||||||
val parsed = try {
|
val parsed = try {
|
||||||
Json.decodeFromString<AuthResponse>(json.text)
|
Json.decodeFromString<AuthResponse>(json.text)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
println("comment: $e")
|
snackString("Failed to login to comments API: ${e.printStackTrace()}")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
authToken = parsed.authToken
|
authToken = parsed.authToken
|
||||||
|
@ -155,12 +178,12 @@ object CommentsAPI {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun errorReason(code: Int) {
|
private fun errorReason(code: Int, reason: String? = null) {
|
||||||
val error = when (code) {
|
val error = when (code) {
|
||||||
429 -> "Rate limited. :("
|
429 -> "Rate limited. :("
|
||||||
else -> "Failed to connect"
|
else -> "Failed to connect"
|
||||||
}
|
}
|
||||||
snackString("Error $code: $error")
|
snackString("Error $code: ${reason ?: error}")
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("GetInstance")
|
@SuppressLint("GetInstance")
|
||||||
|
@ -221,7 +244,7 @@ data class Comment(
|
||||||
@SerialName("parent_comment_id")
|
@SerialName("parent_comment_id")
|
||||||
val parentCommentId: Int?,
|
val parentCommentId: Int?,
|
||||||
@SerialName("content")
|
@SerialName("content")
|
||||||
val content: String,
|
var content: String,
|
||||||
@SerialName("timestamp")
|
@SerialName("timestamp")
|
||||||
val timestamp: String,
|
val timestamp: String,
|
||||||
@SerialName("deleted")
|
@SerialName("deleted")
|
||||||
|
@ -236,7 +259,13 @@ data class Comment(
|
||||||
@SerialName("username")
|
@SerialName("username")
|
||||||
val username: String,
|
val username: String,
|
||||||
@SerialName("profile_picture_url")
|
@SerialName("profile_picture_url")
|
||||||
val profilePictureUrl: String?
|
val profilePictureUrl: String?,
|
||||||
|
@SerialName("is_mod")
|
||||||
|
@Serializable(with = NumericBooleanSerializer::class)
|
||||||
|
val isMod: Boolean? = null,
|
||||||
|
@SerialName("is_admin")
|
||||||
|
@Serializable(with = NumericBooleanSerializer::class)
|
||||||
|
val isAdmin: Boolean? = null
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
|
|
@ -76,7 +76,7 @@ class MediaInfoFragment : Fragment() {
|
||||||
loaded = true
|
loaded = true
|
||||||
|
|
||||||
//Youtube
|
//Youtube
|
||||||
if (media.anime!!.youtube != null && PrefManager.getVal(PrefName.ShowYtButton)) {
|
if (media.anime?.youtube != null && PrefManager.getVal(PrefName.ShowYtButton)) {
|
||||||
binding.animeSourceYT.visibility = View.VISIBLE
|
binding.animeSourceYT.visibility = View.VISIBLE
|
||||||
binding.animeSourceYT.setOnClickListener {
|
binding.animeSourceYT.setOnClickListener {
|
||||||
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(media.anime.youtube))
|
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(media.anime.youtube))
|
||||||
|
|
|
@ -15,10 +15,11 @@ import androidx.core.content.ContextCompat.startActivity
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import ani.dantotsu.*
|
import ani.dantotsu.*
|
||||||
|
import ani.dantotsu.connections.comments.CommentsAPI
|
||||||
import ani.dantotsu.databinding.DialogLayoutBinding
|
import ani.dantotsu.databinding.DialogLayoutBinding
|
||||||
import ani.dantotsu.databinding.ItemAnimeWatchBinding
|
import ani.dantotsu.databinding.ItemAnimeWatchBinding
|
||||||
import ani.dantotsu.databinding.ItemChipBinding
|
import ani.dantotsu.databinding.ItemChipBinding
|
||||||
import ani.dantotsu.media.comments.CommentsFragment
|
import ani.dantotsu.media.comments.CommentsActivity
|
||||||
import ani.dantotsu.media.Media
|
import ani.dantotsu.media.Media
|
||||||
import ani.dantotsu.media.MediaDetailsActivity
|
import ani.dantotsu.media.MediaDetailsActivity
|
||||||
import ani.dantotsu.media.SourceSearchDialogFragment
|
import ani.dantotsu.media.SourceSearchDialogFragment
|
||||||
|
@ -60,11 +61,11 @@ class AnimeWatchAdapter(
|
||||||
val binding = holder.binding
|
val binding = holder.binding
|
||||||
_binding = binding
|
_binding = binding
|
||||||
//CommentsAPI
|
//CommentsAPI
|
||||||
binding.animeComments.visibility = View.VISIBLE
|
binding.animeComments.visibility = if (CommentsAPI.userId == null) View.GONE else View.VISIBLE
|
||||||
binding.animeComments.setOnClickListener {
|
binding.animeComments.setOnClickListener {
|
||||||
startActivity(
|
startActivity(
|
||||||
fragment.requireContext(),
|
fragment.requireContext(),
|
||||||
Intent(fragment.requireContext(), CommentsFragment::class.java)
|
Intent(fragment.requireContext(), CommentsActivity::class.java)
|
||||||
.putExtra("mediaId", media.id),
|
.putExtra("mediaId", media.id),
|
||||||
null
|
null
|
||||||
)
|
)
|
||||||
|
|
|
@ -20,9 +20,10 @@ import java.text.SimpleDateFormat
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import java.util.TimeZone
|
import java.util.TimeZone
|
||||||
|
|
||||||
class CommentItem(private 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
|
||||||
) : BindableItem<ItemCommentsBinding>() {
|
) : BindableItem<ItemCommentsBinding>() {
|
||||||
|
|
||||||
override fun bind(viewBinding: ItemCommentsBinding, position: Int) {
|
override fun bind(viewBinding: ItemCommentsBinding, position: Int) {
|
||||||
|
@ -30,13 +31,29 @@ class CommentItem(private val comment: Comment,
|
||||||
val node = markwon.parse(comment.content)
|
val node = markwon.parse(comment.content)
|
||||||
val spanned = markwon.render(node)
|
val spanned = markwon.render(node)
|
||||||
markwon.setParsedMarkdown(viewBinding.commentText, viewBinding.commentText.setSpoilerText(spanned, markwon))
|
markwon.setParsedMarkdown(viewBinding.commentText, viewBinding.commentText.setSpoilerText(spanned, markwon))
|
||||||
viewBinding.commentDelete.visibility = if (isUserComment) View.VISIBLE else View.GONE
|
viewBinding.commentDelete.visibility = if (isUserComment || CommentsAPI.isAdmin || CommentsAPI.isMod) View.VISIBLE else View.GONE
|
||||||
|
viewBinding.commentBanUser.visibility = if (CommentsAPI.isAdmin || CommentsAPI.isMod) View.VISIBLE else View.GONE
|
||||||
viewBinding.commentEdit.visibility = if (isUserComment) View.VISIBLE else View.GONE
|
viewBinding.commentEdit.visibility = if (isUserComment) View.VISIBLE else View.GONE
|
||||||
viewBinding.commentReply.visibility = View.GONE //TODO: implement reply
|
viewBinding.commentReply.visibility = View.GONE //TODO: implement reply
|
||||||
viewBinding.commentTotalReplies.visibility = View.GONE //TODO: implement reply
|
viewBinding.commentTotalReplies.visibility = View.GONE //TODO: implement reply
|
||||||
viewBinding.commentReply.setOnClickListener {
|
viewBinding.commentReply.setOnClickListener {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
var isEditing = false
|
||||||
|
viewBinding.commentEdit.setOnClickListener {
|
||||||
|
if (!isEditing) {
|
||||||
|
viewBinding.commentEdit.text = "Cancel"
|
||||||
|
isEditing = true
|
||||||
|
editCallback(this)
|
||||||
|
} else {
|
||||||
|
viewBinding.commentEdit.text = "Edit"
|
||||||
|
isEditing = false
|
||||||
|
editCallback(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
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.commentDelete.setOnClickListener {
|
viewBinding.commentDelete.setOnClickListener {
|
||||||
val scope = CoroutineScope(Dispatchers.Main + SupervisorJob())
|
val scope = CoroutineScope(Dispatchers.Main + SupervisorJob())
|
||||||
scope.launch {
|
scope.launch {
|
||||||
|
@ -47,6 +64,15 @@ class CommentItem(private val comment: Comment,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
viewBinding.commentBanUser.setOnClickListener {
|
||||||
|
val scope = CoroutineScope(Dispatchers.Main + SupervisorJob())
|
||||||
|
scope.launch {
|
||||||
|
val success = CommentsAPI.banUser(comment.userId)
|
||||||
|
if (success) {
|
||||||
|
snackString("User Banned")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
//fill the icon if the user has liked the comment
|
//fill the icon if the user has liked the comment
|
||||||
setVoteButtons(viewBinding)
|
setVoteButtons(viewBinding)
|
||||||
viewBinding.commentUpVote.setOnClickListener {
|
viewBinding.commentUpVote.setOnClickListener {
|
||||||
|
@ -137,4 +163,11 @@ class CommentItem(private val comment: Comment,
|
||||||
"now"
|
"now"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun timestampToMillis(timestamp: String): Long {
|
||||||
|
val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
|
||||||
|
dateFormat.timeZone = TimeZone.getTimeZone("UTC")
|
||||||
|
val parsedDate = dateFormat.parse(timestamp)
|
||||||
|
return parsedDate?.time ?: 0
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,332 @@
|
||||||
|
package ani.dantotsu.media.comments
|
||||||
|
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.text.TextWatcher
|
||||||
|
import android.view.View
|
||||||
|
import android.view.inputmethod.InputMethodManager
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.appcompat.widget.PopupMenu
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import ani.dantotsu.R
|
||||||
|
import ani.dantotsu.connections.anilist.Anilist
|
||||||
|
import ani.dantotsu.connections.comments.CommentsAPI
|
||||||
|
import ani.dantotsu.databinding.ActivityCommentsBinding
|
||||||
|
import ani.dantotsu.initActivity
|
||||||
|
import ani.dantotsu.loadImage
|
||||||
|
import ani.dantotsu.snackString
|
||||||
|
import ani.dantotsu.themes.ThemeManager
|
||||||
|
import com.bumptech.glide.Glide
|
||||||
|
import com.bumptech.glide.RequestBuilder
|
||||||
|
import com.bumptech.glide.RequestManager
|
||||||
|
import com.bumptech.glide.load.DataSource
|
||||||
|
import com.bumptech.glide.load.engine.GlideException
|
||||||
|
import com.bumptech.glide.load.resource.gif.GifDrawable
|
||||||
|
import com.bumptech.glide.request.RequestListener
|
||||||
|
import com.bumptech.glide.request.target.Target
|
||||||
|
import com.xwray.groupie.GroupieAdapter
|
||||||
|
import com.xwray.groupie.Section
|
||||||
|
import io.noties.markwon.Markwon
|
||||||
|
import io.noties.markwon.SoftBreakAddsNewLinePlugin
|
||||||
|
import io.noties.markwon.editor.MarkwonEditor
|
||||||
|
import io.noties.markwon.editor.MarkwonEditorTextWatcher
|
||||||
|
import io.noties.markwon.ext.strikethrough.StrikethroughPlugin
|
||||||
|
import io.noties.markwon.ext.tables.TablePlugin
|
||||||
|
import io.noties.markwon.ext.tasklist.TaskListPlugin
|
||||||
|
import io.noties.markwon.html.HtmlPlugin
|
||||||
|
import io.noties.markwon.image.AsyncDrawable
|
||||||
|
import io.noties.markwon.image.glide.GlideImagesPlugin
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
|
class CommentsActivity : AppCompatActivity() {
|
||||||
|
lateinit var binding: ActivityCommentsBinding
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
ThemeManager(this).applyTheme()
|
||||||
|
binding = ActivityCommentsBinding.inflate(layoutInflater)
|
||||||
|
setContentView(binding.root)
|
||||||
|
initActivity(this)
|
||||||
|
//get the media id from the intent
|
||||||
|
val mediaId = intent.getIntExtra("mediaId", -1)
|
||||||
|
if (mediaId == -1) {
|
||||||
|
snackString("Invalid Media ID")
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
val adapter = GroupieAdapter()
|
||||||
|
val section = Section()
|
||||||
|
val markwon = buildMarkwon()
|
||||||
|
|
||||||
|
binding.commentUserAvatar.loadImage(Anilist.avatar)
|
||||||
|
binding.commentTitle.text = getText(R.string.comments)
|
||||||
|
val markwonEditor = MarkwonEditor.create(markwon)
|
||||||
|
binding.commentInput.addTextChangedListener(
|
||||||
|
MarkwonEditorTextWatcher.withProcess(
|
||||||
|
markwonEditor
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
var editing = false
|
||||||
|
var editingCommentId = -1
|
||||||
|
fun editCallback(comment: CommentItem) {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.commentsRefresh.setOnRefreshListener {
|
||||||
|
lifecycleScope.launch {
|
||||||
|
binding.commentsList.visibility = View.GONE
|
||||||
|
adapter.clear()
|
||||||
|
section.clear()
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
val comments = CommentsAPI.getCommentsForId(mediaId)
|
||||||
|
comments?.comments?.forEach {
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
section.add(CommentItem(it, buildMarkwon(), section) { comment ->
|
||||||
|
editCallback(comment)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
adapter.add(section)
|
||||||
|
binding.commentsList.visibility = View.VISIBLE
|
||||||
|
binding.commentsRefresh.isRefreshing = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var pagesLoaded = 1
|
||||||
|
var totalPages = 1
|
||||||
|
binding.commentsList.adapter = adapter
|
||||||
|
binding.commentsList.layoutManager = LinearLayoutManager(this)
|
||||||
|
lifecycleScope.launch {
|
||||||
|
binding.commentsProgressBar.visibility = View.VISIBLE
|
||||||
|
binding.commentsList.visibility = View.GONE
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
val comments = CommentsAPI.getCommentsForId(mediaId)
|
||||||
|
comments?.comments?.forEach {
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
section.add(CommentItem(it, buildMarkwon(), section) { comment ->
|
||||||
|
editCallback(comment)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
totalPages = comments?.totalPages ?: 1
|
||||||
|
}
|
||||||
|
binding.commentsProgressBar.visibility = View.GONE
|
||||||
|
binding.commentsList.visibility = View.VISIBLE
|
||||||
|
adapter.add(section)
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.commentSort.setOnClickListener {
|
||||||
|
val popup = PopupMenu(this, it)
|
||||||
|
popup.setOnMenuItemClickListener { item ->
|
||||||
|
when (item.itemId) {
|
||||||
|
R.id.comment_sort_newest -> {
|
||||||
|
val groups = section.groups
|
||||||
|
groups.sortByDescending { comment ->
|
||||||
|
comment as CommentItem
|
||||||
|
comment.timestampToMillis(comment.comment.timestamp)
|
||||||
|
}
|
||||||
|
section.update(groups)
|
||||||
|
binding.commentsList.scrollToPosition(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
R.id.comment_sort_oldest -> {
|
||||||
|
val groups = section.groups
|
||||||
|
groups.sortBy { comment ->
|
||||||
|
comment as CommentItem
|
||||||
|
comment.timestampToMillis(comment.comment.timestamp)
|
||||||
|
}
|
||||||
|
section.update(groups)
|
||||||
|
binding.commentsList.scrollToPosition(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
R.id.comment_sort_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 -> {
|
||||||
|
val groups = section.groups
|
||||||
|
groups.sortBy { comment ->
|
||||||
|
comment as CommentItem
|
||||||
|
comment.comment.upvotes - comment.comment.downvotes
|
||||||
|
}
|
||||||
|
section.update(groups)
|
||||||
|
binding.commentsList.scrollToPosition(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
popup.inflate(R.menu.comments_sort_menu)
|
||||||
|
popup.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
var fetching = false
|
||||||
|
//if we have scrolled to the bottom of the list, load more comments
|
||||||
|
binding.commentsList.addOnScrollListener(object :
|
||||||
|
androidx.recyclerview.widget.RecyclerView.OnScrollListener() {
|
||||||
|
override fun onScrolled(
|
||||||
|
recyclerView: androidx.recyclerview.widget.RecyclerView,
|
||||||
|
dx: Int,
|
||||||
|
dy: Int
|
||||||
|
) {
|
||||||
|
super.onScrolled(recyclerView, dx, dy)
|
||||||
|
if (!recyclerView.canScrollVertically(1)) {
|
||||||
|
if (pagesLoaded < totalPages && !fetching) {
|
||||||
|
fetching = true
|
||||||
|
lifecycleScope.launch {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
val comments =
|
||||||
|
CommentsAPI.getCommentsForId(mediaId, pagesLoaded + 1)
|
||||||
|
comments?.comments?.forEach {
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
section.add(
|
||||||
|
CommentItem(
|
||||||
|
it,
|
||||||
|
buildMarkwon(),
|
||||||
|
section
|
||||||
|
) { comment ->
|
||||||
|
editCallback(comment)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
totalPages = comments?.totalPages ?: 1
|
||||||
|
}
|
||||||
|
pagesLoaded++
|
||||||
|
fetching = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
binding.commentInput.addTextChangedListener(object : TextWatcher {
|
||||||
|
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun afterTextChanged(s: android.text.Editable?) {
|
||||||
|
if (binding.commentInput.text.length > 300) {
|
||||||
|
binding.commentInput.text.delete(300, binding.commentInput.text.length)
|
||||||
|
snackString("Comment cannot be longer than 300 characters")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
binding.commentSend.setOnClickListener {
|
||||||
|
if (CommentsAPI.isBanned) {
|
||||||
|
snackString("You are banned from commenting :(")
|
||||||
|
return@setOnClickListener
|
||||||
|
}
|
||||||
|
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
|
||||||
|
item.notifyChanged()
|
||||||
|
snackString("Comment edited")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val success = withContext(Dispatchers.IO) {
|
||||||
|
CommentsAPI.comment(mediaId, null, it)
|
||||||
|
}
|
||||||
|
if (success != null)
|
||||||
|
section.add(0, CommentItem(success, buildMarkwon(), section) { comment ->
|
||||||
|
editCallback(comment)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
snackString("Comment cannot be empty")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildMarkwon(): Markwon {
|
||||||
|
val markwon = Markwon.builder(this)
|
||||||
|
.usePlugin(SoftBreakAddsNewLinePlugin.create())
|
||||||
|
.usePlugin(StrikethroughPlugin.create())
|
||||||
|
.usePlugin(HtmlPlugin.create())
|
||||||
|
.usePlugin(TablePlugin.create(this))
|
||||||
|
.usePlugin(TaskListPlugin.create(this))
|
||||||
|
.usePlugin(GlideImagesPlugin.create(object : GlideImagesPlugin.GlideStore {
|
||||||
|
|
||||||
|
private val requestManager: RequestManager =
|
||||||
|
Glide.with(this@CommentsActivity).apply {
|
||||||
|
addDefaultRequestListener(object : RequestListener<Any> {
|
||||||
|
override fun onResourceReady(
|
||||||
|
resource: Any,
|
||||||
|
model: Any,
|
||||||
|
target: Target<Any>,
|
||||||
|
dataSource: DataSource,
|
||||||
|
isFirstResource: Boolean
|
||||||
|
): Boolean {
|
||||||
|
if (resource is GifDrawable) {
|
||||||
|
resource.start()
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLoadFailed(
|
||||||
|
e: GlideException?,
|
||||||
|
model: Any?,
|
||||||
|
target: Target<Any>,
|
||||||
|
isFirstResource: Boolean
|
||||||
|
): Boolean {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun load(drawable: AsyncDrawable): RequestBuilder<Drawable> {
|
||||||
|
return requestManager.load(drawable.destination)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun cancel(target: Target<*>) {
|
||||||
|
requestManager.clear(target)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.build()
|
||||||
|
return markwon
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,183 +0,0 @@
|
||||||
package ani.dantotsu.media.comments
|
|
||||||
|
|
||||||
import android.graphics.drawable.Drawable
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.text.TextWatcher
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
|
||||||
import androidx.appcompat.widget.PopupMenu
|
|
||||||
import androidx.core.view.updateLayoutParams
|
|
||||||
import androidx.lifecycle.lifecycleScope
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
|
||||||
import ani.dantotsu.R
|
|
||||||
import ani.dantotsu.connections.anilist.Anilist
|
|
||||||
import ani.dantotsu.connections.comments.Comment
|
|
||||||
import ani.dantotsu.connections.comments.CommentsAPI
|
|
||||||
import ani.dantotsu.databinding.FragmentCommentsBinding
|
|
||||||
import ani.dantotsu.databinding.ItemCommentsBinding
|
|
||||||
import ani.dantotsu.loadImage
|
|
||||||
import ani.dantotsu.navBarHeight
|
|
||||||
import ani.dantotsu.settings.saving.PrefManager
|
|
||||||
import ani.dantotsu.settings.saving.PrefName
|
|
||||||
import ani.dantotsu.snackString
|
|
||||||
import ani.dantotsu.statusBarHeight
|
|
||||||
import ani.dantotsu.themes.ThemeManager
|
|
||||||
import com.bumptech.glide.Glide
|
|
||||||
import com.bumptech.glide.RequestBuilder
|
|
||||||
import com.bumptech.glide.RequestManager
|
|
||||||
import com.bumptech.glide.load.DataSource
|
|
||||||
import com.bumptech.glide.load.engine.GlideException
|
|
||||||
import com.bumptech.glide.load.resource.gif.GifDrawable
|
|
||||||
import com.bumptech.glide.request.RequestListener
|
|
||||||
import com.bumptech.glide.request.target.Target
|
|
||||||
import com.xwray.groupie.GroupAdapter
|
|
||||||
import com.xwray.groupie.GroupieAdapter
|
|
||||||
import com.xwray.groupie.Section
|
|
||||||
import com.xwray.groupie.viewbinding.GroupieViewHolder
|
|
||||||
import io.noties.markwon.Markwon
|
|
||||||
import io.noties.markwon.SoftBreakAddsNewLinePlugin
|
|
||||||
import io.noties.markwon.editor.MarkwonEditor
|
|
||||||
import io.noties.markwon.editor.MarkwonEditorTextWatcher
|
|
||||||
import io.noties.markwon.ext.strikethrough.StrikethroughPlugin
|
|
||||||
import io.noties.markwon.ext.tables.TablePlugin
|
|
||||||
import io.noties.markwon.ext.tasklist.TaskListPlugin
|
|
||||||
import io.noties.markwon.html.HtmlPlugin
|
|
||||||
import io.noties.markwon.image.AsyncDrawable
|
|
||||||
import io.noties.markwon.image.glide.GlideImagesPlugin
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
|
|
||||||
class CommentsFragment : AppCompatActivity(){
|
|
||||||
lateinit var binding: FragmentCommentsBinding
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
ThemeManager(this).applyTheme()
|
|
||||||
binding = FragmentCommentsBinding.inflate(layoutInflater)
|
|
||||||
setContentView(binding.root)
|
|
||||||
//get the media id from the intent
|
|
||||||
val mediaId = intent.getIntExtra("mediaId", -1)
|
|
||||||
if (mediaId == -1) {
|
|
||||||
snackString("Invalid Media ID")
|
|
||||||
finish()
|
|
||||||
}
|
|
||||||
|
|
||||||
val adapter = GroupieAdapter()
|
|
||||||
val section = Section()
|
|
||||||
val markwon = buildMarkwon()
|
|
||||||
|
|
||||||
binding.commentsLayout.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
|
||||||
topMargin = statusBarHeight
|
|
||||||
bottomMargin = navBarHeight
|
|
||||||
}
|
|
||||||
binding.commentUserAvatar.loadImage(Anilist.avatar)
|
|
||||||
binding.commentTitle.text = "Work in progress"
|
|
||||||
val markwonEditor = MarkwonEditor.create(markwon)
|
|
||||||
binding.commentInput.addTextChangedListener(MarkwonEditorTextWatcher.withProcess(markwonEditor))
|
|
||||||
binding.commentSend.setOnClickListener {
|
|
||||||
if (CommentsAPI.isBanned) {
|
|
||||||
snackString("You are banned from commenting :(")
|
|
||||||
return@setOnClickListener
|
|
||||||
}
|
|
||||||
binding.commentInput.text.toString().let {
|
|
||||||
if (it.isNotEmpty()) {
|
|
||||||
binding.commentInput.text.clear()
|
|
||||||
lifecycleScope.launch {
|
|
||||||
val success = withContext(Dispatchers.IO) {
|
|
||||||
CommentsAPI.comment(mediaId, null, it)
|
|
||||||
}
|
|
||||||
if (success != null)
|
|
||||||
section.add(CommentItem(success, buildMarkwon(), section))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
snackString("Comment cannot be empty")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
binding.commentInput.addTextChangedListener(object : TextWatcher {
|
|
||||||
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun afterTextChanged(s: android.text.Editable?) {
|
|
||||||
if (binding.commentInput.text.length > 300) {
|
|
||||||
binding.commentInput.text.delete(300, binding.commentInput.text.length)
|
|
||||||
snackString("Comment cannot be longer than 300 characters")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
binding.commentsList.adapter = adapter
|
|
||||||
binding.commentsList.layoutManager = LinearLayoutManager(this)
|
|
||||||
lifecycleScope.launch {
|
|
||||||
withContext(Dispatchers.IO) {
|
|
||||||
val comments = CommentsAPI.getCommentsForId(mediaId)
|
|
||||||
comments?.comments?.forEach {
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
section.add(CommentItem(it, buildMarkwon(), section))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
adapter.add(section)
|
|
||||||
}
|
|
||||||
binding.commentSort.setOnClickListener {
|
|
||||||
val popup = PopupMenu(this, it)
|
|
||||||
popup.setOnMenuItemClickListener { item ->
|
|
||||||
true
|
|
||||||
}
|
|
||||||
popup.inflate(R.menu.comments_sort_menu)
|
|
||||||
popup.show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun buildMarkwon(): Markwon {
|
|
||||||
val markwon = Markwon.builder(this)
|
|
||||||
.usePlugin(SoftBreakAddsNewLinePlugin.create())
|
|
||||||
.usePlugin(StrikethroughPlugin.create())
|
|
||||||
.usePlugin(HtmlPlugin.create())
|
|
||||||
.usePlugin(TablePlugin.create(this))
|
|
||||||
.usePlugin(TaskListPlugin.create(this))
|
|
||||||
.usePlugin(GlideImagesPlugin.create(object : GlideImagesPlugin.GlideStore {
|
|
||||||
|
|
||||||
private val requestManager: RequestManager = Glide.with(this@CommentsFragment).apply {
|
|
||||||
addDefaultRequestListener(object : RequestListener<Any> {
|
|
||||||
override fun onResourceReady(
|
|
||||||
resource: Any,
|
|
||||||
model: Any,
|
|
||||||
target: Target<Any>,
|
|
||||||
dataSource: DataSource,
|
|
||||||
isFirstResource: Boolean
|
|
||||||
): Boolean {
|
|
||||||
if (resource is GifDrawable) {
|
|
||||||
resource.start()
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onLoadFailed(
|
|
||||||
e: GlideException?,
|
|
||||||
model: Any?,
|
|
||||||
target: Target<Any>,
|
|
||||||
isFirstResource: Boolean
|
|
||||||
): Boolean {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun load(drawable: AsyncDrawable): RequestBuilder<Drawable> {
|
|
||||||
return requestManager.load(drawable.destination)
|
|
||||||
}
|
|
||||||
override fun cancel(target: Target<*>) {
|
|
||||||
requestManager.clear(target)
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
.build()
|
|
||||||
return markwon
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -5,9 +5,6 @@ import android.app.AlertDialog
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.os.Bundle
|
|
||||||
import ani.dantotsu.settings.FAQActivity
|
|
||||||
import androidx.core.content.ContextCompat.startActivity
|
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.ArrayAdapter
|
import android.widget.ArrayAdapter
|
||||||
import android.widget.CheckBox
|
import android.widget.CheckBox
|
||||||
|
@ -15,9 +12,11 @@ import android.widget.ImageButton
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import android.widget.NumberPicker
|
import android.widget.NumberPicker
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.content.ContextCompat.startActivity
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import ani.dantotsu.*
|
import ani.dantotsu.*
|
||||||
|
import ani.dantotsu.connections.comments.CommentsAPI
|
||||||
import ani.dantotsu.databinding.DialogLayoutBinding
|
import ani.dantotsu.databinding.DialogLayoutBinding
|
||||||
import ani.dantotsu.databinding.ItemAnimeWatchBinding
|
import ani.dantotsu.databinding.ItemAnimeWatchBinding
|
||||||
import ani.dantotsu.databinding.ItemChipBinding
|
import ani.dantotsu.databinding.ItemChipBinding
|
||||||
|
@ -25,11 +24,13 @@ import ani.dantotsu.media.Media
|
||||||
import ani.dantotsu.media.MediaDetailsActivity
|
import ani.dantotsu.media.MediaDetailsActivity
|
||||||
import ani.dantotsu.media.SourceSearchDialogFragment
|
import ani.dantotsu.media.SourceSearchDialogFragment
|
||||||
import ani.dantotsu.media.anime.handleProgress
|
import ani.dantotsu.media.anime.handleProgress
|
||||||
|
import ani.dantotsu.media.comments.CommentsActivity
|
||||||
import ani.dantotsu.others.LanguageMapper
|
import ani.dantotsu.others.LanguageMapper
|
||||||
import ani.dantotsu.others.webview.CookieCatcher
|
import ani.dantotsu.others.webview.CookieCatcher
|
||||||
import ani.dantotsu.parsers.DynamicMangaParser
|
import ani.dantotsu.parsers.DynamicMangaParser
|
||||||
import ani.dantotsu.parsers.MangaReadSources
|
import ani.dantotsu.parsers.MangaReadSources
|
||||||
import ani.dantotsu.parsers.MangaSources
|
import ani.dantotsu.parsers.MangaSources
|
||||||
|
import ani.dantotsu.settings.FAQActivity
|
||||||
import ani.dantotsu.settings.saving.PrefManager
|
import ani.dantotsu.settings.saving.PrefManager
|
||||||
import ani.dantotsu.settings.saving.PrefName
|
import ani.dantotsu.settings.saving.PrefName
|
||||||
import ani.dantotsu.subcriptions.Notifications.Companion.openSettings
|
import ani.dantotsu.subcriptions.Notifications.Companion.openSettings
|
||||||
|
@ -66,6 +67,16 @@ class MangaReadAdapter(
|
||||||
_binding = binding
|
_binding = binding
|
||||||
binding.sourceTitle.setText(R.string.chaps)
|
binding.sourceTitle.setText(R.string.chaps)
|
||||||
|
|
||||||
|
binding.animeComments.visibility = if (CommentsAPI.userId == null) View.GONE else View.VISIBLE
|
||||||
|
binding.animeComments.setOnClickListener {
|
||||||
|
startActivity(
|
||||||
|
fragment.requireContext(),
|
||||||
|
Intent(fragment.requireContext(), CommentsActivity::class.java)
|
||||||
|
.putExtra("mediaId", media.id),
|
||||||
|
null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
//Fuck u launch
|
//Fuck u launch
|
||||||
binding.faqbutton.setOnClickListener {
|
binding.faqbutton.setOnClickListener {
|
||||||
val intent = Intent(fragment.requireContext(), FAQActivity::class.java)
|
val intent = Intent(fragment.requireContext(), FAQActivity::class.java)
|
||||||
|
@ -447,8 +458,7 @@ class MangaReadAdapter(
|
||||||
if (media.manga.chapters!!.isNotEmpty()) {
|
if (media.manga.chapters!!.isNotEmpty()) {
|
||||||
binding.animeSourceNotFound.visibility = View.GONE
|
binding.animeSourceNotFound.visibility = View.GONE
|
||||||
binding.faqbutton.visibility = View.GONE
|
binding.faqbutton.visibility = View.GONE
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
binding.animeSourceNotFound.visibility = View.VISIBLE
|
binding.animeSourceNotFound.visibility = View.VISIBLE
|
||||||
binding.faqbutton.visibility = View.VISIBLE
|
binding.faqbutton.visibility = View.VISIBLE
|
||||||
}
|
}
|
||||||
|
|
14
app/src/main/res/drawable/ic_crown.xml
Normal file
14
app/src/main/res/drawable/ic_crown.xml
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:tint="#fcba03"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M16,9L20,5V16H4V5L6,7M8,9L12,5L14,7M4,19H20"
|
||||||
|
android:strokeWidth="1.5"
|
||||||
|
android:strokeColor="#fcba03"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:strokeLineJoin="round" />
|
||||||
|
</vector>
|
14
app/src/main/res/drawable/ic_shield.xml
Normal file
14
app/src/main/res/drawable/ic_shield.xml
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:tint="#ff0f0f"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M11.302,21.615C11.523,21.744 11.634,21.809 11.79,21.842C11.912,21.868 12.088,21.868 12.21,21.842C12.366,21.809 12.477,21.744 12.698,21.615C14.646,20.478 20,16.908 20,12V6.6C20,6.042 20,5.763 19.893,5.55C19.797,5.362 19.649,5.212 19.461,5.114C19.25,5.004 18.966,5.001 18.399,4.994C15.427,4.959 13.714,4.714 12,3C10.286,4.714 8.573,4.959 5.601,4.994C5.034,5.001 4.75,5.004 4.539,5.114C4.351,5.212 4.203,5.362 4.107,5.55C4,5.763 4,6.042 4,6.6V12C4,16.908 9.354,20.478 11.302,21.615Z"
|
||||||
|
android:strokeWidth="2"
|
||||||
|
android:strokeColor="#ff0f0f"
|
||||||
|
android:strokeLineCap="round"
|
||||||
|
android:strokeLineJoin="round" />
|
||||||
|
</vector>
|
|
@ -4,6 +4,7 @@
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:fitsSystemWindows="true"
|
||||||
android:id="@+id/commentsLayout">
|
android:id="@+id/commentsLayout">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
@ -38,27 +39,43 @@
|
||||||
android:contentDescription="@string/sort_by"
|
android:contentDescription="@string/sort_by"
|
||||||
app:srcCompat="@drawable/ic_round_sort_24"
|
app:srcCompat="@drawable/ic_round_sort_24"
|
||||||
app:tint="?attr/colorOnBackground" />
|
app:tint="?attr/colorOnBackground" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/commentsProgressBar"
|
||||||
|
style="?android:attr/progressBarStyle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="bottom|center_horizontal"
|
||||||
|
android:layout_marginTop="64dp" />
|
||||||
|
|
||||||
|
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||||
|
android:id="@+id/commentsRefresh"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
android:layout_marginBottom="58dp"
|
||||||
|
android:clipChildren="false"
|
||||||
|
android:clipToPadding="false">
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/commentsList"
|
android:id="@+id/commentsList"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="12dp"
|
android:visibility="gone"
|
||||||
android:layout_marginBottom="58dp"
|
|
||||||
tools:listitem="@layout/item_comments" />
|
tools:listitem="@layout/item_comments" />
|
||||||
|
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
android:id="@+id/commentInputLayout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:paddingBottom="8dp"
|
android:paddingBottom="8dp"
|
||||||
android:paddingTop="8dp"
|
android:paddingTop="8dp"
|
||||||
android:layout_gravity="bottom|end"
|
android:layout_gravity="bottom|end"
|
||||||
|
android:windowSoftInputMode="adjustResize"
|
||||||
android:background="@color/nav_bg">
|
android:background="@color/nav_bg">
|
||||||
|
|
||||||
<com.google.android.material.imageview.ShapeableImageView
|
<com.google.android.material.imageview.ShapeableImageView
|
||||||
|
@ -95,6 +112,5 @@
|
||||||
android:layout_marginEnd="12dp"
|
android:layout_marginEnd="12dp"
|
||||||
android:background="@drawable/ic_round_send_24"
|
android:background="@drawable/ic_round_send_24"
|
||||||
tools:ignore="ContentDescription" />
|
tools:ignore="ContentDescription" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</FrameLayout>
|
</FrameLayout>
|
|
@ -19,6 +19,7 @@
|
||||||
android:layout_width="36dp"
|
android:layout_width="36dp"
|
||||||
android:layout_height="36dp"
|
android:layout_height="36dp"
|
||||||
android:scaleType="center"
|
android:scaleType="center"
|
||||||
|
style="@style/CircularImageView"
|
||||||
app:srcCompat="@drawable/ic_round_add_circle_24"
|
app:srcCompat="@drawable/ic_round_add_circle_24"
|
||||||
tools:ignore="ContentDescription,ImageContrastCheck" />
|
tools:ignore="ContentDescription,ImageContrastCheck" />
|
||||||
|
|
||||||
|
@ -33,6 +34,7 @@
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center_vertical"
|
||||||
android:orientation="horizontal">
|
android:orientation="horizontal">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
@ -41,9 +43,34 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:fontFamily="@font/poppins_semi_bold"
|
android:fontFamily="@font/poppins_semi_bold"
|
||||||
android:text="Username"
|
android:text="Username"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:textAlignment="gravity"
|
||||||
android:textSize="15sp"
|
android:textSize="15sp"
|
||||||
|
android:paddingTop="1dp"
|
||||||
|
android:paddingBottom="0dp"
|
||||||
tools:ignore="HardcodedText,RtlSymmetry"/>
|
tools:ignore="HardcodedText,RtlSymmetry"/>
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/modBadge"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:src="@drawable/ic_shield"
|
||||||
|
android:layout_gravity="top"
|
||||||
|
android:visibility="visible"
|
||||||
|
android:scaleX="0.8"
|
||||||
|
android:scaleY="0.8" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/adminBadge"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:src="@drawable/ic_crown"
|
||||||
|
android:layout_gravity="top"
|
||||||
|
android:visibility="visible"
|
||||||
|
android:scaleX="0.9"
|
||||||
|
android:scaleY="0.9" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/commentUserTime"
|
android:id="@+id/commentUserTime"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
@ -103,6 +130,17 @@
|
||||||
android:textSize="12sp"
|
android:textSize="12sp"
|
||||||
android:layout_marginStart="12dp"
|
android:layout_marginStart="12dp"
|
||||||
tools:ignore="HardcodedText" />
|
tools:ignore="HardcodedText" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/commentBanUser"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:alpha="0.6"
|
||||||
|
android:fontFamily="@font/poppins_semi_bold"
|
||||||
|
android:text="Ban User"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:layout_marginStart="12dp"
|
||||||
|
tools:ignore="HardcodedText" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/commentTotalReplies"
|
android:id="@+id/commentTotalReplies"
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<item
|
<item
|
||||||
android:id="@+id/comment_sort_ascending"
|
android:id="@+id/comment_sort_newest"
|
||||||
android:title="@string/comments_sort_ascending" />
|
android:title="@string/newest" />
|
||||||
<item
|
<item
|
||||||
android:id="@+id/comment_sort_descending"
|
android:id="@+id/comment_sort_oldest"
|
||||||
android:title="@string/comments_sort_descending" />
|
android:title="@string/oldest" />
|
||||||
<item
|
<item
|
||||||
android:id="@+id/comment_sort_upvote"
|
android:id="@+id/comment_sort_highest_rated"
|
||||||
android:title="@string/comments_sort_upvote" />
|
android:title="@string/highest_rated" />
|
||||||
<item
|
<item
|
||||||
android:id="@+id/comment_sort_downvote"
|
android:id="@+id/comment_sort_lowest_rated"
|
||||||
android:title="@string/comments_sort_downvote" />
|
android:title="@string/lowest_rated" />
|
||||||
</menu>
|
</menu>
|
|
@ -659,8 +659,8 @@
|
||||||
<string name="try_internal_cast_experimental">Try Internal Cast (Experimental)</string>
|
<string name="try_internal_cast_experimental">Try Internal Cast (Experimental)</string>
|
||||||
|
|
||||||
<string name="comments">Comments</string>
|
<string name="comments">Comments</string>
|
||||||
<string name="comments_sort_ascending">Ascending</string>
|
<string name="newest">newest</string>
|
||||||
<string name="comments_sort_descending">Descending</string>
|
<string name="oldest">oldest</string>
|
||||||
<string name="comments_sort_upvote">Most UpVoted</string>
|
<string name="highest_rated">highest rated</string>
|
||||||
<string name="comments_sort_downvote">Most DownVoted</string>
|
<string name="lowest_rated">lowest rated</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -91,5 +91,15 @@
|
||||||
<item name="android:background">@android:color/transparent</item>
|
<item name="android:background">@android:color/transparent</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style name="CircularImageView" parent="">
|
||||||
|
<item name="shapeAppearanceOverlay">@style/ShapeAppearanceOverlay.App.CircularImageView</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="ShapeAppearanceOverlay.App.CircularImageView" parent="">
|
||||||
|
<item name="cornerFamily">rounded</item>
|
||||||
|
<item name="cornerSize">50%</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
|
||||||
<style name="ThemeOverlay_Dantotsu_MediaRouter" parent="ThemeOverlay.AppCompat"></style>
|
<style name="ThemeOverlay_Dantotsu_MediaRouter" parent="ThemeOverlay.AppCompat"></style>
|
||||||
</resources>
|
</resources>
|
Loading…
Add table
Add a link
Reference in a new issue