diff --git a/app/build.gradle b/app/build.gradle index c8a85389..4c7abcbe 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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 diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 2c1be903..4d994c18 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -109,7 +109,8 @@ android:name=".others.imagesearch.ImageSearchActivity" android:parentActivityName=".MainActivity" /> + android:name=".media.comments.CommentsActivity" + android:windowSoftInputMode="adjustResize|stateHidden" /> diff --git a/app/src/main/java/ani/dantotsu/connections/anilist/AnilistQueries.kt b/app/src/main/java/ani/dantotsu/connections/anilist/AnilistQueries.kt index 890e7132..35289fc7 100644 --- a/app/src/main/java/ani/dantotsu/connections/anilist/AnilistQueries.kt +++ b/app/src/main/java/ani/dantotsu/connections/anilist/AnilistQueries.kt @@ -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 diff --git a/app/src/main/java/ani/dantotsu/connections/comments/CommentsAPI.kt b/app/src/main/java/ani/dantotsu/connections/comments/CommentsAPI.kt index 3d591d45..9e7fa491 100644 --- a/app/src/main/java/ani/dantotsu/connections/comments/CommentsAPI.kt +++ b/app/src/main/java/ani/dantotsu/connections/comments/CommentsAPI.kt @@ -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(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 diff --git a/app/src/main/java/ani/dantotsu/media/MediaInfoFragment.kt b/app/src/main/java/ani/dantotsu/media/MediaInfoFragment.kt index 8b447d50..0f26e174 100644 --- a/app/src/main/java/ani/dantotsu/media/MediaInfoFragment.kt +++ b/app/src/main/java/ani/dantotsu/media/MediaInfoFragment.kt @@ -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)) diff --git a/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchAdapter.kt b/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchAdapter.kt index fb19ec39..d0e50f3a 100644 --- a/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchAdapter.kt +++ b/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchAdapter.kt @@ -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 ) diff --git a/app/src/main/java/ani/dantotsu/media/comments/CommentItem.kt b/app/src/main/java/ani/dantotsu/media/comments/CommentItem.kt index bd38d719..9057a60f 100644 --- a/app/src/main/java/ani/dantotsu/media/comments/CommentItem.kt +++ b/app/src/main/java/ani/dantotsu/media/comments/CommentItem.kt @@ -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() { 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 + } } \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/media/comments/CommentsActivity.kt b/app/src/main/java/ani/dantotsu/media/comments/CommentsActivity.kt new file mode 100644 index 00000000..634f806a --- /dev/null +++ b/app/src/main/java/ani/dantotsu/media/comments/CommentsActivity.kt @@ -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 { + override fun onResourceReady( + resource: Any, + model: Any, + target: Target, + dataSource: DataSource, + isFirstResource: Boolean + ): Boolean { + if (resource is GifDrawable) { + resource.start() + } + return false + } + + override fun onLoadFailed( + e: GlideException?, + model: Any?, + target: Target, + isFirstResource: Boolean + ): Boolean { + return false + } + }) + } + + override fun load(drawable: AsyncDrawable): RequestBuilder { + return requestManager.load(drawable.destination) + } + + override fun cancel(target: Target<*>) { + requestManager.clear(target) + } + })) + .build() + return markwon + } +} \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/media/comments/CommentsFragment.kt b/app/src/main/java/ani/dantotsu/media/comments/CommentsFragment.kt deleted file mode 100644 index 3acf0a71..00000000 --- a/app/src/main/java/ani/dantotsu/media/comments/CommentsFragment.kt +++ /dev/null @@ -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 { - 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 { - override fun onResourceReady( - resource: Any, - model: Any, - target: Target, - dataSource: DataSource, - isFirstResource: Boolean - ): Boolean { - if (resource is GifDrawable) { - resource.start() - } - return false - } - - override fun onLoadFailed( - e: GlideException?, - model: Any?, - target: Target, - isFirstResource: Boolean - ): Boolean { - return false - } - }) - } - - override fun load(drawable: AsyncDrawable): RequestBuilder { - return requestManager.load(drawable.destination) - } - override fun cancel(target: Target<*>) { - requestManager.clear(target) - } - })) - .build() - return markwon - } -} \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/media/manga/MangaReadAdapter.kt b/app/src/main/java/ani/dantotsu/media/manga/MangaReadAdapter.kt index 4cc73ea4..17836928 100644 --- a/app/src/main/java/ani/dantotsu/media/manga/MangaReadAdapter.kt +++ b/app/src/main/java/ani/dantotsu/media/manga/MangaReadAdapter.kt @@ -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,9 +67,19 @@ 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) + val intent = Intent(fragment.requireContext(), FAQActivity::class.java) startActivity(fragment.requireContext(), intent, null) } @@ -447,11 +458,10 @@ 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 - } + } } else { binding.animeSourceContinue.visibility = View.GONE binding.animeSourceNotFound.visibility = View.GONE diff --git a/app/src/main/res/drawable/ic_crown.xml b/app/src/main/res/drawable/ic_crown.xml new file mode 100644 index 00000000..00b5cc98 --- /dev/null +++ b/app/src/main/res/drawable/ic_crown.xml @@ -0,0 +1,14 @@ + + + diff --git a/app/src/main/res/drawable/ic_shield.xml b/app/src/main/res/drawable/ic_shield.xml new file mode 100644 index 00000000..c29effe3 --- /dev/null +++ b/app/src/main/res/drawable/ic_shield.xml @@ -0,0 +1,14 @@ + + + diff --git a/app/src/main/res/layout/fragment_comments.xml b/app/src/main/res/layout/activity_comments.xml similarity index 82% rename from app/src/main/res/layout/fragment_comments.xml rename to app/src/main/res/layout/activity_comments.xml index 22a867d3..bceeb792 100644 --- a/app/src/main/res/layout/fragment_comments.xml +++ b/app/src/main/res/layout/activity_comments.xml @@ -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"> - + + + + - - + - diff --git a/app/src/main/res/layout/item_comments.xml b/app/src/main/res/layout/item_comments.xml index 84836b1e..30669818 100644 --- a/app/src/main/res/layout/item_comments.xml +++ b/app/src/main/res/layout/item_comments.xml @@ -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 @@ + + + + + + + android:id="@+id/comment_sort_newest" + android:title="@string/newest" /> + android:id="@+id/comment_sort_oldest" + android:title="@string/oldest" /> + android:id="@+id/comment_sort_highest_rated" + android:title="@string/highest_rated" /> + android:id="@+id/comment_sort_lowest_rated" + android:title="@string/lowest_rated" /> \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c624da0d..71fc3417 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -659,8 +659,8 @@ Try Internal Cast (Experimental) Comments - Ascending - Descending - Most UpVoted - Most DownVoted + newest + oldest + highest rated + lowest rated diff --git a/app/src/main/res/values/style.xml b/app/src/main/res/values/style.xml index 4f0677d4..fd5798c0 100644 --- a/app/src/main/res/values/style.xml +++ b/app/src/main/res/values/style.xml @@ -91,5 +91,15 @@ @android:color/transparent + + + + + \ No newline at end of file