feat: more thumbnails, descriptions (thanks to @yupcm)

This commit is contained in:
aayush262 2024-05-29 01:59:13 +05:30
parent 11655bd38d
commit 8822ef6805
25 changed files with 279 additions and 101 deletions

View file

@ -101,6 +101,14 @@ class AnilistMutations {
return errors?.toString()
?: (currContext()?.getString(ani.dantotsu.R.string.success) ?: "Success")
}
suspend fun postMessage(userId: Int, text: String, edit: Int? = null,isPrivate: Boolean = false): String {
val encodedText = text.stringSanitizer()
val query = "mutation{SaveMessageActivity(${if (edit != null) "id:$edit," else ""} recipientId:$userId,message:$encodedText,private:$isPrivate){id}}"
val result = executeQuery<JsonObject>(query)
val errors = result?.get("errors")
return errors?.toString()
?: (currContext()?.getString(ani.dantotsu.R.string.success) ?: "Success")
}
suspend fun postReply(activityId: Int, text: String, edit: Int? = null ): String {
val encodedText = text.stringSanitizer()
val query = "mutation{SaveActivityReply(${if (edit != null) "id:$edit," else ""} activityId:$activityId,text:$encodedText){id}}"

View file

@ -6,6 +6,7 @@ import ani.dantotsu.checkGenreTime
import ani.dantotsu.checkId
import ani.dantotsu.connections.anilist.Anilist.authorRoles
import ani.dantotsu.connections.anilist.Anilist.executeQuery
import ani.dantotsu.connections.anilist.api.Activity
import ani.dantotsu.connections.anilist.api.FeedResponse
import ani.dantotsu.connections.anilist.api.FuzzyDate
import ani.dantotsu.connections.anilist.api.NotificationResponse
@ -453,13 +454,24 @@ class AnilistQueries {
)
if (user.id == Anilist.userid) {
anilistActivities.add(0, userToAdd)
} else {
list.add(userToAdd)
}
}
}
if (anilistActivities.isEmpty() && Anilist.token != null){
anilistActivities.add(0,
User(
Anilist.userid!!,
Anilist.username!!,
Anilist.avatar,
Anilist.bg,
activity = listOf()
)
)
}
list.addAll(0, anilistActivities)
return list.toCollection(ArrayList())
} else return null

View file

