feat: anilist notifications

This commit is contained in:
rebelonion 2024-03-07 02:51:04 -06:00
parent e2eae6250b
commit 7ac679f927
15 changed files with 572 additions and 28 deletions

View file

@ -28,6 +28,7 @@ object Anilist {
var bg: String? = null var bg: String? = null
var episodesWatched: Int? = null var episodesWatched: Int? = null
var chapterRead: Int? = null var chapterRead: Int? = null
var unreadNotificationCount: Int = 0
var genres: ArrayList<String>? = null var genres: ArrayList<String>? = null
var tags: Map<Boolean, List<String>>? = null var tags: Map<Boolean, List<String>>? = null

View file

@ -7,6 +7,8 @@ import ani.dantotsu.checkId
import ani.dantotsu.connections.anilist.Anilist.authorRoles import ani.dantotsu.connections.anilist.Anilist.authorRoles
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.Notification
import ani.dantotsu.connections.anilist.api.NotificationResponse
import ani.dantotsu.connections.anilist.api.Page import ani.dantotsu.connections.anilist.api.Page
import ani.dantotsu.connections.anilist.api.Query import ani.dantotsu.connections.anilist.api.Query
import ani.dantotsu.currContext import ani.dantotsu.currContext
@ -36,7 +38,7 @@ class AnilistQueries {
val response: Query.Viewer? val response: Query.Viewer?
measureTimeMillis { measureTimeMillis {
response = 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") } }.also { println("time : $it") }
val user = response?.data?.user ?: return false val user = response?.data?.user ?: return false
@ -49,6 +51,7 @@ class AnilistQueries {
Anilist.episodesWatched = user.statistics?.anime?.episodesWatched Anilist.episodesWatched = user.statistics?.anime?.episodesWatched
Anilist.chapterRead = user.statistics?.manga?.chaptersRead Anilist.chapterRead = user.statistics?.manga?.chaptersRead
Anilist.adult = user.options?.displayAdultContent ?: false Anilist.adult = user.options?.displayAdultContent ?: false
Anilist.unreadNotificationCount = user.unreadNotificationCount?:0
return true return true
} }
@ -1337,4 +1340,12 @@ Page(page:$page,perPage:50) {
default[1] = userBannerImage("MANGA",id) default[1] = userBannerImage("MANGA",id)
return default 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
}
} }

View file

@ -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

View file

@ -46,7 +46,7 @@ data class User(
@SerialName("statistics") var statistics: UserStatisticTypes?, @SerialName("statistics") var statistics: UserStatisticTypes?,
// The number of unread notifications the user has // 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 // The url for the user page on the AniList website
// @SerialName("siteUrl") var siteUrl: String?, // @SerialName("siteUrl") var siteUrl: String?,

View file

@ -80,6 +80,8 @@ class HomeFragment : Fragment() {
binding.homeUserBg.loadImage(Anilist.bg) binding.homeUserBg.loadImage(Anilist.bg)
binding.homeUserDataProgressBar.visibility = View.GONE binding.homeUserDataProgressBar.visibility = View.GONE
binding.homeNotificationDot.visibility = if (Anilist.unreadNotificationCount > 0) View.VISIBLE else View.GONE
binding.homeAnimeList.setOnClickListener { binding.homeAnimeList.setOnClickListener {
ContextCompat.startActivity( ContextCompat.startActivity(
requireActivity(), Intent(requireActivity(), ListActivity::class.java) requireActivity(), Intent(requireActivity(), ListActivity::class.java)
@ -361,6 +363,8 @@ class HomeFragment : Fragment() {
override fun onResume() { override fun onResume() {
if (!model.loaded) Refresh.activity[1]!!.postValue(true) 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() super.onResume()
} }
} }

View file

@ -1,5 +1,6 @@
package ani.dantotsu.notifications package ani.dantotsu.notifications
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.ViewGroup import android.view.ViewGroup
import android.view.Window import android.view.Window
@ -7,16 +8,27 @@ import android.view.WindowManager
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.view.updateLayoutParams import androidx.core.view.updateLayoutParams
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import ani.dantotsu.R 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.databinding.ActivityNotificationBinding
import ani.dantotsu.initActivity 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.PrefManager
import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.statusBarHeight import ani.dantotsu.statusBarHeight
import ani.dantotsu.themes.ThemeManager import ani.dantotsu.themes.ThemeManager
import com.xwray.groupie.GroupieAdapter
import kotlinx.coroutines.launch
class NotificationActivity : AppCompatActivity() { class NotificationActivity : AppCompatActivity() {
private lateinit var binding: ActivityNotificationBinding private lateinit var binding: ActivityNotificationBinding
private var adapter: GroupieAdapter = GroupieAdapter()
private var notificationList: List<Notification> = emptyList()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
val immersiveMode = PrefManager.getVal<Boolean>(PrefName.ImmersiveMode) val immersiveMode = PrefManager.getVal<Boolean>(PrefName.ImmersiveMode)
@ -43,5 +55,46 @@ class NotificationActivity : AppCompatActivity() {
} }
} }
setContentView(binding.root) 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
}
} }
} }

