feat: reviews
This commit is contained in:
parent
831b99ae40
commit
a0fabd3ca6
16 changed files with 642 additions and 34 deletions
|
@ -198,6 +198,12 @@
|
||||||
<activity
|
<activity
|
||||||
android:name=".others.imagesearch.ImageSearchActivity"
|
android:name=".others.imagesearch.ImageSearchActivity"
|
||||||
android:parentActivityName=".MainActivity" />
|
android:parentActivityName=".MainActivity" />
|
||||||
|
<activity
|
||||||
|
android:name=".media.ReviewActivity"
|
||||||
|
android:parentActivityName=".media.MediaDetailsActivity" />
|
||||||
|
<activity
|
||||||
|
android:name=".media.ReviewViewActivity"
|
||||||
|
android:parentActivityName=".media.ReviewActivity" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".media.SearchActivity"
|
android:name=".media.SearchActivity"
|
||||||
android:parentActivityName=".MainActivity" />
|
android:parentActivityName=".MainActivity" />
|
||||||
|
|
|
@ -2,6 +2,7 @@ package ani.dantotsu.connections.anilist
|
||||||
|
|
||||||
import ani.dantotsu.connections.anilist.Anilist.executeQuery
|
import ani.dantotsu.connections.anilist.Anilist.executeQuery
|
||||||
import ani.dantotsu.connections.anilist.api.FuzzyDate
|
import ani.dantotsu.connections.anilist.api.FuzzyDate
|
||||||
|
import ani.dantotsu.connections.anilist.api.Query
|
||||||
import kotlinx.serialization.json.JsonObject
|
import kotlinx.serialization.json.JsonObject
|
||||||
|
|
||||||
class AnilistMutations {
|
class AnilistMutations {
|
||||||
|
@ -69,4 +70,10 @@ class AnilistMutations {
|
||||||
val variables = """{"id":"$listId"}"""
|
val variables = """{"id":"$listId"}"""
|
||||||
executeQuery<JsonObject>(query, variables)
|
executeQuery<JsonObject>(query, variables)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
suspend fun rateReview(reviewId: Int, rating: String): Query.RateReviewResponse? {
|
||||||
|
val query = "mutation{RateReview(reviewId:$reviewId,rating:$rating){id mediaId mediaType summary body(asHtml:true)rating ratingAmount userRating score private siteUrl createdAt updatedAt user{id name bannerImage avatar{medium large}}}}"
|
||||||
|
return executeQuery<Query.RateReviewResponse>(query)
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1504,6 +1504,13 @@ Page(page:$page,perPage:50) {
|
||||||
return author
|
return author
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun getReviews(mediaId: Int, page: Int = 1, sort: String = "UPDATED_AT_DESC"): Query.ReviewsResponse? {
|
||||||
|
return executeQuery<Query.ReviewsResponse>(
|
||||||
|
"""{Page(page:$page,perPage:10){pageInfo{currentPage,hasNextPage,total}reviews(mediaId:$mediaId,sort:$sort){id,mediaId,mediaType,summary,body(asHtml:true)rating,ratingAmount,userRating,score,private,siteUrl,createdAt,updatedAt,user{id,name,bannerImage avatar{medium,large}}}}}""",
|
||||||
|
force = true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun toggleFollow(id: Int): Query.ToggleFollow? {
|
suspend fun toggleFollow(id: Int): Query.ToggleFollow? {
|
||||||
return executeQuery<Query.ToggleFollow>(
|
return executeQuery<Query.ToggleFollow>(
|
||||||
"""mutation{ToggleFollow(userId:$id){id, isFollowing, isFollower}}"""
|
"""mutation{ToggleFollow(userId:$id){id, isFollowing, isFollower}}"""
|
||||||
|
|
|
@ -299,6 +299,70 @@ class Query {
|
||||||
val following: List<ani.dantotsu.connections.anilist.api.User>?
|
val following: List<ani.dantotsu.connections.anilist.api.User>?
|
||||||
) : java.io.Serializable
|
) : java.io.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ReviewsResponse(
|
||||||
|
@SerialName("data")
|
||||||
|
val data: Data
|
||||||
|
) : java.io.Serializable {
|
||||||
|
@Serializable
|
||||||
|
data class Data(
|
||||||
|
@SerialName("Page")
|
||||||
|
val page: ReviewPage?
|
||||||
|
) : java.io.Serializable
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ReviewPage(
|
||||||
|
@SerialName("pageInfo")
|
||||||
|
val pageInfo: PageInfo,
|
||||||
|
@SerialName("reviews")
|
||||||
|
val reviews: List<Review>?
|
||||||
|
) : java.io.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class RateReviewResponse(
|
||||||
|
@SerialName("data")
|
||||||
|
val data: Data
|
||||||
|
) : java.io.Serializable {
|
||||||
|
@Serializable
|
||||||
|
data class Data(
|
||||||
|
@SerialName("RateReview")
|
||||||
|
val rateReview: Review
|
||||||
|
) : java.io.Serializable
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Review(
|
||||||
|
@SerialName("id")
|
||||||
|
val id: Int,
|
||||||
|
@SerialName("mediaId")
|
||||||
|
val mediaId: Int,
|
||||||
|
@SerialName("mediaType")
|
||||||
|
val mediaType: String,
|
||||||
|
@SerialName("summary")
|
||||||
|
val summary: String,
|
||||||
|
@SerialName("body")
|
||||||
|
val body: String,
|
||||||
|
@SerialName("rating")
|
||||||
|
var rating: Int,
|
||||||
|
@SerialName("ratingAmount")
|
||||||
|
var ratingAmount: Int,
|
||||||
|
@SerialName("userRating")
|
||||||
|
var userRating: String,
|
||||||
|
@SerialName("score")
|
||||||
|
val score: Int,
|
||||||
|
@SerialName("private")
|
||||||
|
val private: Boolean,
|
||||||
|
@SerialName("siteUrl")
|
||||||
|
val siteUrl: String,
|
||||||
|
@SerialName("createdAt")
|
||||||
|
val createdAt: Int,
|
||||||
|
@SerialName("updatedAt")
|
||||||
|
val updatedAt: Int?,
|
||||||
|
@SerialName("user")
|
||||||
|
val user: ani.dantotsu.connections.anilist.api.User?,
|
||||||
|
) : java.io.Serializable
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class UserProfile(
|
data class UserProfile(
|
||||||
@SerialName("id")
|
@SerialName("id")
|
||||||
|
|
|
@ -226,7 +226,7 @@ class AnimeDownloaderService : Service() {
|
||||||
task.episode
|
task.episode
|
||||||
) ?: throw Exception("Failed to create output directory")
|
) ?: throw Exception("Failed to create output directory")
|
||||||
|
|
||||||
outputDir.findFile("${task.getTaskName()}.mp4")?.delete()
|
outputDir.findFile("${task.getTaskName()}.mkv")?.delete()
|
||||||
val outputFile = outputDir.createFile("video/x-matroska", "${task.getTaskName()}.mkv")
|
val outputFile = outputDir.createFile("video/x-matroska", "${task.getTaskName()}.mkv")
|
||||||
?: throw Exception("Failed to create output file")
|
?: throw Exception("Failed to create output file")
|
||||||
|
|
||||||
|
@ -245,7 +245,7 @@ class AnimeDownloaderService : Service() {
|
||||||
.append(defaultHeaders["User-Agent"]).append("\"\'\r\n\'")
|
.append(defaultHeaders["User-Agent"]).append("\"\'\r\n\'")
|
||||||
}
|
}
|
||||||
val probeRequest =
|
val probeRequest =
|
||||||
"-headers $headersStringBuilder -i ${task.video.file.url} -show_entries format=duration -v quiet -of csv=\"p=0\""
|
"-headers $headersStringBuilder -i \"${task.video.file.url}\" -show_entries format=duration -v quiet -of csv=\"p=0\""
|
||||||
ffExtension.executeFFProbe(
|
ffExtension.executeFFProbe(
|
||||||
probeRequest
|
probeRequest
|
||||||
) {
|
) {
|
||||||
|
@ -256,7 +256,7 @@ class AnimeDownloaderService : Service() {
|
||||||
|
|
||||||
val headers = headersStringBuilder.toString()
|
val headers = headersStringBuilder.toString()
|
||||||
var request = "-headers $headers "
|
var request = "-headers $headers "
|
||||||
request += "-i ${task.video.file.url} -c copy -map 0:v -map 0:a -map 0:s?" +
|
request += "-i \"${task.video.file.url}\" -c copy -map 0:v -map 0:a -map 0:s?" +
|
||||||
" -f matroska -timeout 600 -reconnect 1" +
|
" -f matroska -timeout 600 -reconnect 1" +
|
||||||
" -reconnect_streamed 1 -allowed_extensions ALL " +
|
" -reconnect_streamed 1 -allowed_extensions ALL " +
|
||||||
"-tls_verify 0 $path -v trace"
|
"-tls_verify 0 $path -v trace"
|
||||||
|
|
|
@ -517,26 +517,24 @@ class MediaInfoFragment : Fragment() {
|
||||||
}
|
}
|
||||||
parent.addView(root)
|
parent.addView(root)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ItemTitleSearchBinding.inflate(
|
ItemTitleSearchBinding.inflate(
|
||||||
LayoutInflater.from(context),
|
LayoutInflater.from(context),
|
||||||
parent,
|
parent,
|
||||||
false
|
false
|
||||||
).apply {
|
).apply {
|
||||||
|
|
||||||
titleSearchImage.loadImage(media.banner ?: media.cover)
|
titleSearchImage.loadImage(media.banner ?: media.cover)
|
||||||
titleSearchText.text =
|
titleSearchText.text =
|
||||||
getString(R.string.search_title, media.mainName())
|
getString(R.string.reviews)
|
||||||
titleSearchCard.setSafeOnClickListener {
|
titleSearchCard.setSafeOnClickListener {
|
||||||
val query = Intent(requireContext(), SearchActivity::class.java)
|
val query = Intent(requireContext(), ReviewActivity::class.java)
|
||||||
.putExtra("type", "ANIME")
|
.putExtra("mediaId", media.id)
|
||||||
.putExtra("query", media.mainName())
|
ContextCompat.startActivity(requireContext(), query, null)
|
||||||
.putExtra("search", true)
|
|
||||||
ContextCompat.startActivity(requireContext(), query, null)
|
|
||||||
}
|
|
||||||
|
|
||||||
parent.addView(root)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
parent.addView(root)
|
||||||
}
|
}
|
||||||
|
|
||||||
ItemTitleRecyclerBinding.inflate(
|
ItemTitleRecyclerBinding.inflate(
|
||||||
|
|
149
app/src/main/java/ani/dantotsu/media/ReviewActivity.kt
Normal file
149
app/src/main/java/ani/dantotsu/media/ReviewActivity.kt
Normal file
|
@ -0,0 +1,149 @@
|
||||||
|
package ani.dantotsu.media
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.text.SpannableString
|
||||||
|
import android.view.MotionEvent
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
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.anilist.api.Query
|
||||||
|
import ani.dantotsu.databinding.ActivityFollowBinding
|
||||||
|
import ani.dantotsu.initActivity
|
||||||
|
import ani.dantotsu.navBarHeight
|
||||||
|
import ani.dantotsu.profile.FollowerItem
|
||||||
|
import ani.dantotsu.statusBarHeight
|
||||||
|
import ani.dantotsu.themes.ThemeManager
|
||||||
|
import com.xwray.groupie.GroupieAdapter
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
|
class ReviewActivity : AppCompatActivity() {
|
||||||
|
private lateinit var binding: ActivityFollowBinding
|
||||||
|
val adapter = GroupieAdapter()
|
||||||
|
private val reviews = mutableListOf<Query.Review>()
|
||||||
|
var mediaId = 0
|
||||||
|
private var currentPage: Int = 1
|
||||||
|
private var hasNextPage: Boolean = true
|
||||||
|
|
||||||
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
ThemeManager(this).applyTheme()
|
||||||
|
initActivity(this)
|
||||||
|
binding = ActivityFollowBinding.inflate(layoutInflater)
|
||||||
|
binding.listToolbar.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||||
|
topMargin = statusBarHeight
|
||||||
|
}
|
||||||
|
binding.listFrameLayout.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||||
|
bottomMargin = navBarHeight
|
||||||
|
}
|
||||||
|
setContentView(binding.root)
|
||||||
|
mediaId = intent.getIntExtra("mediaId", -1)
|
||||||
|
if (mediaId == -1) {
|
||||||
|
finish()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
binding.followerGrid.visibility = View.GONE
|
||||||
|
binding.followerList.visibility = View.GONE
|
||||||
|
binding.followFilterButton.visibility = View.GONE
|
||||||
|
binding.listTitle.text = getString(R.string.reviews)
|
||||||
|
binding.listRecyclerView.adapter = adapter
|
||||||
|
binding.listRecyclerView.layoutManager = LinearLayoutManager(
|
||||||
|
this,
|
||||||
|
LinearLayoutManager.VERTICAL,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
binding.listProgressBar.visibility = View.VISIBLE
|
||||||
|
binding.listBack.setOnClickListener { onBackPressedDispatcher.onBackPressed() }
|
||||||
|
|
||||||
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
|
val response = Anilist.query.getReviews(mediaId)
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
binding.listProgressBar.visibility = View.GONE
|
||||||
|
binding.listRecyclerView.setOnTouchListener { _, event ->
|
||||||
|
if (event?.action == MotionEvent.ACTION_UP) {
|
||||||
|
if (hasNextPage && !binding.listRecyclerView.canScrollVertically(1) && !binding.followRefresh.isVisible
|
||||||
|
&& binding.listRecyclerView.adapter!!.itemCount != 0 &&
|
||||||
|
(binding.listRecyclerView.layoutManager as LinearLayoutManager).findLastVisibleItemPosition() == (binding.listRecyclerView.adapter!!.itemCount - 1)
|
||||||
|
) {
|
||||||
|
binding.followRefresh.visibility = ViewGroup.VISIBLE
|
||||||
|
loadPage(++currentPage) {
|
||||||
|
binding.followRefresh.visibility = ViewGroup.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
currentPage = response?.data?.page?.pageInfo?.currentPage ?: 1
|
||||||
|
hasNextPage = response?.data?.page?.pageInfo?.hasNextPage ?: false
|
||||||
|
response?.data?.page?.reviews?.let {
|
||||||
|
reviews.addAll(it)
|
||||||
|
fillList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadPage(page: Int, callback: () -> Unit) {
|
||||||
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
|
val response = Anilist.query.getReviews(mediaId, page)
|
||||||
|
currentPage = response?.data?.page?.pageInfo?.currentPage ?: 1
|
||||||
|
hasNextPage = response?.data?.page?.pageInfo?.hasNextPage ?: false
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
response?.data?.page?.reviews?.let {
|
||||||
|
reviews.addAll(it)
|
||||||
|
fillList()
|
||||||
|
}
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun fillList() {
|
||||||
|
adapter.clear()
|
||||||
|
reviews.forEach {
|
||||||
|
val username = it.user?.name ?: "Unknown"
|
||||||
|
val name = SpannableString(username + " - " + it.score)
|
||||||
|
//change the size of the score
|
||||||
|
name.setSpan(
|
||||||
|
android.text.style.RelativeSizeSpan(0.9f),
|
||||||
|
0,
|
||||||
|
name.length,
|
||||||
|
android.text.Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||||
|
)
|
||||||
|
//give the text an underline
|
||||||
|
name.setSpan(
|
||||||
|
android.text.style.UnderlineSpan(),
|
||||||
|
username.length + 3,
|
||||||
|
name.length,
|
||||||
|
android.text.Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||||
|
)
|
||||||
|
adapter.add(
|
||||||
|
FollowerItem(
|
||||||
|
it.id,
|
||||||
|
name,
|
||||||
|
it.user?.avatar?.medium,
|
||||||
|
it.user?.bannerImage,
|
||||||
|
it.summary,
|
||||||
|
this::onUserClick
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onUserClick(userId: Int) {
|
||||||
|
val review = reviews.find { it.id == userId }
|
||||||
|
if (review != null) {
|
||||||
|
startActivity(Intent(this, ReviewViewActivity::class.java).putExtra("review", review))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
178
app/src/main/java/ani/dantotsu/media/ReviewViewActivity.kt
Normal file
178
app/src/main/java/ani/dantotsu/media/ReviewViewActivity.kt
Normal file
|
@ -0,0 +1,178 @@
|
||||||
|
package ani.dantotsu.media
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.webkit.WebResourceRequest
|
||||||
|
import android.webkit.WebView
|
||||||
|
import android.webkit.WebViewClient
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.view.updateLayoutParams
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import ani.dantotsu.R
|
||||||
|
import ani.dantotsu.connections.anilist.Anilist
|
||||||
|
import ani.dantotsu.connections.anilist.api.Query
|
||||||
|
import ani.dantotsu.databinding.ActivityReviewViewBinding
|
||||||
|
import ani.dantotsu.getThemeColor
|
||||||
|
import ani.dantotsu.initActivity
|
||||||
|
import ani.dantotsu.loadImage
|
||||||
|
import ani.dantotsu.navBarHeight
|
||||||
|
import ani.dantotsu.profile.activity.ActivityItemBuilder
|
||||||
|
import ani.dantotsu.statusBarHeight
|
||||||
|
import ani.dantotsu.themes.ThemeManager
|
||||||
|
import ani.dantotsu.toast
|
||||||
|
import ani.dantotsu.util.AniMarkdown
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
|
class ReviewViewActivity : AppCompatActivity() {
|
||||||
|
private lateinit var binding: ActivityReviewViewBinding
|
||||||
|
private lateinit var review: Query.Review
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
ThemeManager(this).applyTheme()
|
||||||
|
initActivity(this)
|
||||||
|
binding = ActivityReviewViewBinding.inflate(layoutInflater)
|
||||||
|
binding.userContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||||
|
topMargin = statusBarHeight
|
||||||
|
}
|
||||||
|
binding.reviewContent.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
||||||
|
bottomMargin += navBarHeight
|
||||||
|
}
|
||||||
|
setContentView(binding.root)
|
||||||
|
review = intent.getSerializableExtra("review") as Query.Review
|
||||||
|
binding.userName.text = review.user?.name
|
||||||
|
binding.userAvatar.loadImage(review.user?.avatar?.medium)
|
||||||
|
binding.userTime.text = ActivityItemBuilder.getDateTime(review.createdAt)
|
||||||
|
binding.profileUserBio.settings.loadWithOverviewMode = true
|
||||||
|
binding.profileUserBio.settings.useWideViewPort = true
|
||||||
|
binding.profileUserBio.setInitialScale(1)
|
||||||
|
val styledHtml = AniMarkdown.getFullAniHTML(
|
||||||
|
review.body,
|
||||||
|
ContextCompat.getColor(this, R.color.bg_opp)
|
||||||
|
)
|
||||||
|
binding.profileUserBio.loadDataWithBaseURL(
|
||||||
|
null,
|
||||||
|
styledHtml,
|
||||||
|
"text/html",
|
||||||
|
"utf-8",
|
||||||
|
null
|
||||||
|
)
|
||||||
|
binding.profileUserBio.setBackgroundColor(
|
||||||
|
ContextCompat.getColor(
|
||||||
|
this,
|
||||||
|
android.R.color.transparent
|
||||||
|
)
|
||||||
|
)
|
||||||
|
binding.profileUserBio.setLayerType(View.LAYER_TYPE_HARDWARE, null)
|
||||||
|
binding.profileUserBio.webViewClient = object : WebViewClient() {
|
||||||
|
override fun onPageFinished(view: WebView?, url: String?) {
|
||||||
|
super.onPageFinished(view, url)
|
||||||
|
binding.profileUserBio.setBackgroundColor(
|
||||||
|
ContextCompat.getColor(
|
||||||
|
this@ReviewViewActivity,
|
||||||
|
android.R.color.transparent
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun shouldOverrideUrlLoading(
|
||||||
|
view: WebView?,
|
||||||
|
request: WebResourceRequest?
|
||||||
|
): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
userVote(review.userRating)
|
||||||
|
enableVote()
|
||||||
|
binding.voteCount.text = review.rating.toString()
|
||||||
|
binding.voteText.text = getString(
|
||||||
|
R.string.vote_out_of_total,
|
||||||
|
review.rating.toString(),
|
||||||
|
review.ratingAmount.toString()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun userVote(type: String) {
|
||||||
|
val selectedColor = getThemeColor(com.google.android.material.R.attr.colorPrimary)
|
||||||
|
val unselectedColor = getThemeColor(androidx.appcompat.R.attr.colorControlNormal)
|
||||||
|
when (type) {
|
||||||
|
"NO_VOTE" -> {
|
||||||
|
binding.upvote.setColorFilter(unselectedColor)
|
||||||
|
binding.downvote.setColorFilter(unselectedColor)
|
||||||
|
}
|
||||||
|
|
||||||
|
"UP_VOTE" -> {
|
||||||
|
binding.upvote.setColorFilter(selectedColor)
|
||||||
|
binding.downvote.setColorFilter(unselectedColor)
|
||||||
|
}
|
||||||
|
|
||||||
|
"DOWN_VOTE" -> {
|
||||||
|
binding.upvote.setColorFilter(unselectedColor)
|
||||||
|
binding.downvote.setColorFilter(selectedColor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun rateReview(rating: String) {
|
||||||
|
disableVote()
|
||||||
|
lifecycleScope.launch {
|
||||||
|
val result = Anilist.mutation.rateReview(review.id, rating)
|
||||||
|
if (result != null) {
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
val res = result.data.rateReview
|
||||||
|
review.rating = res.rating
|
||||||
|
review.ratingAmount = res.ratingAmount
|
||||||
|
review.userRating = res.userRating
|
||||||
|
userVote(review.userRating)
|
||||||
|
binding.voteCount.text = review.rating.toString()
|
||||||
|
binding.voteText.text = getString(
|
||||||
|
R.string.vote_out_of_total,
|
||||||
|
review.rating.toString(),
|
||||||
|
review.ratingAmount.toString()
|
||||||
|
)
|
||||||
|
userVote(review.userRating)
|
||||||
|
enableVote()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
toast(
|
||||||
|
getString(R.string.error_message, "response is null")
|
||||||
|
)
|
||||||
|
enableVote()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun disableVote() {
|
||||||
|
binding.upvote.setOnClickListener(null)
|
||||||
|
binding.downvote.setOnClickListener(null)
|
||||||
|
binding.upvote.isEnabled = false
|
||||||
|
binding.downvote.isEnabled = false
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun enableVote() {
|
||||||
|
binding.upvote.setOnClickListener {
|
||||||
|
if (review.userRating == "UP_VOTE") {
|
||||||
|
rateReview("NO_VOTE")
|
||||||
|
} else {
|
||||||
|
rateReview("UP_VOTE")
|
||||||
|
}
|
||||||
|
disableVote()
|
||||||
|
}
|
||||||
|
binding.downvote.setOnClickListener {
|
||||||
|
if (review.userRating == "DOWN_VOTE") {
|
||||||
|
rateReview("NO_VOTE")
|
||||||
|
} else {
|
||||||
|
rateReview("DOWN_VOTE")
|
||||||
|
}
|
||||||
|
disableVote()
|
||||||
|
}
|
||||||
|
binding.upvote.isEnabled = true
|
||||||
|
binding.downvote.isEnabled = true
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,6 +2,7 @@ package ani.dantotsu.profile
|
||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.text.SpannableString
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup.MarginLayoutParams
|
import android.view.ViewGroup.MarginLayoutParams
|
||||||
import android.widget.ImageButton
|
import android.widget.ImageButton
|
||||||
|
@ -54,7 +55,7 @@ class FollowActivity : AppCompatActivity() {
|
||||||
)
|
)
|
||||||
binding.listRecyclerView.adapter = adapter
|
binding.listRecyclerView.adapter = adapter
|
||||||
binding.listProgressBar.visibility = View.VISIBLE
|
binding.listProgressBar.visibility = View.VISIBLE
|
||||||
binding.listBack.setOnClickListener { finish() }
|
binding.listBack.setOnClickListener { onBackPressedDispatcher.onBackPressed() }
|
||||||
|
|
||||||
val title = intent.getStringExtra("title")
|
val title = intent.getStringExtra("title")
|
||||||
val userID = intent.getIntExtra("userId", 0)
|
val userID = intent.getIntExtra("userId", 0)
|
||||||
|
@ -97,10 +98,11 @@ class FollowActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
users?.forEach { user ->
|
users?.forEach { user ->
|
||||||
if (getLayoutType(selected) == 0) {
|
if (getLayoutType(selected) == 0) {
|
||||||
|
val username = SpannableString(user.name ?: "Unknown")
|
||||||
adapter.add(
|
adapter.add(
|
||||||
FollowerItem(
|
FollowerItem(
|
||||||
user.id,
|
user.id,
|
||||||
user.name ?: "Unknown",
|
username,
|
||||||
user.avatar?.medium,
|
user.avatar?.medium,
|
||||||
user.bannerImage ?: user.avatar?.medium
|
user.bannerImage ?: user.avatar?.medium
|
||||||
) { onUserClick(it) })
|
) { onUserClick(it) })
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package ani.dantotsu.profile
|
package ani.dantotsu.profile
|
||||||
|
|
||||||
|
|
||||||
|
import android.text.SpannableString
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
import ani.dantotsu.blurImage
|
import ani.dantotsu.blurImage
|
||||||
|
@ -10,9 +11,10 @@ import com.xwray.groupie.viewbinding.BindableItem
|
||||||
|
|
||||||
class FollowerItem(
|
class FollowerItem(
|
||||||
private val id: Int,
|
private val id: Int,
|
||||||
private val name: String,
|
private val name: SpannableString,
|
||||||
private val avatar: String?,
|
private val avatar: String?,
|
||||||
private val banner: String?,
|
private val banner: String?,
|
||||||
|
private val altText: String? = null,
|
||||||
val clickCallback: (Int) -> Unit
|
val clickCallback: (Int) -> Unit
|
||||||
) : BindableItem<ItemFollowerBinding>() {
|
) : BindableItem<ItemFollowerBinding>() {
|
||||||
private lateinit var binding: ItemFollowerBinding
|
private lateinit var binding: ItemFollowerBinding
|
||||||
|
@ -21,6 +23,10 @@ class FollowerItem(
|
||||||
binding = viewBinding
|
binding = viewBinding
|
||||||
binding.profileUserName.text = name
|
binding.profileUserName.text = name
|
||||||
avatar?.let { binding.profileUserAvatar.loadImage(it) }
|
avatar?.let { binding.profileUserAvatar.loadImage(it) }
|
||||||
|
altText?.let {
|
||||||
|
binding.altText.visibility = View.VISIBLE
|
||||||
|
binding.altText.text = it
|
||||||
|
}
|
||||||
blurImage(binding.profileBannerImage, banner ?: avatar)
|
blurImage(binding.profileBannerImage, banner ?: avatar)
|
||||||
binding.root.setOnClickListener { clickCallback(id) }
|
binding.root.setOnClickListener { clickCallback(id) }
|
||||||
}
|
}
|
||||||
|
|
12
app/src/main/res/drawable/ic_thumbs.xml
Normal file
12
app/src/main/res/drawable/ic_thumbs.xml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:tint="?attr/colorControlNormal"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M7.24,11V20H5.63C4.73,20 4.01,19.28 4.01,18.39V12.62C4.01,11.73 4.74,11 5.63,11H7.24ZM18.5,9.5H13.72V6C13.72,4.9 12.82,4 11.73,4H11.64C11.24,4 10.88,4.24 10.72,4.61L7.99,11V20H17.19C17.92,20 18.54,19.48 18.67,18.76L19.99,11.26C20.15,10.34 19.45,9.5 18.51,9.5H18.5Z" />
|
||||||
|
|
||||||
|
</vector>
|
5
app/src/main/res/drawable/surface_rounded_bg.xml
Normal file
5
app/src/main/res/drawable/surface_rounded_bg.xml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<solid android:color="?attr/colorSurfaceVariant" />
|
||||||
|
<corners android:radius="16dp" />
|
||||||
|
</shape>
|
155
app/src/main/res/layout/activity_review_view.xml
Normal file
155
app/src/main/res/layout/activity_review_view.xml
Normal file
|
@ -0,0 +1,155 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/reviewContainer"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/userContainer"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<com.google.android.material.card.MaterialCardView
|
||||||
|
android:id="@+id/userAvatarContainer"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="start|center_vertical"
|
||||||
|
android:layout_margin="12dp"
|
||||||
|
android:backgroundTint="@color/transparent"
|
||||||
|
app:cardCornerRadius="64dp"
|
||||||
|
app:strokeColor="@color/transparent">
|
||||||
|
|
||||||
|
<com.google.android.material.imageview.ShapeableImageView
|
||||||
|
android:id="@+id/userAvatar"
|
||||||
|
android:layout_width="64dp"
|
||||||
|
android:layout_height="64dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
app:srcCompat="@drawable/ic_round_add_circle_24"
|
||||||
|
tools:ignore="ContentDescription,ImageContrastCheck"
|
||||||
|
tools:tint="@color/bg_black_50" />
|
||||||
|
</com.google.android.material.card.MaterialCardView>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/userName"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_marginEnd="4dp"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:fontFamily="@font/poppins_bold"
|
||||||
|
android:paddingTop="1dp"
|
||||||
|
android:paddingBottom="0dp"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:text="Username"
|
||||||
|
android:textColor="@color/bg_opp"
|
||||||
|
android:textSize="18sp"
|
||||||
|
tools:ignore="HardcodedText,RtlSymmetry" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_marginEnd="4dp"
|
||||||
|
android:alpha="0.6"
|
||||||
|
android:fontFamily="@font/poppins_semi_bold"
|
||||||
|
android:text="•"
|
||||||
|
android:textSize="18sp"
|
||||||
|
tools:ignore="HardcodedText,RtlSymmetry" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/userTime"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="end|center_vertical"
|
||||||
|
android:layout_marginEnd="6dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:alpha="0.6"
|
||||||
|
android:fontFamily="@font/poppins_semi_bold"
|
||||||
|
android:text="Time"
|
||||||
|
android:textSize="14sp"
|
||||||
|
tools:ignore="HardcodedText,RtlSymmetry" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<WebView
|
||||||
|
android:id="@+id/profileUserBio"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:nestedScrollingEnabled="true"
|
||||||
|
android:padding="16dp"
|
||||||
|
android:textAlignment="textStart"
|
||||||
|
tools:text="@string/slogan" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/reviewContent"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_horizontal"
|
||||||
|
android:layout_marginTop="32dp"
|
||||||
|
android:layout_marginBottom="32dp"
|
||||||
|
android:background="@drawable/surface_rounded_bg"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_horizontal"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/downvote"
|
||||||
|
android:layout_width="36dp"
|
||||||
|
android:layout_height="36dp"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:scaleX="-1"
|
||||||
|
android:scaleY="-1"
|
||||||
|
android:src="@drawable/ic_thumbs"
|
||||||
|
tools:ignore="ContentDescription" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/voteCount"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_marginEnd="4dp"
|
||||||
|
android:fontFamily="@font/poppins_semi_bold"
|
||||||
|
android:text="0"
|
||||||
|
android:textSize="18sp"
|
||||||
|
tools:ignore="HardcodedText" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/upvote"
|
||||||
|
android:layout_width="36dp"
|
||||||
|
android:layout_height="36dp"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:src="@drawable/ic_thumbs"
|
||||||
|
tools:ignore="ContentDescription" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/voteText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_margin="8dp"
|
||||||
|
android:fontFamily="@font/poppins_semi_bold"
|
||||||
|
android:text="@string/vote_out_of_total"
|
||||||
|
android:textSize="14sp"
|
||||||
|
tools:ignore="HardcodedText" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
</androidx.core.widget.NestedScrollView>
|
|
@ -52,14 +52,34 @@
|
||||||
|
|
||||||
</com.google.android.material.card.MaterialCardView>
|
</com.google.android.material.card.MaterialCardView>
|
||||||
|
|
||||||
<TextView
|
<LinearLayout
|
||||||
android:id="@+id/profileUserName"
|
android:id="@+id/profileUserInfoContainer"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="match_parent"
|
||||||
android:layout_gravity="start|center_vertical"
|
android:layout_gravity="start|center_vertical"
|
||||||
android:layout_marginStart="120dp"
|
android:layout_marginStart="100dp"
|
||||||
android:fontFamily="@font/poppins_semi_bold"
|
android:gravity="center_vertical"
|
||||||
android:text="@string/username"
|
android:orientation="vertical">
|
||||||
android:textSize="18sp" />
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/profileUserName"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:fontFamily="@font/poppins_semi_bold"
|
||||||
|
android:text="@string/username"
|
||||||
|
android:textSize="18sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/altText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="start|center_vertical"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:fontFamily="@font/poppins_semi_bold"
|
||||||
|
android:maxLines="2"
|
||||||
|
android:text="@string/lorem_ipsum"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:visibility="gone" />
|
||||||
|
</LinearLayout>
|
||||||
</androidx.cardview.widget.CardView>
|
</androidx.cardview.widget.CardView>
|
||||||
</FrameLayout>
|
</FrameLayout>
|
|
@ -50,7 +50,7 @@
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:maxLines="2"
|
android:maxLines="2"
|
||||||
android:padding="4dp"
|
android:padding="4dp"
|
||||||
android:text="@string/search"
|
android:text="@string/reviews"
|
||||||
android:textAllCaps="true"
|
android:textAllCaps="true"
|
||||||
android:textColor="@color/bg_white"
|
android:textColor="@color/bg_white"
|
||||||
android:textSize="14sp" />
|
android:textSize="14sp" />
|
||||||
|
|
|
@ -430,7 +430,7 @@
|
||||||
<string name="error_message">Error: %1$s</string>
|
<string name="error_message">Error: %1$s</string>
|
||||||
<string name="install_step">Step: %1$s</string>
|
<string name="install_step">Step: %1$s</string>
|
||||||
<string name="review">Review</string>
|
<string name="review">Review</string>
|
||||||
|
<string name="reviews">Reviews</string>
|
||||||
<string name="discord_nothing_button">Display only the first button</string>
|
<string name="discord_nothing_button">Display only the first button</string>
|
||||||
<string name="discord_dantotsu_button">Display dantotsu in the second button</string>
|
<string name="discord_dantotsu_button">Display dantotsu in the second button</string>
|
||||||
<string name="discord_anilist_button">Display your AniList profile instead</string>
|
<string name="discord_anilist_button">Display your AniList profile instead</string>
|
||||||
|
@ -850,8 +850,6 @@ Non quae tempore quo provident laudantium qui illo dolor vel quia dolor et exerc
|
||||||
<string name="do_it">Do it!</string>
|
<string name="do_it">Do it!</string>
|
||||||
<string name="password">Password</string>
|
<string name="password">Password</string>
|
||||||
|
|
||||||
<string name="search_title">Search %1$s</string>
|
|
||||||
|
|
||||||
<string name="profile_stats_widget">Track progress directly from your home screen</string>
|
<string name="profile_stats_widget">Track progress directly from your home screen</string>
|
||||||
<string name="anime_watched">Anime\nWatched</string>
|
<string name="anime_watched">Anime\nWatched</string>
|
||||||
<string name="manga_read">Manga\nRead</string>
|
<string name="manga_read">Manga\nRead</string>
|
||||||
|
@ -981,4 +979,5 @@ Non quae tempore quo provident laudantium qui illo dolor vel quia dolor et exerc
|
||||||
<string name="download_subtitle">Download Subtitle</string>
|
<string name="download_subtitle">Download Subtitle</string>
|
||||||
<string name="no_video_selected">No video selected</string>
|
<string name="no_video_selected">No video selected</string>
|
||||||
<string name="no_subtitles_available">No subtitles available</string>
|
<string name="no_subtitles_available">No subtitles available</string>
|
||||||
|
<string name="vote_out_of_total">(%1$s out of %2$s liked this review)</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue