Skip to content

Commit

Permalink
feat: anilist notifications
Browse files Browse the repository at this point in the history
  • Loading branch information
rebelonion committed Mar 7, 2024
1 parent e2eae62 commit 7ac679f
Show file tree
Hide file tree
Showing 15 changed files with 573 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ object Anilist {
var bg: String? = null
var episodesWatched: Int? = null
var chapterRead: Int? = null
var unreadNotificationCount: Int = 0

var genres: ArrayList<String>? = null
var tags: Map<Boolean, List<String>>? = null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import ani.dantotsu.checkId
import ani.dantotsu.connections.anilist.Anilist.authorRoles
import ani.dantotsu.connections.anilist.Anilist.executeQuery
import ani.dantotsu.connections.anilist.api.FuzzyDate
import ani.dantotsu.connections.anilist.api.Notification
import ani.dantotsu.connections.anilist.api.NotificationResponse
import ani.dantotsu.connections.anilist.api.Page
import ani.dantotsu.connections.anilist.api.Query
import ani.dantotsu.currContext
Expand Down Expand Up @@ -36,7 +38,7 @@ class AnilistQueries {
val response: Query.Viewer?
measureTimeMillis {
response =
executeQuery("""{Viewer{name options{displayAdultContent}avatar{medium}bannerImage id mediaListOptions{rowOrder animeList{sectionOrder customLists}mangaList{sectionOrder customLists}}statistics{anime{episodesWatched}manga{chaptersRead}}}}""")
executeQuery("""{Viewer{name options{displayAdultContent}avatar{medium}bannerImage id mediaListOptions{rowOrder animeList{sectionOrder customLists}mangaList{sectionOrder customLists}}statistics{anime{episodesWatched}manga{chaptersRead}}unreadNotificationCount}}""")
}.also { println("time : $it") }
val user = response?.data?.user ?: return false

Expand All @@ -49,6 +51,7 @@ class AnilistQueries {
Anilist.episodesWatched = user.statistics?.anime?.episodesWatched
Anilist.chapterRead = user.statistics?.manga?.chaptersRead
Anilist.adult = user.options?.displayAdultContent ?: false
Anilist.unreadNotificationCount = user.unreadNotificationCount?:0
return true
}

Expand Down Expand Up @@ -1337,4 +1340,12 @@ Page(page:$page,perPage:50) {
default[1] = userBannerImage("MANGA",id)
return default
}

suspend fun getNotifications(id: Int): NotificationResponse? {
val res = executeQuery<NotificationResponse>("""{User(id:$id){unreadNotificationCount}Page{notifications(resetNotificationCount:true){__typename...on AiringNotification{id,type,animeId,episode,contexts,createdAt,media{id,title{romaji,english,native,userPreferred}bannerImage,coverImage{medium,large}},}...on FollowingNotification{id,userId,type,context,createdAt,user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityMessageNotification{id,userId,type,activityId,context,createdAt,message{id}user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityMentionNotification{id,userId,type,activityId,context,createdAt,activity{__typename}user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityReplyNotification{id,userId,type,activityId,context,createdAt,activity{__typename}user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityReplySubscribedNotification{id,userId,type,activityId,context,createdAt,activity{__typename}user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityLikeNotification{id,userId,type,activityId,context,createdAt,activity{__typename}user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityReplyLikeNotification{id,userId,type,activityId,context,createdAt,activity{__typename}user{id,name,bannerImage,avatar{medium,large,}}}...on ThreadCommentMentionNotification{id,userId,type,commentId,context,createdAt,thread{id}comment{id}user{id,name,bannerImage,avatar{medium,large,}}}...on ThreadCommentReplyNotification{id,userId,type,commentId,context,createdAt,thread{id}comment{id}user{id,name,bannerImage,avatar{medium,large,}}}...on ThreadCommentSubscribedNotification{id,userId,type,commentId,context,createdAt,thread{id}comment{id}user{id,name,bannerImage,avatar{medium,large,}}}...on ThreadCommentLikeNotification{id,userId,type,commentId,context,createdAt,thread{id}comment{id}user{id,name,bannerImage,avatar{medium,large,}}}...on ThreadLikeNotification{id,userId,type,threadId,context,createdAt,thread{id}comment{id}user{id,name,bannerImage,avatar{medium,large,}}}...on RelatedMediaAdditionNotification{id,type,context,createdAt,media{id,title{romaji,english,native,userPreferred}bannerImage,coverImage{medium,large}}}...on MediaDataChangeNotification{id,type,mediaId,context,reason,createdAt,media{id,title{romaji,english,native,userPreferred}bannerImage,coverImage{medium,large}}}...on MediaMergeNotification{id,type,mediaId,deletedMediaTitles,context,reason,createdAt,media{id,title{romaji,english,native,userPreferred}bannerImage,coverImage{medium,large}}}...on MediaDeletionNotification{id,type,deletedMediaTitle,context,reason,createdAt,}}}}""", force = true)
if (res != null) {
Anilist.unreadNotificationCount = 0
}
return res
}
}
118 changes: 118 additions & 0 deletions app/src/main/java/ani/dantotsu/connections/anilist/api/Notification.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package ani.dantotsu.connections.anilist.api

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

enum class NotificationType(val value: String) {
ACTIVITY_MESSAGE("ACTIVITY_MESSAGE"),
ACTIVITY_REPLY("ACTIVITY_REPLY"),
FOLLOWING("FOLLOWING"),
ACTIVITY_MENTION("ACTIVITY_MENTION"),
THREAD_COMMENT_MENTION("THREAD_COMMENT_MENTION"),
THREAD_SUBSCRIBED("THREAD_SUBSCRIBED"),
THREAD_COMMENT_REPLY("THREAD_COMMENT_REPLY"),
AIRING("AIRING"),
ACTIVITY_LIKE("ACTIVITY_LIKE"),
ACTIVITY_REPLY_LIKE("ACTIVITY_REPLY_LIKE"),
THREAD_LIKE("THREAD_LIKE"),
THREAD_COMMENT_LIKE("THREAD_COMMENT_LIKE"),
ACTIVITY_REPLY_SUBSCRIBED("ACTIVITY_REPLY_SUBSCRIBED"),
RELATED_MEDIA_ADDITION("RELATED_MEDIA_ADDITION"),
MEDIA_DATA_CHANGE("MEDIA_DATA_CHANGE"),
MEDIA_MERGE("MEDIA_MERGE"),
MEDIA_DELETION("MEDIA_DELETION")
}

@Serializable
data class NotificationResponse(
@SerialName("data")
val data: Data,
) : java.io.Serializable {
@Serializable
data class Data(
@SerialName("User")
val user: NotificationUser,
@SerialName("Page")
val page: NotificationPage,
) : java.io.Serializable
}

@Serializable
data class NotificationUser(
@SerialName("unreadNotificationCount")
val unreadNotificationCount: Int,
) : java.io.Serializable

@Serializable
data class NotificationPage(
@SerialName("notifications")
val notifications: List<Notification>,
) : java.io.Serializable

@Serializable
data class Notification(
@SerialName("__typename")
val typename: String,
@SerialName("id")
val id: Int,
@SerialName("userId")
val userId: Int?,
@SerialName("CommentId")
val commentId: Int?,
@SerialName("type")
val notificationType: String,
@SerialName("activityId")
val activityId: Int?,
@SerialName("animeId")
val mediaId: Int?,
@SerialName("episode")
val episode: Int?,
@SerialName("contexts")
val contexts: List<String>?,
@SerialName("context")
val context: String?,
@SerialName("reason")
val reason: String?,
@SerialName("deletedMediaTitle")
val deletedMediaTitle: String?,
@SerialName("deletedMediaTitles")
val deletedMediaTitles: List<String>?,
@SerialName("createdAt")
val createdAt: Int,
@SerialName("media")
val media: ani.dantotsu.connections.anilist.api.Media?,
@SerialName("user")
val user: ani.dantotsu.connections.anilist.api.User?,
@SerialName("message")
val message: MessageActivity?,
@SerialName("activity")
val activity: ActivityUnion?,
@SerialName("Thread")
val thread: Thread?,
@SerialName("comment")
val comment: ThreadComment?,
) : java.io.Serializable

@Serializable
data class MessageActivity(
@SerialName("id")
val id: Int?,
) : java.io.Serializable

@Serializable
data class ActivityUnion(
@SerialName("id")
val id: Int?,
) : java.io.Serializable

@Serializable
data class Thread(
@SerialName("id")
val id: Int?,
) : java.io.Serializable

@Serializable
data class ThreadComment(
@SerialName("id")
val id: Int?,
) : java.io.Serializable
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ data class User(
@SerialName("statistics") var statistics: UserStatisticTypes?,

// The number of unread notifications the user has
// @SerialName("unreadNotificationCount") var unreadNotificationCount: Int?,
@SerialName("unreadNotificationCount") var unreadNotificationCount: Int?,

// The url for the user page on the AniList website
// @SerialName("siteUrl") var siteUrl: String?,
Expand Down
4 changes: 4 additions & 0 deletions app/src/main/java/ani/dantotsu/home/HomeFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ class HomeFragment : Fragment() {
binding.homeUserBg.loadImage(Anilist.bg)
binding.homeUserDataProgressBar.visibility = View.GONE

binding.homeNotificationDot.visibility = if (Anilist.unreadNotificationCount > 0) View.VISIBLE else View.GONE

binding.homeAnimeList.setOnClickListener {
ContextCompat.startActivity(
requireActivity(), Intent(requireActivity(), ListActivity::class.java)
Expand Down Expand Up @@ -361,6 +363,8 @@ class HomeFragment : Fragment() {

override fun onResume() {
if (!model.loaded) Refresh.activity[1]!!.postValue(true)
if (_binding != null)
binding.homeNotificationDot.visibility = if (Anilist.unreadNotificationCount > 0) View.VISIBLE else View.GONE
super.onResume()
}
}
Original file line number Diff line number Diff line change
@@ -1,22 +1,34 @@
package ani.dantotsu.notifications

import android.content.Intent
import android.os.Bundle
import android.view.ViewGroup
import android.view.Window
import android.view.WindowManager
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
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.Notification
import ani.dantotsu.databinding.ActivityNotificationBinding
import ani.dantotsu.initActivity
import ani.dantotsu.media.MediaDetailsActivity
import ani.dantotsu.profile.ProfileActivity
import ani.dantotsu.profile.activity.NotificationItem
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.statusBarHeight
import ani.dantotsu.themes.ThemeManager
import com.xwray.groupie.GroupieAdapter
import kotlinx.coroutines.launch

class NotificationActivity : AppCompatActivity() {
private lateinit var binding: ActivityNotificationBinding
private var adapter: GroupieAdapter = GroupieAdapter()
private var notificationList: List<Notification> = emptyList()

override fun onCreate(savedInstanceState: Bundle?) {
val immersiveMode = PrefManager.getVal<Boolean>(PrefName.ImmersiveMode)
Expand All @@ -43,5 +55,46 @@ class NotificationActivity : AppCompatActivity() {
}
}
setContentView(binding.root)

binding.notificationList.adapter = adapter
binding.notificationList.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)

binding.listBack.setOnClickListener {
onBackPressed()
}

lifecycleScope.launch {
val res = Anilist.query.getNotifications(Anilist.userid?:0)
res?.data?.page?.notifications?.let { notifications ->
notificationList = notifications
adapter.update(notificationList.map { NotificationItem(it, ::onNotificationClick) })
}
}
}

private fun onNotificationClick(id: Int, type: NotificationClickType) {
when (type) {
NotificationClickType.USER -> {
ContextCompat.startActivity(
this, Intent(this, ProfileActivity::class.java)
.putExtra("userId", id), null
)
}
NotificationClickType.MEDIA -> {
ContextCompat.startActivity(
this, Intent(this, MediaDetailsActivity::class.java)
.putExtra("mediaId", id), null
)
}
NotificationClickType.UNDEFINED -> {
// Do nothing
}
}
}

companion object {
enum class NotificationClickType {
USER, MEDIA, UNDEFINED
}
}
}
34 changes: 27 additions & 7 deletions app/src/main/java/ani/dantotsu/profile/ProfileFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,14 @@ class ProfileFragment() : Fragment() {
binding.profileUserBio.settings.loadWithOverviewMode = true
binding.profileUserBio.settings.useWideViewPort = true
binding.profileUserBio.setInitialScale(1)
val styledHtml = styled(
convertMarkdownToHtml(user.about ?: ""),
backGroundColorTypedValue.data,
textColorTypedValue.data
)
binding.profileUserBio.loadDataWithBaseURL(
null,
styled(
convertMarkdownToHtml(user.about ?: ""),
backGroundColorTypedValue.data,
textColorTypedValue.data
),
styledHtml,
"text/html; charset=utf-8",
"UTF-8",
null
Expand Down Expand Up @@ -215,7 +216,22 @@ class ProfileFragment() : Fragment() {
}
}

private fun styled(html: String, backGroundColor: Int, textColor: Int): String {
private fun styled(html: String, backGroundColor: Int, textColor: Int): String { //istg anilist has the worst api
//remove some of the html entities
val step1 = html.replace("&nbsp;", " ")
.replace("&amp;", "&")
.replace("&lt;", "<")
.replace("&gt;", ">")
.replace("&quot;", "\"")
.replace("&apos;", "'")
.replace("<pre>", "")
.replace("`", "")
.replace("~", "")

val step2 = step1.replace("(?s)___(.*?)___".toRegex(), "<br><em><strong>$1</strong></em><br>")
val step3 = step2.replace("(?s)__(.*?)__".toRegex(), "<br><strong>$1</strong><br>")


return """
<html>
<head>
Expand All @@ -233,14 +249,18 @@ class ProfileFragment() : Fragment() {
max-width: 100%;
height: auto; /* Maintain aspect ratio */
}
video {
max-width: 100%;
height: auto; /* Maintain aspect ratio */
}
a {
color: ${textColor.toCssColor()};
}
/* Add responsive design elements for other content as needed */
</style>
</head>
<body>
$html
$step3
</body>
""".trimIndent()
Expand Down
22 changes: 22 additions & 0 deletions app/src/main/java/ani/dantotsu/profile/activity/ActivityItem.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package ani.dantotsu.profile.activity

import android.view.View
import ani.dantotsu.R
import ani.dantotsu.databinding.ItemNotificationBinding
import com.xwray.groupie.viewbinding.BindableItem

class ActivityItem(
): BindableItem<ItemNotificationBinding>() {
private lateinit var binding: ItemNotificationBinding
override fun bind(viewBinding: ItemNotificationBinding, position: Int) {
binding = viewBinding
}

override fun getLayout(): Int {
return R.layout.item_notification
}

override fun initializeViewBinding(view: View): ItemNotificationBinding {
return ItemNotificationBinding.bind(view)
}
}
Loading

0 comments on commit 7ac679f

Please sign in to comment.