Skip to content

Commit

Permalink
feat: reviews
Browse files Browse the repository at this point in the history
  • Loading branch information
rebelonion committed May 12, 2024
1 parent 831b99a commit a0fabd3
Show file tree
Hide file tree
Showing 16 changed files with 642 additions and 34 deletions.
6 changes: 6 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,12 @@
<activity
android:name=".others.imagesearch.ImageSearchActivity"
android:parentActivityName=".MainActivity" />
<activity
android:name=".media.ReviewActivity"
android:parentActivityName=".media.MediaDetailsActivity" />
<activity
android:name=".media.ReviewViewActivity"
android:parentActivityName=".media.ReviewActivity" />
<activity
android:name=".media.SearchActivity"
android:parentActivityName=".MainActivity" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package ani.dantotsu.connections.anilist

import ani.dantotsu.connections.anilist.Anilist.executeQuery
import ani.dantotsu.connections.anilist.api.FuzzyDate
import ani.dantotsu.connections.anilist.api.Query
import kotlinx.serialization.json.JsonObject

class AnilistMutations {
Expand Down Expand Up @@ -69,4 +70,10 @@ class AnilistMutations {
val variables = """{"id":"$listId"}"""
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)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1504,6 +1504,13 @@ Page(page:$page,perPage:50) {
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? {
return executeQuery<Query.ToggleFollow>(
"""mutation{ToggleFollow(userId:$id){id, isFollowing, isFollower}}"""
Expand Down
64 changes: 64 additions & 0 deletions app/src/main/java/ani/dantotsu/connections/anilist/api/Data.kt
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,70 @@ class Query {
val following: List<ani.dantotsu.connections.anilist.api.User>?
) : 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
data class UserProfile(
@SerialName("id")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ class AnimeDownloaderService : Service() {
task.episode
) ?: 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")
?: throw Exception("Failed to create output file")

Expand All @@ -245,7 +245,7 @@ class AnimeDownloaderService : Service() {
.append(defaultHeaders["User-Agent"]).append("\"\'\r\n\'")
}
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(
probeRequest
) {
Expand All @@ -256,7 +256,7 @@ class AnimeDownloaderService : Service() {

val headers = headersStringBuilder.toString()
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" +
" -reconnect_streamed 1 -allowed_extensions ALL " +
"-tls_verify 0 $path -v trace"
Expand Down
32 changes: 15 additions & 17 deletions app/src/main/java/ani/dantotsu/media/MediaInfoFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -517,26 +517,24 @@ class MediaInfoFragment : Fragment() {
}
parent.addView(root)
}
}

ItemTitleSearchBinding.inflate(
LayoutInflater.from(context),
parent,
false
).apply {

titleSearchImage.loadImage(media.banner ?: media.cover)
titleSearchText.text =
getString(R.string.search_title, media.mainName())
titleSearchCard.setSafeOnClickListener {
val query = Intent(requireContext(), SearchActivity::class.java)
.putExtra("type", "ANIME")
.putExtra("query", media.mainName())
.putExtra("search", true)
ContextCompat.startActivity(requireContext(), query, null)
}
ItemTitleSearchBinding.inflate(
LayoutInflater.from(context),
parent,
false
).apply {

parent.addView(root)
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)
}

parent.addView(root)
}

ItemTitleRecyclerBinding.inflate(
Expand Down
149 changes: 149 additions & 0 deletions app/src/main/java/ani/dantotsu/media/ReviewActivity.kt
Original file line number Diff line number Diff line change
@@ -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))
}
}
}
Loading

0 comments on commit a0fabd3

Please sign in to comment.