View file

@ -70,13 +70,14 @@ class ProfileFragment() : Fragment() {
binding.profileUserBio.settings.loadWithOverviewMode = true binding.profileUserBio.settings.loadWithOverviewMode = true
binding.profileUserBio.settings.useWideViewPort = true binding.profileUserBio.settings.useWideViewPort = true
binding.profileUserBio.setInitialScale(1) binding.profileUserBio.setInitialScale(1)
val styledHtml = styled(
convertMarkdownToHtml(user.about ?: ""),
backGroundColorTypedValue.data,
textColorTypedValue.data
)
binding.profileUserBio.loadDataWithBaseURL( binding.profileUserBio.loadDataWithBaseURL(
null, null,
styled( styledHtml,
convertMarkdownToHtml(user.about ?: ""),
backGroundColorTypedValue.data,
textColorTypedValue.data
),
"text/html; charset=utf-8", "text/html; charset=utf-8",
"UTF-8", "UTF-8",
null null
@ -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 """ return """
<html> <html>
<head> <head>
@ -233,6 +249,10 @@ class ProfileFragment() : Fragment() {
max-width: 100%; max-width: 100%;
height: auto; /* Maintain aspect ratio */ height: auto; /* Maintain aspect ratio */
} }
video {
max-width: 100%;
height: auto; /* Maintain aspect ratio */
}
a { a {
color: ${textColor.toCssColor()}; color: ${textColor.toCssColor()};
} }
@ -240,7 +260,7 @@ class ProfileFragment() : Fragment() {
</style> </style>
</head> </head>
<body> <body>
$html $step3
</body> </body>
""".trimIndent() """.trimIndent()

View file

@ -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)
}
}

View file

