feat: comments targeted at database

This commit is contained in:
rebelonion 2024-02-15 12:44:52 -06:00
parent 1694a1cb24
commit a73c4cd678
17 changed files with 544 additions and 228 deletions

View file

@ -48,7 +48,7 @@ android {
buildTypes {
alpha {
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"]
debuggable System.getenv("CI") == null
isDefault true

View file

@ -109,7 +109,8 @@
android:name=".others.imagesearch.ImageSearchActivity"
android:parentActivityName=".MainActivity" />
<activity
android:name=".media.comments.CommentsFragment" />
android:name=".media.comments.CommentsActivity"
android:windowSoftInputMode="adjustResize|stateHidden" />
<activity
android:name=".media.SearchActivity"
android:parentActivityName=".MainActivity" />

View file

@ -20,6 +20,7 @@ import ani.dantotsu.others.MalScraper
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.snackString
import ani.dantotsu.toast
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.runBlocking

View file

@ -9,8 +9,6 @@ import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.snackString
import com.lagradost.nicehttp.Requests
import eu.kanade.tachiyomi.network.NetworkHelper
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@ -27,7 +25,7 @@ import javax.crypto.Cipher
import javax.crypto.spec.SecretKeySpec
object CommentsAPI {
val address: String = "http://10.0.2.2:8081"
val address: String = "https://1224665.xyz:443"
val appSecret = BuildConfig.APP_SECRET
var authToken: String? = null
var userId: String? = null
@ -55,7 +53,7 @@ object CommentsAPI {
val json = request.post(url)
val res = json.code == 200
if (!res) {
errorReason(json.code)
errorReason(json.code, json.text)
}
return res
}
@ -73,7 +71,7 @@ object CommentsAPI {
val json = request.post(url, requestBody = body.build())
val res = json.code == 200
if (!res) {
errorReason(json.code)
errorReason(json.code, json.text)
return null
}
val parsed = try {
@ -105,7 +103,32 @@ object CommentsAPI {
val json = request.delete(url)
val res = json.code == 200
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
}
@ -125,7 +148,7 @@ object CommentsAPI {
val parsed = try {
Json.decodeFromString<AuthResponse>(json.text)
} catch (e: Exception) {
println("comment: $e")
snackString("Failed to login to comments API: ${e.printStackTrace()}")
return
}
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) {
429 -> "Rate limited. :("
else -> "Failed to connect"
}
snackString("Error $code: $error")
snackString("Error $code: ${reason ?: error}")
}
@SuppressLint("GetInstance")
@ -221,7 +244,7 @@ data class Comment(
@SerialName("parent_comment_id")
val parentCommentId: Int?,
@SerialName("content")
val content: String,
var content: String,
@SerialName("timestamp")
val timestamp: String,
@SerialName("deleted")
@ -236,7 +259,13 @@ data class Comment(
@SerialName("username")
val username: String,
@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

View file

@ -76,7 +76,7 @@ class MediaInfoFragment : Fragment() {
loaded = true
//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.setOnClickListener {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(media.anime.youtube))

View file

@ -15,10 +15,11 @@ import androidx.core.content.ContextCompat.startActivity
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.RecyclerView
import ani.dantotsu.*
import ani.dantotsu.connections.comments.CommentsAPI
import ani.dantotsu.databinding.DialogLayoutBinding
import ani.dantotsu.databinding.ItemAnimeWatchBinding
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.MediaDetailsActivity
import ani.dantotsu.media.SourceSearchDialogFragment
@ -60,11 +61,11 @@ class AnimeWatchAdapter(
val binding = holder.binding
_binding = binding
//CommentsAPI
binding.animeComments.visibility = View.VISIBLE
binding.animeComments.visibility = if (CommentsAPI.userId == null) View.GONE else View.VISIBLE
binding.animeComments.setOnClickListener {
startActivity(
fragment.requireContext(),
Intent(fragment.requireContext(), CommentsFragment::class.java)
Intent(fragment.requireContext(), CommentsActivity::class.java)
.putExtra("mediaId", media.id),
null
)

View file

@ -20,9 +20,10 @@ import java.text.SimpleDateFormat
import java.util.Date
import java.util.TimeZone
class CommentItem(private val comment: Comment,
class CommentItem(val comment: Comment,
private val markwon: Markwon,
private val section: Section
private val section: Section,
private val editCallback: (CommentItem) -> Unit
) : BindableItem<ItemCommentsBinding>() {
override fun bind(viewBinding: ItemCommentsBinding, position: Int) {
@ -30,13 +31,29 @@ class CommentItem(private val comment: Comment,
val node = markwon.parse(comment.content)
val spanned = markwon.render(node)
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.commentReply.visibility = View.GONE //TODO: implement reply
viewBinding.commentTotalReplies.visibility = View.GONE //TODO: implement reply
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 {
val scope = CoroutineScope(Dispatchers.Main + SupervisorJob())
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
setVoteButtons(viewBinding)
viewBinding.commentUpVote.setOnClickListener {
@ -137,4 +163,11 @@ class CommentItem(private val comment: Comment,
"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
}
}

View file

@ -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
}
}

View file

@ -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
}
}

View file

@ -5,9 +5,6 @@ import android.app.AlertDialog
import android.content.Intent
import android.view.LayoutInflater
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.widget.ArrayAdapter
import android.widget.CheckBox
@ -15,9 +12,11 @@ import android.widget.ImageButton
import android.widget.LinearLayout
import android.widget.NumberPicker
import androidx.core.content.ContextCompat
import androidx.core.content.ContextCompat.startActivity
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.RecyclerView
import ani.dantotsu.*
import ani.dantotsu.connections.comments.CommentsAPI
import ani.dantotsu.databinding.DialogLayoutBinding
import ani.dantotsu.databinding.ItemAnimeWatchBinding
import ani.dantotsu.databinding.ItemChipBinding
@ -25,11 +24,13 @@ import ani.dantotsu.media.Media
import ani.dantotsu.media.MediaDetailsActivity
import ani.dantotsu.media.SourceSearchDialogFragment
import ani.dantotsu.media.anime.handleProgress
import ani.dantotsu.media.comments.CommentsActivity
import ani.dantotsu.others.LanguageMapper
import ani.dantotsu.others.webview.CookieCatcher
import ani.dantotsu.parsers.DynamicMangaParser
import ani.dantotsu.parsers.MangaReadSources
import ani.dantotsu.parsers.MangaSources
import ani.dantotsu.settings.FAQActivity
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.subcriptions.Notifications.Companion.openSettings
@ -66,6 +67,16 @@ class MangaReadAdapter(
_binding = binding
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
binding.faqbutton.setOnClickListener {
val intent = Intent(fragment.requireContext(), FAQActivity::class.java)
@ -447,8 +458,7 @@ class MangaReadAdapter(
if (media.manga.chapters!!.isNotEmpty()) {
binding.animeSourceNotFound.visibility = View.GONE
binding.faqbutton.visibility = View.GONE
}
else {
} else {
binding.animeSourceNotFound.visibility = View.VISIBLE
binding.faqbutton.visibility = View.VISIBLE
}

View 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>

View 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>

View file

@ -4,6 +4,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:fitsSystemWindows="true"
android:id="@+id/commentsLayout">
<LinearLayout
@ -38,27 +39,43 @@
android:contentDescription="@string/sort_by"
app:srcCompat="@drawable/ic_round_sort_24"
app:tint="?attr/colorOnBackground" />
</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
android:id="@+id/commentsList"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:layout_marginBottom="58dp"
android:visibility="gone"
tools:listitem="@layout/item_comments" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/commentInputLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingBottom="8dp"
android:paddingTop="8dp"
android:layout_gravity="bottom|end"
android:windowSoftInputMode="adjustResize"
android:background="@color/nav_bg">
<com.google.android.material.imageview.ShapeableImageView
@ -95,6 +112,5 @@
android:layout_marginEnd="12dp"
android:background="@drawable/ic_round_send_24"
tools:ignore="ContentDescription" />
</LinearLayout>
</FrameLayout>

View file

@ -19,6 +19,7 @@
android:layout_width="36dp"
android:layout_height="36dp"
android:scaleType="center"
style="@style/CircularImageView"
app:srcCompat="@drawable/ic_round_add_circle_24"
tools:ignore="ContentDescription,ImageContrastCheck" />
@ -33,6 +34,7 @@
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
@ -41,9 +43,34 @@
android:layout_height="wrap_content"
android:fontFamily="@font/poppins_semi_bold"
android:text="Username"
android:layout_gravity="center_vertical"
android:gravity="center_vertical"
android:textAlignment="gravity"
android:textSize="15sp"
android:paddingTop="1dp"
android:paddingBottom="0dp"
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
android:id="@+id/commentUserTime"
android:layout_width="wrap_content"
@ -103,6 +130,17 @@
android:textSize="12sp"
android:layout_marginStart="12dp"
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>
<TextView
android:id="@+id/commentTotalReplies"

View file

@ -1,15 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/comment_sort_ascending"
android:title="@string/comments_sort_ascending" />
android:id="@+id/comment_sort_newest"
android:title="@string/newest" />
<item
android:id="@+id/comment_sort_descending"
android:title="@string/comments_sort_descending" />
android:id="@+id/comment_sort_oldest"
android:title="@string/oldest" />
<item
android:id="@+id/comment_sort_upvote"
android:title="@string/comments_sort_upvote" />
android:id="@+id/comment_sort_highest_rated"
android:title="@string/highest_rated" />
<item
android:id="@+id/comment_sort_downvote"
android:title="@string/comments_sort_downvote" />
android:id="@+id/comment_sort_lowest_rated"
android:title="@string/lowest_rated" />
</menu>

View file

@ -659,8 +659,8 @@
<string name="try_internal_cast_experimental">Try Internal Cast (Experimental)</string>
<string name="comments">Comments</string>
<string name="comments_sort_ascending">Ascending</string>
<string name="comments_sort_descending">Descending</string>
<string name="comments_sort_upvote">Most UpVoted</string>
<string name="comments_sort_downvote">Most DownVoted</string>
<string name="newest">newest</string>
<string name="oldest">oldest</string>
<string name="highest_rated">highest rated</string>
<string name="lowest_rated">lowest rated</string>
</resources>

View file

@ -91,5 +91,15 @@
<item name="android:background">@android:color/transparent</item>
</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>
</resources>