This commit is contained in:
rebelonion 2024-05-20 07:29:04 -05:00
commit 41ed5a66da
10 changed files with 97 additions and 50 deletions

View file

@ -75,7 +75,7 @@ class AnilistQueries {
media.cameFromContinue = false
val query =
"""{Media(id:${media.id}){id favourites popularity episodes chapters mediaListEntry{id status score(format:POINT_100)progress private notes repeat customLists updatedAt startedAt{year month day}completedAt{year month day}}isFavourite siteUrl idMal nextAiringEpisode{episode airingAt}source countryOfOrigin format duration season seasonYear startDate{year month day}endDate{year month day}genres studios(isMain:true){nodes{id name siteUrl}}description trailer{site id}synonyms tags{name rank isMediaSpoiler}characters(sort:[ROLE,FAVOURITES_DESC],perPage:25,page:1){edges{role voiceActors { id name { first middle last full native userPreferred } image { large medium } languageV2 } node{id image{medium}name{userPreferred}isFavourite}}}relations{edges{relationType(version:2)node{id idMal mediaListEntry{progress private score(format:POINT_100)status}episodes chapters nextAiringEpisode{episode}popularity meanScore isAdult isFavourite format title{english romaji userPreferred}type status(version:2)bannerImage coverImage{large}}}}staffPreview:staff(perPage:8,sort:[RELEVANCE,ID]){edges{role node{id image{large medium}name{userPreferred}}}}recommendations(sort:RATING_DESC){nodes{mediaRecommendation{id idMal mediaListEntry{progress private score(format:POINT_100)status}episodes chapters nextAiringEpisode{episode}meanScore isAdult isFavourite format title{english romaji userPreferred}type status(version:2)bannerImage coverImage{large}}}}externalLinks{url site}}Page(page:1){pageInfo{total perPage currentPage lastPage hasNextPage}mediaList(isFollowing:true,sort:[STATUS],mediaId:${media.id}){id status score(format: POINT_100) progress progressVolumes user{id name avatar{large medium}}}}}"""
"""{Media(id:${media.id}){id favourites popularity episodes chapters mediaListEntry{id status score(format:POINT_100)progress private notes repeat customLists updatedAt startedAt{year month day}completedAt{year month day}}reviews(perPage:3, sort:SCORE_DESC){nodes{id mediaId mediaType summary body(asHtml:true) rating ratingAmount userRating score private siteUrl createdAt updatedAt user{id name bannerImage avatar{medium large}}}}isFavourite siteUrl idMal nextAiringEpisode{episode airingAt}source countryOfOrigin format duration season seasonYear startDate{year month day}endDate{year month day}genres studios(isMain:true){nodes{id name siteUrl}}description trailer{site id}synonyms tags{name rank isMediaSpoiler}characters(sort:[ROLE,FAVOURITES_DESC],perPage:25,page:1){edges{role voiceActors { id name { first middle last full native userPreferred } image { large medium } languageV2 } node{id image{medium}name{userPreferred}isFavourite}}}relations{edges{relationType(version:2)node{id idMal mediaListEntry{progress private score(format:POINT_100)status}episodes chapters nextAiringEpisode{episode}popularity meanScore isAdult isFavourite format title{english romaji userPreferred}type status(version:2)bannerImage coverImage{large}}}}staffPreview:staff(perPage:8,sort:[RELEVANCE,ID]){edges{role node{id image{large medium}name{userPreferred}}}}recommendations(sort:RATING_DESC){nodes{mediaRecommendation{id idMal mediaListEntry{progress private score(format:POINT_100)status}episodes chapters nextAiringEpisode{episode}meanScore isAdult isFavourite format title{english romaji userPreferred}type status(version:2)bannerImage coverImage{large}}}}externalLinks{url site}}Page(page:1){pageInfo{total perPage currentPage lastPage hasNextPage}mediaList(isFollowing:true,sort:[STATUS],mediaId:${media.id}){id status score(format: POINT_100) progress progressVolumes user{id name avatar{large medium}}}}}"""
runBlocking {
val anilist = async {
var response = executeQuery<Query.Media>(query, force = true)
@ -211,6 +211,9 @@ class AnilistQueries {
}
}
}
if (fetchedMedia.reviews?.nodes != null){
media.review = fetchedMedia.reviews!!.nodes as ArrayList<Query.Review>
}
if (user?.mediaList?.isNotEmpty() == true) {
media.users = user.mediaList?.mapNotNull {
it.user?.let { user ->
@ -1505,7 +1508,7 @@ Page(page:$page,perPage:50) {
return author
}
suspend fun getReviews(mediaId: Int, page: Int = 1, sort: String = "CREATED_AT_DESC"): Query.ReviewsResponse? {
suspend fun getReviews(mediaId: Int, page: Int = 1, sort: String = "SCORE_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

View file

@ -152,7 +152,7 @@ data class Media(
@SerialName("mediaListEntry") var mediaListEntry: MediaList?,
// User reviews of the media
// @SerialName("reviews") var reviews: ReviewConnection?,
@SerialName("reviews") var reviews: ReviewConnection?,
// User recommendations for similar media
@SerialName("recommendations") var recommendations: RecommendationConnection?,
@ -538,3 +538,8 @@ data class MediaListGroup(
@SerialName("status") var status: MediaListStatus?,
) : java.io.Serializable
@Serializable
data class ReviewConnection(
@SerialName("nodes") var nodes: List<Query.Review>?,
)

View file

@ -5,6 +5,7 @@ import ani.dantotsu.connections.anilist.api.FuzzyDate
import ani.dantotsu.connections.anilist.api.MediaEdge
import ani.dantotsu.connections.anilist.api.MediaList
import ani.dantotsu.connections.anilist.api.MediaType
import ani.dantotsu.connections.anilist.api.Query
import ani.dantotsu.media.anime.Anime
import ani.dantotsu.media.manga.Manga
import ani.dantotsu.profile.User
@ -62,6 +63,7 @@ data class Media(
var timeUntilAiring: Long? = null,
var characters: ArrayList<Character>? = null,
var review: ArrayList<Query.Review>? = null,
var staff: ArrayList<Author>? = null,
var prequel: Media? = null,
var sequel: Media? = null,

View file

@ -34,7 +34,6 @@ import ani.dantotsu.databinding.ItemChipBinding
import ani.dantotsu.databinding.ItemQuelsBinding
import ani.dantotsu.databinding.ItemTitleChipgroupBinding
import ani.dantotsu.databinding.ItemTitleRecyclerBinding
import ani.dantotsu.databinding.ItemTitleSearchBinding
import ani.dantotsu.databinding.ItemTitleTextBinding
import ani.dantotsu.databinding.ItemTitleTrailerBinding
import ani.dantotsu.displayTimer
@ -46,6 +45,7 @@ import ani.dantotsu.px
import ani.dantotsu.setSafeOnClickListener
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import com.xwray.groupie.GroupieAdapter
import io.noties.markwon.Markwon
import io.noties.markwon.SoftBreakAddsNewLinePlugin
import kotlinx.coroutines.Dispatchers
@ -81,7 +81,8 @@ class MediaInfoFragment : Fragment() {
@SuppressLint("SetJavaScriptEnabled")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val model: MediaDetailsViewModel by activityViewModels()
val offline: Boolean = PrefManager.getVal(PrefName.OfflineMode) || !isOnline(requireContext())
val offline: Boolean =
PrefManager.getVal(PrefName.OfflineMode) || !isOnline(requireContext())
binding.mediaInfoProgressBar.isGone = loaded
binding.mediaInfoContainer.isVisible = loaded
binding.mediaInfoContainer.updateLayoutParams<ViewGroup.MarginLayoutParams> { bottomMargin += 128f.px + navBarHeight }
@ -254,7 +255,8 @@ class MediaInfoFragment : Fragment() {
if (!media.users.isNullOrEmpty() && !offline) {
val users: ArrayList<User> = media.users ?: arrayListOf()
if (Anilist.token != null && media.userStatus != null) {
users.add(0,
users.add(
0,
User(
id = Anilist.userid!!,
name = getString(R.string.you),
@ -263,7 +265,8 @@ class MediaInfoFragment : Fragment() {
status = media.userStatus,
score = media.userScore.toFloat(),
progress = media.userProgress,
totalEpisodes = media.anime?.totalEpisodes ?: media.manga?.totalChapters,
totalEpisodes = media.anime?.totalEpisodes
?: media.manga?.totalChapters,
nextAiringEpisode = media.anime?.nextAiringEpisode
)
)
@ -519,23 +522,42 @@ class MediaInfoFragment : Fragment() {
}
}
ItemTitleSearchBinding.inflate(
if (!media.review.isNullOrEmpty()) {
ItemTitleRecyclerBinding.inflate(
LayoutInflater.from(context),
parent,
false
).apply {
titleSearchImage.loadImage(media.banner ?: media.cover)
titleSearchText.text =
getString(R.string.reviews)
titleSearchCard.setSafeOnClickListener {
val query = Intent(requireContext(), ReviewActivity::class.java)
.putExtra("mediaId", media.id)
ContextCompat.startActivity(requireContext(), query, null)
fun onUserClick(userId: Int) {
val review = media.review!!.find { i -> i.id == userId }
if (review != null) {
startActivity(
Intent(requireContext(), ReviewViewActivity::class.java)
.putExtra("review", review)
)
}
}
val adapter = GroupieAdapter()
media.review!!.forEach {
adapter.add(ReviewAdapter(it, ::onUserClick))
}
itemTitle.setText(R.string.reviews)
itemRecycler.adapter = adapter
itemRecycler.layoutManager = LinearLayoutManager(
requireContext(),
LinearLayoutManager.VERTICAL,
false
)
itemMore.visibility = View.VISIBLE
itemMore.setSafeOnClickListener {
startActivity(
Intent(requireContext(), ReviewActivity::class.java)
.putExtra("mediaId", media.id)
)
}
parent.addView(root)
}
}
ItemTitleRecyclerBinding.inflate(
LayoutInflater.from(context),

View file

@ -125,7 +125,6 @@ class ReviewActivity : AppCompatActivity() {
adapter.add(
ReviewAdapter(
it,
this,
this::onUserClick
)
)

View file

@ -1,30 +1,24 @@
package ani.dantotsu.media
import android.content.Context
import android.text.SpannableString
import android.view.View
import androidx.lifecycle.lifecycleScope
import ani.dantotsu.R
import ani.dantotsu.blurImage
import ani.dantotsu.connections.anilist.Anilist
import ani.dantotsu.connections.anilist.api.Query
import ani.dantotsu.databinding.ItemFollowerBinding
import ani.dantotsu.databinding.ItemReviewsBinding
import ani.dantotsu.getThemeColor
import ani.dantotsu.loadImage
import ani.dantotsu.profile.activity.ActivityItemBuilder
import ani.dantotsu.toast
import com.xwray.groupie.viewbinding.BindableItem
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class ReviewAdapter(
private var review: Query.Review,
val context: ReviewActivity,
val clickCallback: (Int) -> Unit
) : BindableItem<ItemReviewsBinding>() {
private lateinit var binding: ItemReviewsBinding
@ -34,7 +28,8 @@ class ReviewAdapter(
binding.reviewUserAvatar.loadImage(review.user?.avatar?.medium)
binding.reviewText.text = review.summary
binding.reviewPostTime.text = ActivityItemBuilder.getDateTime(review.createdAt)
binding.reviewTag.text = "[${review.score}]"
val text = "[${review.score/ 10.0f}]"
binding.reviewTag.text = text
binding.root.setOnClickListener { clickCallback(review.id) }
userVote(review.userRating)
enableVote()
@ -75,7 +70,8 @@ class ReviewAdapter(
private fun rateReview(rating: String) {
disableVote()
context.lifecycleScope.launch {
val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
scope.launch {
val result = Anilist.mutation.rateReview(review.id, rating)
if (result != null) {
withContext(Dispatchers.Main) {
@ -91,7 +87,7 @@ class ReviewAdapter(
} else {
withContext(Dispatchers.Main) {
toast(
context.getString(R.string.error_message, "response is null")
binding.root.context.getString(R.string.error_message, "response is null")
)
enableVote()
}

View file

@ -27,7 +27,6 @@
android:id="@+id/activityUserAvatar"
android:layout_width="42dp"
android:layout_height="42dp"
android:layout_gravity="center"
app:srcCompat="@drawable/ic_round_add_circle_24"
tools:ignore="ContentDescription,ImageContrastCheck"
tools:tint="@color/transparent" />

View file

@ -12,14 +12,16 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:backgroundTint="@color/bg_white"
android:backgroundTint="@color/transparent"
app:strokeColor="@color/transparent"
app:cardCornerRadius="124dp">
<ImageView
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/profileUserAvatar"
android:layout_width="100dp"
android:layout_height="100dp"
tools:ignore="ContentDescription,ImageContrastCheck"
android:layout_width="92dp"
android:layout_height="92dp"
app:srcCompat="@drawable/ic_round_add_circle_24"
tools:ignore="ContentDescription"
tools:tint="@color/transparent" />
<LinearLayout

View file

@ -24,7 +24,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="12dp"
android:backgroundTint="@color/transparent"
app:cardCornerRadius="64dp"
app:strokeColor="@color/transparent">

View file

@ -5,16 +5,35 @@
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/itemTitle"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="32dp"
android:orientation="horizontal"
tools:ignore="UseCompoundDrawables">
<TextView
android:id="@+id/itemTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_weight="1"
android:fontFamily="@font/poppins_bold"
android:padding="8dp"
android:text="@string/relations"
android:textSize="16sp" />
<ImageView
android:id="@+id/itemMore"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginEnd="16dp"
android:fontFamily="@font/poppins_bold"
android:padding="8dp"
android:visibility="gone"
android:src="@drawable/arrow_mark"
android:textSize="16sp"
tools:ignore="ContentDescription" />
</LinearLayout>
<ani.dantotsu.FadingEdgeRecyclerView
android:id="@+id/itemRecycler"