@ -0,0 +1,144 @@
package ani.dantotsu.profile.activity
import android.view.View
import ani.dantotsu.R
import ani.dantotsu.connections.anilist.api.Notification
import ani.dantotsu.connections.anilist.api.NotificationType
import ani.dantotsu.databinding.ItemNotificationBinding
import ani.dantotsu.loadImage
import ani.dantotsu.notifications.NotificationActivity
import com.xwray.groupie.viewbinding.BindableItem
class NotificationItem(
private val notification: Notification,
val clickCallback: (Int, NotificationActivity.Companion.NotificationClickType) -> Unit
): BindableItem<ItemNotificationBinding>() {
private lateinit var binding: ItemNotificationBinding
private lateinit var clickType: NotificationActivity.Companion.NotificationClickType
private var id = 0
override fun bind(viewBinding: ItemNotificationBinding, position: Int) {
binding = viewBinding
setBinding()
}
override fun getLayout(): Int {
return R.layout.item_notification
}
override fun initializeViewBinding(view: View): ItemNotificationBinding {
return ItemNotificationBinding.bind(view)
}
private fun setBinding() {
val notificationType: NotificationType =
NotificationType.valueOf(notification.notificationType)
binding.notificationText.text = NotificationItemBuilder.getContent(notification)
binding.notificationDate.text = NotificationItemBuilder.getDateTime(notification.createdAt)
binding.root.setOnClickListener { clickCallback(id, clickType) }
when (notificationType) {
NotificationType.ACTIVITY_MESSAGE -> {
binding.notificationCover.loadImage(notification.user?.avatar?.large)
binding.notificationBannerImage.loadImage(notification.user?.bannerImage)
clickType = NotificationActivity.Companion.NotificationClickType.USER
id = notification.user?.id ?: 0
}
NotificationType.ACTIVITY_REPLY -> {
binding.notificationCover.loadImage(notification.user?.avatar?.large)
binding.notificationBannerImage.loadImage(notification.user?.bannerImage)
clickType = NotificationActivity.Companion.NotificationClickType.USER
id = notification.user?.id ?: 0
}
NotificationType.FOLLOWING -> {
binding.notificationCover.loadImage(notification.user?.avatar?.large)
binding.notificationBannerImage.loadImage(notification.user?.bannerImage)
clickType = NotificationActivity.Companion.NotificationClickType.USER
id = notification.user?.id ?: 0
}
NotificationType.ACTIVITY_MENTION -> {
binding.notificationCover.loadImage(notification.user?.avatar?.large)
binding.notificationBannerImage.loadImage(notification.user?.bannerImage)
clickType = NotificationActivity.Companion.NotificationClickType.USER
id = notification.user?.id ?: 0
}
NotificationType.THREAD_COMMENT_MENTION -> {
binding.notificationCover.loadImage(notification.user?.avatar?.large)
binding.notificationBannerImage.loadImage(notification.user?.bannerImage)
clickType = NotificationActivity.Companion.NotificationClickType.USER
id = notification.user?.id ?: 0
}
NotificationType.THREAD_SUBSCRIBED -> {
binding.notificationCover.loadImage(notification.user?.avatar?.large)
binding.notificationBannerImage.loadImage(notification.user?.bannerImage)
clickType = NotificationActivity.Companion.NotificationClickType.USER
id = notification.user?.id ?: 0
}
NotificationType.THREAD_COMMENT_REPLY -> {
binding.notificationCover.loadImage(notification.user?.avatar?.large)
binding.notificationBannerImage.loadImage(notification.user?.bannerImage)
clickType = NotificationActivity.Companion.NotificationClickType.USER
id = notification.user?.id ?: 0
}
NotificationType.AIRING -> {
binding.notificationCover.loadImage(notification.media?.coverImage?.large)
binding.notificationBannerImage.loadImage(notification.media?.bannerImage)
clickType = NotificationActivity.Companion.NotificationClickType.MEDIA
id = notification.media?.id ?: 0
}
NotificationType.ACTIVITY_LIKE -> {
binding.notificationCover.loadImage(notification.user?.avatar?.large)
binding.notificationBannerImage.loadImage(notification.user?.bannerImage)
clickType = NotificationActivity.Companion.NotificationClickType.USER
id = notification.user?.id ?: 0
}
NotificationType.ACTIVITY_REPLY_LIKE -> {
binding.notificationCover.loadImage(notification.user?.avatar?.large)
binding.notificationBannerImage.loadImage(notification.user?.bannerImage)
clickType = NotificationActivity.Companion.NotificationClickType.USER
id = notification.user?.id ?: 0
}
NotificationType.THREAD_LIKE -> {
binding.notificationCover.loadImage(notification.user?.avatar?.large)
binding.notificationBannerImage.loadImage(notification.user?.bannerImage)
clickType = NotificationActivity.Companion.NotificationClickType.USER
id = notification.user?.id ?: 0
}
NotificationType.THREAD_COMMENT_LIKE -> {
binding.notificationCover.loadImage(notification.user?.avatar?.large)
binding.notificationBannerImage.loadImage(notification.user?.bannerImage)
clickType = NotificationActivity.Companion.NotificationClickType.USER
id = notification.user?.id ?: 0
}
NotificationType.ACTIVITY_REPLY_SUBSCRIBED -> {
binding.notificationCover.loadImage(notification.user?.avatar?.large)
binding.notificationBannerImage.loadImage(notification.user?.bannerImage)
clickType = NotificationActivity.Companion.NotificationClickType.USER
id = notification.user?.id ?: 0
}
NotificationType.RELATED_MEDIA_ADDITION -> {
binding.notificationCover.loadImage(notification.media?.coverImage?.large)
binding.notificationBannerImage.loadImage(notification.media?.bannerImage)
clickType = NotificationActivity.Companion.NotificationClickType.MEDIA
id = notification.media?.id ?: 0
}
NotificationType.MEDIA_DATA_CHANGE -> {
binding.notificationCover.loadImage(notification.media?.coverImage?.large)
binding.notificationBannerImage.loadImage(notification.media?.bannerImage)
clickType = NotificationActivity.Companion.NotificationClickType.MEDIA
id = notification.media?.id ?: 0
}
NotificationType.MEDIA_MERGE -> {
binding.notificationCover.loadImage(notification.media?.coverImage?.large)
binding.notificationBannerImage.loadImage(notification.media?.bannerImage)
clickType = NotificationActivity.Companion.NotificationClickType.MEDIA
id = notification.media?.id ?: 0
}
NotificationType.MEDIA_DELETION -> {
binding.notificationCover.visibility = View.GONE
clickType = NotificationActivity.Companion.NotificationClickType.UNDEFINED
id = 0
}
}
}
}