@ -101,7 +101,7 @@ class StatusActivity : AppCompatActivity(), StoriesCallback {
override fun onStoriesStart() {
position -= 1
if (position >= 0) {
if (position >= 0 && activity[position].activity.isNotEmpty()) {
val key = "activities"
val watchedActivity = PrefManager.getCustomVal<Set<Int>>(key, setOf())
val startFrom = findFirstNonMatch(watchedActivity, activity[position].activity )

View file

@ -5,6 +5,7 @@ import android.content.Context
import android.content.Intent
import android.content.res.ColorStateList
import android.util.AttributeSet
import android.view.Gravity
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
@ -15,6 +16,7 @@ import androidx.core.app.ActivityOptionsCompat
import androidx.core.content.ContextCompat
import androidx.core.view.ViewCompat
import androidx.core.view.isVisible
import androidx.core.widget.NestedScrollView
import androidx.fragment.app.FragmentActivity
import ani.dantotsu.R
import ani.dantotsu.blurImage

View file

@ -15,6 +15,8 @@ import ani.dantotsu.profile.ProfileActivity
import ani.dantotsu.profile.User
import ani.dantotsu.setAnimation
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.snackString
import ani.dantotsu.util.MarkdownCreatorActivity
class UserStatusAdapter(private val user: ArrayList<User>) :
RecyclerView.Adapter<UserStatusAdapter.UsersViewHolder>() {
@ -23,6 +25,10 @@ class UserStatusAdapter(private val user: ArrayList<User>) :
RecyclerView.ViewHolder(binding.root) {
init {
itemView.setOnClickListener {
if (user[bindingAdapterPosition].activity.isEmpty()) {
snackString("No activity")
return@setOnClickListener
}
StatusActivity.user = user
ContextCompat.startActivity(
itemView.context,
@ -34,14 +40,23 @@ class UserStatusAdapter(private val user: ArrayList<User>) :
)
}
itemView.setOnLongClickListener {
ContextCompat.startActivity(
itemView.context,
Intent(
if (user[bindingAdapterPosition].id == Anilist.userid) {
ContextCompat.startActivity(
itemView.context,
ProfileActivity::class.java
).putExtra("userId", user[bindingAdapterPosition].id),
null
)
Intent(itemView.context, MarkdownCreatorActivity::class.java)
.putExtra("type", "activity"),
null
)
}else{
ContextCompat.startActivity(
itemView.context,
Intent(
itemView.context,
ProfileActivity::class.java
).putExtra("userId", user[bindingAdapterPosition].id),
null
)
}
true
}
}

View file

@ -9,6 +9,7 @@ import androidx.core.content.ContextCompat
import androidx.core.util.Pair
import androidx.core.view.ViewCompat
import androidx.recyclerview.widget.RecyclerView
import ani.dantotsu.copyToClipboard
import ani.dantotsu.databinding.ItemCharacterBinding
import ani.dantotsu.loadImage
import ani.dantotsu.setAnimation
@ -32,6 +33,9 @@ class CharacterAdapter(
binding.itemCompactRelation.text = whitespace
binding.itemCompactImage.loadImage(character.image)
binding.itemCompactTitle.text = character.name
binding.root.setOnClickListener {
copyToClipboard(character.name ?: "")
}
}
override fun getItemCount(): Int = characterList.size

View file

@ -13,6 +13,7 @@ import ani.dantotsu.media.anime.Episode
import ani.dantotsu.media.anime.SelectorDialogFragment
import ani.dantotsu.media.manga.MangaChapter
import ani.dantotsu.others.AniSkip
import ani.dantotsu.others.Anify
import ani.dantotsu.others.Jikan
import ani.dantotsu.others.Kitsu
import ani.dantotsu.parsers.AnimeSources
@ -99,6 +100,15 @@ class MediaDetailsViewModel : ViewModel() {
if (kitsuEpisodes.value == null) kitsuEpisodes.postValue(Kitsu.getKitsuEpisodesDetails(s))
}
}
private val anifyEpisodes: MutableLiveData<Map<String, Episode>> =
MutableLiveData<Map<String, Episode>>(null)
fun getAnifyEpisodes(): LiveData<Map<String, Episode>> = anifyEpisodes
suspend fun loadAnifyEpisodes(s: Int) {
tryWithSuspend {
if (anifyEpisodes.value == null) anifyEpisodes.postValue(Anify.fetchAndParseMetadata(s))
}
}
private val fillerEpisodes: MutableLiveData<Map<String, Episode>> =
MutableLiveData<Map<String, Episode>>(null)

View file

@ -26,4 +26,5 @@ data class Anime(
var slug: String? = null,
var kitsuEpisodes: Map<String, Episode>? = null,
var fillerEpisodes: Map<String, Episode>? = null,
var anifyEpisodes: Map<String, Episode>? = null,
) : Serializable

View file

@ -49,6 +49,7 @@ import ani.dantotsu.media.MediaType
import ani.dantotsu.navBarHeight
import ani.dantotsu.notifications.subscription.SubscriptionHelper
import ani.dantotsu.notifications.subscription.SubscriptionHelper.Companion.saveSubscription
import ani.dantotsu.others.Anify
import ani.dantotsu.others.LanguageMapper
import ani.dantotsu.parsers.AnimeParser
import ani.dantotsu.parsers.AnimeSources
@ -214,8 +215,9 @@ class AnimeWatchFragment : Fragment() {
media.selected!!.sourceIndex = model.watchSources!!.list.lastIndex
} else {
awaitAll(
async { model.loadKitsuEpisodes(media) },
async { model.loadFillerEpisodes(media) }
//async { model.loadKitsuEpisodes(media) }, if someday anify dies
//async { model.loadFillerEpisodes(media) },
async { model.loadAnifyEpisodes(media.id)}
)
}
model.loadEpisodes(media, media.selected!!.sourceIndex)
@ -231,19 +233,6 @@ class AnimeWatchFragment : Fragment() {
val episodes = loadedEpisodes[media.selected!!.sourceIndex]
if (episodes != null) {
episodes.forEach { (i, episode) ->
fun getThumbnail(episodes: List<MediaStreamingEpisode>): List<Pair<String, FileUrl?>> {
return episodes.mapNotNull { episode ->
val regex = Regex("""Episode\s*(\d+)\s*-\s*(.*)""")
val number = episode.title?.let {
val matchResult = regex.matchEntire(it)
matchResult?.destructured?.component1()
}
number?.let { number to FileUrl[episode.thumbnail] }
}
}
val getThumbnail = getThumbnail(media.streamingEpisodes ?: emptyList())
if (media.anime?.fillerEpisodes != null) {
if (media.anime!!.fillerEpisodes!!.containsKey(i)) {
episode.title =
@ -258,11 +247,23 @@ class AnimeWatchFragment : Fragment() {
episode.title = if (MediaNameAdapter.removeEpisodeNumberCompletely(
episode.title ?: ""
).isBlank()
) media.anime!!.kitsuEpisodes!![i]?.title
)
media.anime!!.kitsuEpisodes!![i]?.title
?: episode.title else episode.title
?: media.anime!!.kitsuEpisodes!![i]?.title ?: episode.title
episode.thumb = getThumbnail.find { it.first == i }?.second
?: media.anime!!.kitsuEpisodes!![i]?.thumb ?: episode.thumb
episode.thumb = media.anime!!.kitsuEpisodes!![i]?.thumb ?: episode.thumb
}
}
if (media.anime?.anifyEpisodes != null) {
if (media.anime!!.anifyEpisodes!!.containsKey(i)) {
episode.desc = media.anime!!.anifyEpisodes!![i]?.desc ?: episode.desc
episode.title = if (MediaNameAdapter.removeEpisodeNumberCompletely(
episode.title ?: ""
).isBlank()
) media.anime!!.anifyEpisodes!![i]?.title ?: episode.title else episode.title
?: media.anime!!.anifyEpisodes!![i]?.title ?: episode.title
episode.thumb = media.anime!!.anifyEpisodes!![i]?.thumb ?: episode.thumb
}
}
}
@ -309,6 +310,10 @@ class AnimeWatchFragment : Fragment() {
if (i != null)
media.anime?.fillerEpisodes = i
}
model.getAnifyEpisodes().observe(viewLifecycleOwner) { i ->
if (i != null)
media.anime?.anifyEpisodes = i
}
}
fun onSourceChange(i: Int): AnimeParser {

View file

@ -0,0 +1,46 @@
package ani.dantotsu.others
import ani.dantotsu.FileUrl
import ani.dantotsu.Mapper
import ani.dantotsu.client
import ani.dantotsu.media.anime.Episode
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.decodeFromJsonElement
object Anify {
suspend fun fetchAndParseMetadata(id :Int): Map<String, Episode>? {
val response = client.get("https://api.anify.tv/content-metadata/$id")
.parsed<JsonArray>().map {
Mapper.json.decodeFromJsonElement<ContentMetadata>(it)
}
return response.first().data.associate {
it.number.toString() to Episode(
number = it.number.toString(),
title = it.title,
desc = it.description,
thumb = FileUrl[it.img],
filler = it.isFiller,
)
}
}
@Serializable
data class ContentMetadata(
@SerialName("providerId") val providerId: String,
@SerialName("data") val data: List<ProviderData>
)
@Serializable
data class ProviderData(
@SerialName("id") val id: String,
@SerialName("description") val description: String,
@SerialName("hasDub") val hasDub: Boolean,
@SerialName("img") val img: String,
@SerialName("isFiller") val isFiller: Boolean,
@SerialName("number") val number: Int,
@SerialName("title") val title: String,
@SerialName("updatedAt") val updatedAt: Long,
@SerialName("rating") val rating: Double? = null
)
}

View file

@ -157,15 +157,6 @@ class ProfileActivity : AppCompatActivity(), AppBarLayout.OnOffsetChangedListene
true
}
R.id.action_create_new_activity -> {
ContextCompat.startActivity(
context,
Intent(context, MarkdownCreatorActivity::class.java)
.putExtra("type", "activity"),
null
)
true
}
else -> false
}

View file

@ -20,6 +20,7 @@ import ani.dantotsu.media.MediaDetailsActivity
import ani.dantotsu.navBarHeight
import ani.dantotsu.profile.ProfileActivity
import ani.dantotsu.setBaseline
import ani.dantotsu.util.MarkdownCreatorActivity
import com.xwray.groupie.GroupieAdapter
import eu.kanade.tachiyomi.util.system.getSerializableCompat
import kotlinx.coroutines.launch
@ -46,6 +47,27 @@ class ActivityFragment : Fragment() {
type = arguments?.getSerializableCompat<ActivityType>("type") as ActivityType
userId = arguments?.getInt("userId")
activityId = arguments?.getInt("activityId")
binding.titleBar.visibility = if (type == ActivityType.OTHER_USER) View.VISIBLE else View.GONE
binding.titleText.text = if (userId == Anilist.userid) getString(R.string.create_new_activity) else getString(R.string.write_a_message)
binding.titleImage.setOnClickListener {
if(userId == Anilist.userid) {
ContextCompat.startActivity(
requireContext(),
Intent(context, MarkdownCreatorActivity::class.java)
.putExtra("type", "activity"),
null
)
} else{
ContextCompat.startActivity(
requireContext(),
Intent(context, MarkdownCreatorActivity::class.java)
.putExtra("type", "message")
.putExtra("userId", userId),
null
)
}
}
binding.listRecyclerView.adapter = adapter
binding.listRecyclerView.layoutManager = LinearLayoutManager(context)
binding.listProgressBar.isVisible = true
@ -53,7 +75,7 @@ class ActivityFragment : Fragment() {
binding.feedRefresh.updateLayoutParams<ViewGroup.MarginLayoutParams> {
bottomMargin = navBarHeight
}
binding.emptyTextView.text = getString(R.string.no_notifications)
binding.emptyTextView.text = getString(R.string.no_activities)
lifecycleScope.launch {
getList()
if (adapter.itemCount == 0) {
@ -87,8 +109,8 @@ class ActivityFragment : Fragment() {
private suspend fun getList() {
val list = when (type) {
ActivityType.GLOBAL -> getActivities(true)
ActivityType.USER -> getActivities()
ActivityType.GLOBAL -> getActivities(global = true)
ActivityType.USER -> getActivities(filter = true)
ActivityType.OTHER_USER -> getActivities(userId = userId)
ActivityType.ONE -> getActivities(activityId = activityId)
}
@ -99,12 +121,13 @@ class ActivityFragment : Fragment() {
global: Boolean = false,
userId: Int? = null,
activityId: Int? = null,
filter:Boolean = false
): List<Activity> {
val res = Anilist.query.getFeed(userId, global, page, activityId)?.data?.page?.activities
page += 1
return res
?.filter { if (Anilist.adult) true else it.media?.isAdult != true }
?.filterNot { it.recipient?.id != null && it.recipient.id != Anilist.userid }
?.filterNot { it.recipient?.id != null && it.recipient.id != Anilist.userid && filter }
?: emptyList()
}

View file

@ -68,12 +68,10 @@ class ActivityItem(
}
binding.activityLikeCount.text = (activity.likeCount ?: 0).toString()
binding.activityLikeContainer.setOnClickListener {
scope.launch {
val res = Anilist.mutation.toggleLike(activity.id, "ACTIVITY")
withContext(Dispatchers.Main) {
if (res != null) {
if (activity.isLiked == true) {
activity.likeCount = activity.likeCount?.minus(1)
} else {
@ -89,7 +87,7 @@ class ActivityItem(
}
}
}
binding.activityDelete.isVisible = activity.userId == Anilist.userid
binding.activityDelete.isVisible = activity.userId == Anilist.userid || activity.messenger?.id == Anilist.userid
binding.activityDelete.setOnClickListener {
scope.launch {
val res = Anilist.mutation.deleteActivity(activity.id)
@ -109,6 +107,7 @@ class ActivityItem(
val banner = activity.media?.bannerImage
binding.activityContent.visibility = View.GONE
binding.activityBannerContainer.visibility = View.VISIBLE
binding.activityPrivate.visibility = View.GONE
binding.activityMediaName.text = activity.media?.title?.userPreferred
val activityText = "${activity.user!!.name} ${activity.status} ${
activity.progress
@ -135,6 +134,7 @@ class ActivityItem(
"TextActivity" -> {
binding.activityBannerContainer.visibility = View.GONE
binding.activityContent.visibility = View.VISIBLE
binding.activityPrivate.visibility = View.GONE
if (!(context as android.app.Activity).isDestroyed) {
val markwon = buildMarkwon(context, false)
markwon.setMarkdown(
@ -164,6 +164,7 @@ class ActivityItem(
"MessageActivity" -> {
binding.activityBannerContainer.visibility = View.GONE
binding.activityContent.visibility = View.VISIBLE
binding.activityPrivate.visibility = if (activity.isPrivate == true) View.VISIBLE else View.GONE
if (!(context as android.app.Activity).isDestroyed) {
val markwon = buildMarkwon(context, false)
markwon.setMarkdown(
@ -178,6 +179,18 @@ class ActivityItem(
clickCallback(activity.messengerId ?: -1, "USER")
}
binding.activityEdit.isVisible = false
binding.activityEdit.isVisible = activity.messenger?.id == Anilist.userid
binding.activityEdit.setOnClickListener {
ContextCompat.startActivity(
context,
Intent(context, MarkdownCreatorActivity::class.java)
.putExtra("type", "message")
.putExtra("other", activity.message)
.putExtra("edit", activity.id)
.putExtra("userId", activity.recipientId),
null
)
}
}
}
}

View file

@ -90,10 +90,8 @@ class AniMarkdown { //istg anilist has the worst api
/* Add responsive design elements for other content as needed */
</style>
</head>
<body>
$basicHtml
</body>
<body>$basicHtml</body>
</html>
""".trimIndent()
return returnHtml
}

View file

@ -51,6 +51,7 @@ class MarkdownCreatorActivity : AppCompatActivity() {
binding.markdownCreatorTitle.text = when (type) {
"activity" -> getString(R.string.create_new_activity)
"review" -> getString(R.string.create_new_review)
"message" -> getString(R.string.create_new_message)
"replyActivity" -> {
parentId = intent.getIntExtra("parentId", -1)
if (parentId == -1) {
@ -64,6 +65,7 @@ class MarkdownCreatorActivity : AppCompatActivity() {
else -> ""
}
ping = intent.getStringExtra("other")
val userId = intent.getIntExtra("userId", -1)
text = ping ?: ""
binding.editText.setText(text)
binding.editText.addTextChangedListener {
@ -106,7 +108,11 @@ class MarkdownCreatorActivity : AppCompatActivity() {
} else {
Anilist.mutation.postReply(parentId, text)
}
"message" -> if (isEdit) { //TODO private
Anilist.mutation.postMessage(userId , text, editId)
} else {
Anilist.mutation.postMessage(userId , text)
}
else -> "Error: Unknown type"
}
toast(success)