View file

@ -0,0 +1,148 @@
package ani.dantotsu.profile.activity
import ani.dantotsu.connections.anilist.api.Notification
import ani.dantotsu.connections.anilist.api.NotificationType
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
/*
* ACTIVITY_MESSAGE
A user has sent you message
ACTIVITY_REPLY
A user has replied to your activity
FOLLOWING
A user has followed you
ACTIVITY_MENTION
A user has mentioned you in their activity
THREAD_COMMENT_MENTION
A user has mentioned you in a forum comment
THREAD_SUBSCRIBED
A user has commented in one of your subscribed forum threads
THREAD_COMMENT_REPLY
A user has replied to your forum comment
AIRING
An anime you are currently watching has aired
ACTIVITY_LIKE
A user has liked your activity
ACTIVITY_REPLY_LIKE
A user has liked your activity reply
THREAD_LIKE
A user has liked your forum thread
THREAD_COMMENT_LIKE
A user has liked your forum comment
ACTIVITY_REPLY_SUBSCRIBED
A user has replied to activity you have also replied to
RELATED_MEDIA_ADDITION
A new anime or manga has been added to the site where its related media is on the user's list
MEDIA_DATA_CHANGE
An anime or manga has had a data change that affects how a user may track it in their lists
MEDIA_MERGE
Anime or manga entries on the user's list have been merged into a single entry
MEDIA_DELETION
An anime or manga on the user's list has been deleted from the site
* */
interface NotificationItemBuilder {
companion object {
fun getContent(notification: Notification): String {
val notificationType: NotificationType =
NotificationType.valueOf(notification.notificationType)
return when (notificationType) {
NotificationType.ACTIVITY_MESSAGE -> {
"${notification.user?.name} sent you a message"
}
NotificationType.ACTIVITY_REPLY -> {
"${notification.user?.name} replied to your activity"
}
NotificationType.FOLLOWING -> {
"${notification.user?.name} followed you"
}
NotificationType.ACTIVITY_MENTION -> {
"${notification.user?.name} mentioned you in their activity"
}
NotificationType.THREAD_COMMENT_MENTION -> {
"${notification.user?.name} mentioned you in a forum comment"
}
NotificationType.THREAD_SUBSCRIBED -> {
"${notification.user?.name} commented in one of your subscribed forum threads"
}
NotificationType.THREAD_COMMENT_REPLY -> {
"${notification.user?.name} replied to your forum comment"
}
NotificationType.AIRING -> {
"Episode ${notification.episode} of ${notification.media?.title?.english ?: notification.media?.title?.romaji} has aired"
}
NotificationType.ACTIVITY_LIKE -> {
"${notification.user?.name} liked your activity"
}
NotificationType.ACTIVITY_REPLY_LIKE -> {
"${notification.user?.name} liked your reply"
}
NotificationType.THREAD_LIKE -> {
"${notification.user?.name} liked your forum thread"
}
NotificationType.THREAD_COMMENT_LIKE -> {
"${notification.user?.name} liked your forum comment"
}
NotificationType.ACTIVITY_REPLY_SUBSCRIBED -> {
"${notification.user?.name} replied to activity you have also replied to"
}
NotificationType.RELATED_MEDIA_ADDITION -> {
"${notification.media?.title?.english ?: notification.media?.title?.romaji} has been added to the site"
}
NotificationType.MEDIA_DATA_CHANGE -> {
"${notification.media?.title?.english ?: notification.media?.title?.romaji} has had a data change: ${notification.reason}"
}
NotificationType.MEDIA_MERGE -> {
"${notification.deletedMediaTitles?.joinToString(", ")} have been merged into ${notification.media?.title?.english ?: notification.media?.title?.romaji}"
}
NotificationType.MEDIA_DELETION -> {
"${notification.deletedMediaTitle} has been deleted from the site"
}
}
}
fun getDateTime(time: Int): String {
val date = Date(time * 1000L)
val sdf = SimpleDateFormat("dd/MM/yyyy hh:mm a", Locale.getDefault())
return sdf.format(date)
}
}
}

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="?attr/colorError"/>
<size
android:width="100dp"
android:height="100dp"/>
</shape>

View file

@ -46,7 +46,7 @@
</FrameLayout> </FrameLayout>
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/listRecyclerView" android:id="@+id/notificationList"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="8dp" android:layout_marginStart="8dp"

View file

@ -58,6 +58,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_horizontal|bottom" android:layout_gravity="center_horizontal|bottom"
android:layout_marginBottom="8dp"
android:orientation="vertical"> android:orientation="vertical">
<com.google.android.material.card.MaterialCardView <com.google.android.material.card.MaterialCardView
@ -96,14 +97,14 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center" android:layout_gravity="center"
android:backgroundTint="?attr/colorOnSecondary" android:backgroundTint="?attr/colorSecondaryContainer"
android:enabled="true" android:enabled="true"
android:fontFamily="@font/poppins_bold" android:fontFamily="@font/poppins_bold"
android:text="Follow" android:text="Follow"
android:textColor="@color/bg_opp" android:textColor="@color/bg_opp"
android:textSize="14sp" android:textSize="14sp"
app:cornerRadius="8dp" app:cornerRadius="8dp"
app:strokeColor="?attr/colorOnSecondary" app:strokeColor="?attr/colorSecondaryContainer"
tools:ignore="HardcodedText,SpeakableTextPresentCheck" /> tools:ignore="HardcodedText,SpeakableTextPresentCheck" />
</LinearLayout> </LinearLayout>

View file

@ -135,23 +135,35 @@
</FrameLayout> </FrameLayout>
<com.google.android.material.card.MaterialCardView <FrameLayout
android:id="@+id/homeUserAvatarContainer" android:layout_width="wrap_content"
android:layout_width="52dp" android:layout_height="wrap_content">
android:layout_height="52dp"
android:layout_marginTop="4dp"
android:backgroundTint="@color/nav_bg_inv"
app:cardCornerRadius="26dp">
<com.google.android.material.imageview.ShapeableImageView <com.google.android.material.card.MaterialCardView
android:id="@+id/homeUserAvatar" android:id="@+id/homeUserAvatarContainer"
android:layout_width="52dp" android:layout_width="52dp"
android:layout_height="52dp" android:layout_height="52dp"
android:scaleType="center" android:layout_marginTop="4dp"
app:srcCompat="@drawable/ic_round_settings_24" android:backgroundTint="@color/nav_bg_inv"
tools:ignore="ContentDescription,ImageContrastCheck" /> app:cardCornerRadius="26dp">
</com.google.android.material.card.MaterialCardView> <com.google.android.material.imageview.ShapeableImageView
android:id="@+id/homeUserAvatar"
android:layout_width="52dp"
android:layout_height="52dp"
android:scaleType="center"
app:srcCompat="@drawable/ic_round_settings_24"
tools:ignore="ContentDescription,ImageContrastCheck" />
</com.google.android.material.card.MaterialCardView>
<View
android:id="@+id/homeNotificationDot"
android:layout_width="20dp"
android:layout_height="20dp"
android:background="@drawable/notification_circle"/>
</FrameLayout>
</LinearLayout> </LinearLayout>

View file

@ -3,7 +3,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="180dp" android:layout_height="170dp"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:orientation="horizontal"> android:orientation="horizontal">
@ -39,9 +39,10 @@
<com.google.android.material.imageview.ShapeableImageView <com.google.android.material.imageview.ShapeableImageView
android:id="@+id/notificationCover" android:id="@+id/notificationCover"
android:layout_width="102dp" android:layout_width="96dp"
android:layout_height="154dp" android:layout_height="144dp"
android:layout_gravity="center" android:layout_gravity="center"
android:scaleType="centerCrop"
app:srcCompat="@drawable/ic_round_add_circle_24" app:srcCompat="@drawable/ic_round_add_circle_24"
tools:ignore="ContentDescription,ImageContrastCheck" tools:ignore="ContentDescription,ImageContrastCheck"
tools:tint="@color/transparent" /> tools:tint="@color/transparent" />
@ -63,6 +64,7 @@
tools:ignore="HardcodedText" /> tools:ignore="HardcodedText" />
<TextView <TextView
android:id="@+id/notificationDate"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="end" android:layout_gravity="end"