diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 7a22ab3d..8408d9ff 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -114,6 +114,16 @@ android:name=".profile.FollowActivity" android:windowSoftInputMode="adjustResize|stateHidden" android:parentActivityName=".MainActivity" /> + + + + @@ -221,6 +231,7 @@ + ? = null var tags: Map>? = null diff --git a/app/src/main/java/ani/dantotsu/connections/anilist/AnilistQueries.kt b/app/src/main/java/ani/dantotsu/connections/anilist/AnilistQueries.kt index 86d2a6a9..070146c4 100644 --- a/app/src/main/java/ani/dantotsu/connections/anilist/AnilistQueries.kt +++ b/app/src/main/java/ani/dantotsu/connections/anilist/AnilistQueries.kt @@ -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 @@ -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 @@ -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 } @@ -65,7 +68,7 @@ class AnilistQueries { media.cameFromContinue = false val query = - """{Media(id:${media.id}){id 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 node{id image{medium}name{userPreferred}}}}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 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}}}""" + """{Media(id:${media.id}){id 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 node{id image{medium}name{userPreferred}}}}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}}}""" runBlocking { val anilist = async { var response = executeQuery(query, force = true, show = true) @@ -136,6 +139,29 @@ class AnilistQueries { } } } + if (fetchedMedia.staff != null) { + media.staff = arrayListOf() + fetchedMedia.staff?.edges?.forEach { i -> + i.node?.apply { + media.staff?.add( + Author( + id = id, + name = i.node?.name?.userPreferred, + image = i.node?.image?.medium, + role = when (i.role.toString()) { + "MAIN" -> currContext()?.getString(R.string.main_role) + ?: "MAIN" + + "SUPPORTING" -> currContext()?.getString(R.string.supporting_role) + ?: "SUPPORTING" + + else -> i.role.toString() + } + ) + ) + } + } + } if (fetchedMedia.relations != null) { media.relations = arrayListOf() fetchedMedia.relations?.edges?.forEach { mediaEdge -> @@ -212,8 +238,10 @@ class AnilistQueries { fetchedMedia.staff?.edges?.find { authorRoles.contains(it.role?.trim()) }?.node?.let { media.anime.author = Author( - it.id.toString(), - it.name?.userPreferred ?: "N/A" + it.id, + it.name?.userPreferred ?: "N/A", + it.image?.medium, + "AUTHOR" ) } @@ -232,8 +260,10 @@ class AnilistQueries { } else if (media.manga != null) { fetchedMedia.staff?.edges?.find { authorRoles.contains(it.role?.trim()) }?.node?.let { media.manga.author = Author( - it.id.toString(), - it.name?.userPreferred ?: "N/A" + it.id, + it.name?.userPreferred ?: "N/A", + it.image?.medium, + "AUTHOR" ) } } @@ -1310,4 +1340,12 @@ Page(page:$page,perPage:50) { default[1] = userBannerImage("MANGA",id) return default } + + suspend fun getNotifications(id: Int): NotificationResponse? { + val res = executeQuery("""{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 + } } \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/connections/anilist/api/Notification.kt b/app/src/main/java/ani/dantotsu/connections/anilist/api/Notification.kt new file mode 100644 index 00000000..073fadc1 --- /dev/null +++ b/app/src/main/java/ani/dantotsu/connections/anilist/api/Notification.kt @@ -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, +) : 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?, + @SerialName("context") + val context: String?, + @SerialName("reason") + val reason: String?, + @SerialName("deletedMediaTitle") + val deletedMediaTitle: String?, + @SerialName("deletedMediaTitles") + val deletedMediaTitles: List?, + @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 diff --git a/app/src/main/java/ani/dantotsu/connections/anilist/api/Staff.kt b/app/src/main/java/ani/dantotsu/connections/anilist/api/Staff.kt index b4742e5b..7b56f693 100644 --- a/app/src/main/java/ani/dantotsu/connections/anilist/api/Staff.kt +++ b/app/src/main/java/ani/dantotsu/connections/anilist/api/Staff.kt @@ -15,7 +15,7 @@ data class Staff( @SerialName("languageV2") var languageV2: String?, // The staff images - // @SerialName("image") var image: StaffImage?, + @SerialName("image") var image: StaffImage?, // A general description of the staff member @SerialName("description") var description: String?, @@ -93,7 +93,14 @@ data class StaffConnection( // The pagination information // @SerialName("pageInfo") var pageInfo: PageInfo?, ) +@Serializable +data class StaffImage( + // The character's image of media at its largest size + @SerialName("large") var large: String?, + // The character's image of media at medium size + @SerialName("medium") var medium: String?, +) : java.io.Serializable @Serializable data class StaffEdge( var role: String?, diff --git a/app/src/main/java/ani/dantotsu/connections/anilist/api/User.kt b/app/src/main/java/ani/dantotsu/connections/anilist/api/User.kt index c74ef85b..b1ec8862 100644 --- a/app/src/main/java/ani/dantotsu/connections/anilist/api/User.kt +++ b/app/src/main/java/ani/dantotsu/connections/anilist/api/User.kt @@ -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?, diff --git a/app/src/main/java/ani/dantotsu/home/AnimeFragment.kt b/app/src/main/java/ani/dantotsu/home/AnimeFragment.kt index f3baeb3f..2e110541 100644 --- a/app/src/main/java/ani/dantotsu/home/AnimeFragment.kt +++ b/app/src/main/java/ani/dantotsu/home/AnimeFragment.kt @@ -283,7 +283,6 @@ class AnimeFragment : Fragment() { binding.root.requestApplyInsets() binding.root.requestLayout() } - super.onResume() } } \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/home/AnimePageAdapter.kt b/app/src/main/java/ani/dantotsu/home/AnimePageAdapter.kt index 295b49b2..fc480ae3 100644 --- a/app/src/main/java/ani/dantotsu/home/AnimePageAdapter.kt +++ b/app/src/main/java/ani/dantotsu/home/AnimePageAdapter.kt @@ -94,6 +94,8 @@ class AnimePageAdapter : RecyclerView.Adapter 0) View.VISIBLE else View.GONE + binding.animeNotificationCount.text = Anilist.unreadNotificationCount.toString() listOf( binding.animePreviousSeason, diff --git a/app/src/main/java/ani/dantotsu/home/HomeFragment.kt b/app/src/main/java/ani/dantotsu/home/HomeFragment.kt index dc03d34b..e0a2dc14 100644 --- a/app/src/main/java/ani/dantotsu/home/HomeFragment.kt +++ b/app/src/main/java/ani/dantotsu/home/HomeFragment.kt @@ -79,6 +79,8 @@ class HomeFragment : Fragment() { if (!(PrefManager.getVal(PrefName.BannerAnimations) as Boolean)) binding.homeUserBg.pause() binding.homeUserBg.loadImage(Anilist.bg) binding.homeUserDataProgressBar.visibility = View.GONE + binding.homeNotificationCount.visibility = if (Anilist.unreadNotificationCount > 0) View.VISIBLE else View.GONE + binding.homeNotificationCount.text = Anilist.unreadNotificationCount.toString() binding.homeAnimeList.setOnClickListener { ContextCompat.startActivity( @@ -305,6 +307,7 @@ class HomeFragment : Fragment() { } } + val array = arrayOf( "AnimeContinue", "AnimeFav", @@ -357,9 +360,12 @@ class HomeFragment : Fragment() { } } } - override fun onResume() { if (!model.loaded) Refresh.activity[1]!!.postValue(true) + if (_binding != null) { + binding.homeNotificationCount.visibility = if (Anilist.unreadNotificationCount > 0) View.VISIBLE else View.GONE + binding.homeNotificationCount.text = Anilist.unreadNotificationCount.toString() + } super.onResume() } } \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/home/MangaPageAdapter.kt b/app/src/main/java/ani/dantotsu/home/MangaPageAdapter.kt index 6cedbb80..1f714260 100644 --- a/app/src/main/java/ani/dantotsu/home/MangaPageAdapter.kt +++ b/app/src/main/java/ani/dantotsu/home/MangaPageAdapter.kt @@ -74,7 +74,8 @@ class MangaPageAdapter : RecyclerView.Adapter 0) View.VISIBLE else View.GONE + binding.mangaNotificationCount.text = Anilist.unreadNotificationCount.toString() binding.mangaSearchBar.hint = "MANGA" binding.mangaSearchBarText.setOnClickListener { ContextCompat.startActivity( diff --git a/app/src/main/java/ani/dantotsu/media/Author.kt b/app/src/main/java/ani/dantotsu/media/Author.kt index 4c99f710..9ab3c169 100644 --- a/app/src/main/java/ani/dantotsu/media/Author.kt +++ b/app/src/main/java/ani/dantotsu/media/Author.kt @@ -3,7 +3,9 @@ package ani.dantotsu.media import java.io.Serializable data class Author( - val id: String, - val name: String, + val id: Int, + val name: String?, + val image: String?, + val role: String?, var yearMedia: MutableMap>? = null ) : Serializable diff --git a/app/src/main/java/ani/dantotsu/media/AuthorAdapter.kt b/app/src/main/java/ani/dantotsu/media/AuthorAdapter.kt new file mode 100644 index 00000000..35195960 --- /dev/null +++ b/app/src/main/java/ani/dantotsu/media/AuthorAdapter.kt @@ -0,0 +1,60 @@ +package ani.dantotsu.media + +import android.annotation.SuppressLint +import android.app.Activity +import android.content.Intent +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.core.app.ActivityOptionsCompat +import androidx.core.content.ContextCompat +import androidx.core.util.Pair +import androidx.core.view.ViewCompat +import androidx.recyclerview.widget.RecyclerView +import ani.dantotsu.databinding.ItemCharacterBinding +import ani.dantotsu.loadImage +import ani.dantotsu.setAnimation +import java.io.Serializable + +class AuthorAdapter( + private val authorList: ArrayList +) : RecyclerView.Adapter() { + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AuthorViewHolder { + val binding = + ItemCharacterBinding.inflate(LayoutInflater.from(parent.context), parent, false) + return AuthorViewHolder(binding) + } + + @SuppressLint("SetTextI18n") + override fun onBindViewHolder(holder:AuthorViewHolder, position: Int) { + val binding = holder.binding + setAnimation(binding.root.context, holder.binding.root) + val author = authorList[position] + binding.itemCompactRelation.text = author.role + binding.itemCompactImage.loadImage(author.image) + binding.itemCompactTitle.text = author.name + } + + override fun getItemCount(): Int = authorList.size + inner class AuthorViewHolder(val binding: ItemCharacterBinding) : + RecyclerView.ViewHolder(binding.root) { + init { + itemView.setOnClickListener { + val author = authorList[bindingAdapterPosition] + ContextCompat.startActivity( + itemView.context, + Intent( + itemView.context, + AuthorActivity::class.java + ).putExtra("author", author as Serializable), + ActivityOptionsCompat.makeSceneTransitionAnimation( + itemView.context as Activity, + Pair.create( + binding.itemCompactImage, + ViewCompat.getTransitionName(binding.itemCompactImage)!! + ), + ).toBundle() + ) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/media/Media.kt b/app/src/main/java/ani/dantotsu/media/Media.kt index ebc4d4f7..ea8273ec 100644 --- a/app/src/main/java/ani/dantotsu/media/Media.kt +++ b/app/src/main/java/ani/dantotsu/media/Media.kt @@ -58,6 +58,7 @@ data class Media( var endDate: FuzzyDate? = null, var characters: ArrayList? = null, + var staff: ArrayList? = null, var prequel: Media? = null, var sequel: Media? = null, var relations: ArrayList? = null, diff --git a/app/src/main/java/ani/dantotsu/media/MediaAdaptor.kt b/app/src/main/java/ani/dantotsu/media/MediaAdaptor.kt index c17a5ec1..7d96e95b 100644 --- a/app/src/main/java/ani/dantotsu/media/MediaAdaptor.kt +++ b/app/src/main/java/ani/dantotsu/media/MediaAdaptor.kt @@ -43,6 +43,7 @@ class MediaAdaptor( private val activity: FragmentActivity, private val matchParent: Boolean = false, private val viewPager: ViewPager2? = null, + private val fav: Boolean = false, ) : RecyclerView.Adapter() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { @@ -128,6 +129,7 @@ class MediaAdaptor( ) b.itemCompactTotal.text = " | ${media.manga.totalChapters ?: "~"}" } + b.itemCompactProgressContainer.visibility = if (fav) View.GONE else View.VISIBLE } } diff --git a/app/src/main/java/ani/dantotsu/media/MediaInfoFragment.kt b/app/src/main/java/ani/dantotsu/media/MediaInfoFragment.kt index cafdb4ff..06fb3aa9 100644 --- a/app/src/main/java/ani/dantotsu/media/MediaInfoFragment.kt +++ b/app/src/main/java/ani/dantotsu/media/MediaInfoFragment.kt @@ -411,23 +411,6 @@ class MediaInfoFragment : Fragment() { parent.addView(bind.root) } - if (!media.characters.isNullOrEmpty() && !offline) { - val bind = ItemTitleRecyclerBinding.inflate( - LayoutInflater.from(context), - parent, - false - ) - bind.itemTitle.setText(R.string.characters) - bind.itemRecycler.adapter = - CharacterAdapter(media.characters!!) - bind.itemRecycler.layoutManager = LinearLayoutManager( - requireContext(), - LinearLayoutManager.HORIZONTAL, - false - ) - parent.addView(bind.root) - } - if (!media.relations.isNullOrEmpty() && !offline) { if (media.sequel != null || media.prequel != null) { val bind = ItemQuelsBinding.inflate( @@ -490,7 +473,38 @@ class MediaInfoFragment : Fragment() { ) parent.addView(bindi.root) } - + if (!media.characters.isNullOrEmpty() && !offline) { + val bind = ItemTitleRecyclerBinding.inflate( + LayoutInflater.from(context), + parent, + false + ) + bind.itemTitle.setText(R.string.characters) + bind.itemRecycler.adapter = + CharacterAdapter(media.characters!!) + bind.itemRecycler.layoutManager = LinearLayoutManager( + requireContext(), + LinearLayoutManager.HORIZONTAL, + false + ) + parent.addView(bind.root) + } + if (!media.staff.isNullOrEmpty() && !offline) { + val bind = ItemTitleRecyclerBinding.inflate( + LayoutInflater.from(context), + parent, + false + ) + bind.itemTitle.setText(R.string.staff) + bind.itemRecycler.adapter = + AuthorAdapter(media.staff!!) + bind.itemRecycler.layoutManager = LinearLayoutManager( + requireContext(), + LinearLayoutManager.HORIZONTAL, + false + ) + parent.addView(bind.root) + } if (!media.recommendations.isNullOrEmpty() && !offline) { val bind = ItemTitleRecyclerBinding.inflate( LayoutInflater.from(context), diff --git a/app/src/main/java/ani/dantotsu/media/SearchActivity.kt b/app/src/main/java/ani/dantotsu/media/SearchActivity.kt index 2962a527..33403915 100644 --- a/app/src/main/java/ani/dantotsu/media/SearchActivity.kt +++ b/app/src/main/java/ani/dantotsu/media/SearchActivity.kt @@ -199,7 +199,9 @@ class SearchActivity : AppCompatActivity() { var state: Parcelable? = null override fun onPause() { - headerAdaptor.addHistory() + if (this::headerAdaptor.isInitialized) { + headerAdaptor.addHistory() + } super.onPause() state = binding.searchRecyclerView.layoutManager?.onSaveInstanceState() } diff --git a/app/src/main/java/ani/dantotsu/media/manga/MangaChapterAdapter.kt b/app/src/main/java/ani/dantotsu/media/manga/MangaChapterAdapter.kt index a2cf40b7..d19aa011 100644 --- a/app/src/main/java/ani/dantotsu/media/manga/MangaChapterAdapter.kt +++ b/app/src/main/java/ani/dantotsu/media/manga/MangaChapterAdapter.kt @@ -369,7 +369,7 @@ class MangaChapterAdapter( } 1L -> "1 day ago" in 2..6 -> "$daysDifference days ago" - else -> SimpleDateFormat("dd MMM yyyy", Locale.ENGLISH).format(targetDate) + else -> SimpleDateFormat("dd MMM yyyy", Locale.getDefault()).format(targetDate) } } diff --git a/app/src/main/java/ani/dantotsu/profile/ActivityActivity.kt b/app/src/main/java/ani/dantotsu/profile/ActivityActivity.kt new file mode 100644 index 00000000..47d2ad67 --- /dev/null +++ b/app/src/main/java/ani/dantotsu/profile/ActivityActivity.kt @@ -0,0 +1,29 @@ +package ani.dantotsu.profile + +import android.annotation.SuppressLint +import android.os.Bundle +import android.view.ViewGroup +import androidx.appcompat.app.AppCompatActivity +import androidx.core.view.updateLayoutParams +import ani.dantotsu.databinding.ActivityFollowBinding +import ani.dantotsu.initActivity +import ani.dantotsu.statusBarHeight +import ani.dantotsu.themes.ThemeManager + +class ActivityActivity : AppCompatActivity() { + private lateinit var binding: ActivityFollowBinding + + @SuppressLint("SetTextI18n") + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + ThemeManager(this).applyTheme() + initActivity(this) + binding = ActivityFollowBinding.inflate(layoutInflater) + setContentView(binding.root) + + binding.listTitle.text = "Activity" + binding.listToolbar.updateLayoutParams { topMargin = statusBarHeight } + binding.followerGrid.visibility = ViewGroup.GONE + binding.followerList.visibility = ViewGroup.GONE + } +} \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/profile/FollowActivity.kt b/app/src/main/java/ani/dantotsu/profile/FollowActivity.kt index 4d05174c..9cb1b612 100644 --- a/app/src/main/java/ani/dantotsu/profile/FollowActivity.kt +++ b/app/src/main/java/ani/dantotsu/profile/FollowActivity.kt @@ -2,23 +2,17 @@ package ani.dantotsu.profile import android.content.Intent import android.os.Bundle -import android.view.ViewGroup import android.view.ViewGroup.MarginLayoutParams -import android.view.Window -import android.view.WindowManager import android.widget.ImageButton import androidx.appcompat.app.AppCompatActivity -import androidx.core.content.ContextCompat import androidx.core.view.updateLayoutParams import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.LinearLayoutManager -import ani.dantotsu.R import ani.dantotsu.connections.anilist.Anilist import ani.dantotsu.connections.anilist.api.User import ani.dantotsu.databinding.ActivityFollowBinding import ani.dantotsu.initActivity -import ani.dantotsu.navBarHeight import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.statusBarHeight @@ -40,24 +34,8 @@ class FollowActivity : AppCompatActivity(){ ThemeManager(this).applyTheme() initActivity(this) binding = ActivityFollowBinding.inflate(layoutInflater) + binding.listToolbar.updateLayoutParams { topMargin = statusBarHeight } setContentView(binding.root) - - if (!PrefManager.getVal(PrefName.ImmersiveMode)) { - this.window.statusBarColor = - ContextCompat.getColor(this, R.color.nav_bg_inv) - binding.root.fitsSystemWindows = true - - } else { - binding.root.fitsSystemWindows = false - requestWindowFeature(Window.FEATURE_NO_TITLE) - window.setFlags( - WindowManager.LayoutParams.FLAG_FULLSCREEN, - WindowManager.LayoutParams.FLAG_FULLSCREEN - ) - binding.listTitle.updateLayoutParams { - topMargin = statusBarHeight - } - } val layoutType = PrefManager.getVal(PrefName.FollowerLayout) selected = getSelected(layoutType) binding.followerGrid.alpha = 0.33f diff --git a/app/src/main/java/ani/dantotsu/profile/FollowerItem.kt b/app/src/main/java/ani/dantotsu/profile/FollowerItem.kt index ca419759..378a1b82 100644 --- a/app/src/main/java/ani/dantotsu/profile/FollowerItem.kt +++ b/app/src/main/java/ani/dantotsu/profile/FollowerItem.kt @@ -1,10 +1,17 @@ package ani.dantotsu.profile +import android.app.Activity +import android.content.Context import android.view.View import ani.dantotsu.R import ani.dantotsu.databinding.ItemFollowerBinding import ani.dantotsu.loadImage +import com.bumptech.glide.Glide +import com.bumptech.glide.load.engine.DiskCacheStrategy +import com.bumptech.glide.load.model.GlideUrl +import com.bumptech.glide.request.RequestOptions import com.xwray.groupie.viewbinding.BindableItem +import jp.wasabeef.glide.transformations.BlurTransformation class FollowerItem( private val id: Int, @@ -18,8 +25,19 @@ class FollowerItem( override fun bind(viewBinding: ItemFollowerBinding, position: Int) { binding = viewBinding binding.profileUserName.text = name + val context = binding.profileBannerImage.context avatar?.let { binding.profileUserAvatar.loadImage(it) } - banner?.let { binding.profileBannerImage.loadImage(it) } + if (banner != null) { + binding.profileBannerImage.loadImage(banner) + if (!(context as Activity).isDestroyed) + Glide.with(context as Context) + .load(GlideUrl(banner)) + .diskCacheStrategy(DiskCacheStrategy.ALL).override(400) + .apply(RequestOptions.bitmapTransform(BlurTransformation(2, 6))) + .into(binding.profileBannerImage) + } else { + binding.profileBannerImage.setImageResource(R.drawable.linear_gradient_bg) + } binding.root.setOnClickListener { clickCallback(id) } } diff --git a/app/src/main/java/ani/dantotsu/profile/ProfileActivity.kt b/app/src/main/java/ani/dantotsu/profile/ProfileActivity.kt index 9006afcd..a7afa0f5 100644 --- a/app/src/main/java/ani/dantotsu/profile/ProfileActivity.kt +++ b/app/src/main/java/ani/dantotsu/profile/ProfileActivity.kt @@ -105,7 +105,15 @@ class ProfileActivity : AppCompatActivity() { } binding.profileProgressBar.visibility = View.GONE binding.profileTopContainer.visibility = View.VISIBLE - + binding.profileActivityButton.setOnClickListener { + ContextCompat.startActivity( + this@ProfileActivity, + Intent(this@ProfileActivity, ActivityActivity::class.java) + .putExtra("userId", user.id) + .putExtra("username", user.name), + null + ) + } binding.profileMenuButton.setOnClickListener { val popup = PopupMenu(this@ProfileActivity, binding.profileMenuButton) popup.menuInflater.inflate(R.menu.menu_profile, popup.menu) @@ -151,9 +159,8 @@ class ProfileActivity : AppCompatActivity() { binding.profileBannerImage.loadImage(user.bannerImage) binding.profileBannerImage.updateLayoutParams { height += statusBarHeight } binding.profileBannerGradient.updateLayoutParams { height += statusBarHeight } - binding.profileMenuButton.updateLayoutParams { - topMargin += statusBarHeight - } + binding.profileMenuButton.updateLayoutParams { topMargin += statusBarHeight } + binding.profileActivityButton.updateLayoutParams { topMargin += statusBarHeight } binding.profileBannerImage.setOnLongClickListener { ImageViewDialog.newInstance( this@ProfileActivity, diff --git a/app/src/main/java/ani/dantotsu/profile/ProfileFragment.kt b/app/src/main/java/ani/dantotsu/profile/ProfileFragment.kt index 50daccab..00135292 100644 --- a/app/src/main/java/ani/dantotsu/profile/ProfileFragment.kt +++ b/app/src/main/java/ani/dantotsu/profile/ProfileFragment.kt @@ -18,6 +18,8 @@ import ani.dantotsu.connections.anilist.ProfileViewModel import ani.dantotsu.connections.anilist.api.Query import ani.dantotsu.databinding.FragmentProfileBinding import ani.dantotsu.loadImage +import ani.dantotsu.media.Author +import ani.dantotsu.media.AuthorAdapter import ani.dantotsu.media.Character import ani.dantotsu.media.CharacterAdapter import ani.dantotsu.media.Media @@ -68,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 @@ -118,7 +121,6 @@ class ProfileFragment() : Fragment() { binding.profileFavAnimeContainer, binding.profileFavAnimeRecyclerView, binding.profileFavAnimeProgressBar, - binding.profileFavAnimeEmpty, binding.profileFavAnime ) @@ -127,7 +129,6 @@ class ProfileFragment() : Fragment() { binding.profileFavMangaContainer, binding.profileFavMangaRecyclerView, binding.profileFavMangaProgressBar, - binding.profileFavMangaEmpty, binding.profileFavManga ) @@ -145,14 +146,14 @@ class ProfileFragment() : Fragment() { false ) - val favStaff = arrayListOf() + val favStaff = arrayListOf() user.favourites?.staff?.nodes?.forEach { i -> - favStaff.add(Character(i.id, i.name.full, i.image.large, i.image.large, "")) + favStaff.add(Author(i.id, i.name.full, i.image.large , "" )) } if (favStaff.isEmpty()) { binding.profileFavStaffContainer.visibility = View.GONE } - binding.profileFavStaffRecycler.adapter = CharacterAdapter(favStaff) + binding.profileFavStaffRecycler.adapter = AuthorAdapter(favStaff) binding.profileFavStaffRecycler.layoutManager = LinearLayoutManager( requireContext(), LinearLayoutManager.HORIZONTAL, @@ -184,21 +185,18 @@ class ProfileFragment() : Fragment() { container: View, recyclerView: RecyclerView, progress: View, - empty: View, title: View ) { container.visibility = View.VISIBLE progress.visibility = View.VISIBLE recyclerView.visibility = View.GONE - empty.visibility = View.GONE title.visibility = View.INVISIBLE mode.observe(viewLifecycleOwner) { recyclerView.visibility = View.GONE - empty.visibility = View.GONE if (it != null) { if (it.isNotEmpty()) { - recyclerView.adapter = MediaAdaptor(0, it, requireActivity()) + recyclerView.adapter = MediaAdaptor(0, it, requireActivity(), fav=true) recyclerView.layoutManager = LinearLayoutManager( requireContext(), LinearLayoutManager.HORIZONTAL, @@ -209,7 +207,7 @@ class ProfileFragment() : Fragment() { LayoutAnimationController(setSlideIn(), 0.25f) } else { - empty.visibility = View.VISIBLE + container.visibility = View.GONE } title.visibility = View.VISIBLE title.startAnimation(setSlideUp()) @@ -218,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(" ", " ") + .replace("&", "&") + .replace("<", "<") + .replace(">", ">") + .replace(""", "\"") + .replace("'", "'") + .replace("
", "")
+            .replace("`", "")
+            .replace("~", "")
+
+        val step2 = step1.replace("(?s)___(.*?)___".toRegex(), "
$1
") + val step3 = step2.replace("(?s)__(.*?)__".toRegex(), "
$1
") + + return """ @@ -236,6 +249,10 @@ class ProfileFragment() : Fragment() { max-width: 100%; height: auto; /* Maintain aspect ratio */ } + video { + max-width: 100%; + height: auto; /* Maintain aspect ratio */ + } a { color: ${textColor.toCssColor()}; } @@ -243,7 +260,7 @@ class ProfileFragment() : Fragment() { - $html + $step3 """.trimIndent() diff --git a/app/src/main/java/ani/dantotsu/profile/activity/ActivityItem.kt b/app/src/main/java/ani/dantotsu/profile/activity/ActivityItem.kt new file mode 100644 index 00000000..f1ced201 --- /dev/null +++ b/app/src/main/java/ani/dantotsu/profile/activity/ActivityItem.kt @@ -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() { + 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) + } +} \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/profile/activity/NotificationActivity.kt b/app/src/main/java/ani/dantotsu/profile/activity/NotificationActivity.kt new file mode 100644 index 00000000..a7ff26dc --- /dev/null +++ b/app/src/main/java/ani/dantotsu/profile/activity/NotificationActivity.kt @@ -0,0 +1,79 @@ +package ani.dantotsu.profile.activity + +import android.annotation.SuppressLint +import android.content.Intent +import android.os.Bundle +import android.view.ViewGroup +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.connections.anilist.Anilist +import ani.dantotsu.connections.anilist.api.Notification +import ani.dantotsu.databinding.ActivityFollowBinding +import ani.dantotsu.initActivity +import ani.dantotsu.media.MediaDetailsActivity +import ani.dantotsu.profile.ProfileActivity +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: ActivityFollowBinding + private var adapter: GroupieAdapter = GroupieAdapter() + private var notificationList: List = emptyList() + + @SuppressLint("SetTextI18n") + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + ThemeManager(this).applyTheme() + initActivity(this) + binding = ActivityFollowBinding.inflate(layoutInflater) + setContentView(binding.root) + binding.listTitle.text = "Notifications" + binding.listToolbar.updateLayoutParams { topMargin = statusBarHeight } + binding.listRecyclerView.adapter = adapter + binding.listRecyclerView.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false) + binding.followerGrid.visibility = ViewGroup.GONE + binding.followerList.visibility = ViewGroup.GONE + 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 + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/profile/activity/NotificationItem.kt b/app/src/main/java/ani/dantotsu/profile/activity/NotificationItem.kt new file mode 100644 index 00000000..31290be3 --- /dev/null +++ b/app/src/main/java/ani/dantotsu/profile/activity/NotificationItem.kt @@ -0,0 +1,184 @@ +package ani.dantotsu.profile.activity + +import android.app.Activity +import android.content.Context +import android.content.res.Resources +import android.util.TypedValue +import android.view.View +import android.view.ViewGroup +import androidx.core.view.updateLayoutParams +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.navBarHeight +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName +import ani.dantotsu.statusBarHeight +import com.bumptech.glide.Glide +import com.bumptech.glide.load.engine.DiskCacheStrategy +import com.bumptech.glide.load.model.GlideUrl +import com.bumptech.glide.request.RequestOptions +import com.xwray.groupie.viewbinding.BindableItem +import jp.wasabeef.glide.transformations.BlurTransformation + +class NotificationItem( + private val notification: Notification, + val clickCallback: (Int, NotificationActivity.Companion.NotificationClickType) -> Unit +): BindableItem() { + 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 image(user: Boolean = false) { + val context = binding.notificationBannerImage.context + val cover = if (user) notification.user?.bannerImage else notification.media?.bannerImage + if (cover != null) { + if (!(context as Activity).isDestroyed) + Glide.with(context as Context) + .load(GlideUrl(cover)) + .diskCacheStrategy(DiskCacheStrategy.ALL).override(400) + .apply(RequestOptions.bitmapTransform(BlurTransformation(2, 6))) + .into(binding.notificationBannerImage) + } else { + binding.notificationBannerImage.setImageResource(R.drawable.linear_gradient_bg) + } + if (user) { + binding.notificationCover.visibility = View.GONE + binding.notificationCoverUserContainer.visibility = View.VISIBLE + binding.notificationCoverUser.loadImage(notification.user?.avatar?.large) + val height = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 80f, context.resources.displayMetrics).toInt() + binding.notificationBannerImage.layoutParams.height = height + binding.notificationBannerGradient.layoutParams.height = height + } else{ + binding.notificationCoverUser.visibility = View.VISIBLE + binding.notificationCoverUserContainer.visibility = View.GONE + binding.notificationCover.loadImage(notification.media?.coverImage?.large) + } + } + + 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) + image(true) + clickType = NotificationActivity.Companion.NotificationClickType.USER + id = notification.user?.id ?: 0 + } + NotificationType.ACTIVITY_REPLY -> { + binding.notificationCover.loadImage(notification.user?.avatar?.large) + image(true) + clickType = NotificationActivity.Companion.NotificationClickType.USER + id = notification.user?.id ?: 0 + } + NotificationType.FOLLOWING -> { + binding.notificationCover.loadImage(notification.user?.avatar?.large) + image(true) + clickType = NotificationActivity.Companion.NotificationClickType.USER + id = notification.user?.id ?: 0 + } + NotificationType.ACTIVITY_MENTION -> { + binding.notificationCover.loadImage(notification.user?.avatar?.large) + image(true) + clickType = NotificationActivity.Companion.NotificationClickType.USER + id = notification.user?.id ?: 0 + } + NotificationType.THREAD_COMMENT_MENTION -> { + binding.notificationCover.loadImage(notification.user?.avatar?.large) + image(true) + clickType = NotificationActivity.Companion.NotificationClickType.USER + id = notification.user?.id ?: 0 + } + NotificationType.THREAD_SUBSCRIBED -> { + binding.notificationCover.loadImage(notification.user?.avatar?.large) + image(true) + clickType = NotificationActivity.Companion.NotificationClickType.USER + id = notification.user?.id ?: 0 + } + NotificationType.THREAD_COMMENT_REPLY -> { + binding.notificationCover.loadImage(notification.user?.avatar?.large) + image(true) + clickType = NotificationActivity.Companion.NotificationClickType.USER + id = notification.user?.id ?: 0 + } + NotificationType.AIRING -> { + binding.notificationCover.loadImage(notification.media?.coverImage?.large) + image() + clickType = NotificationActivity.Companion.NotificationClickType.MEDIA + id = notification.media?.id ?: 0 + } + NotificationType.ACTIVITY_LIKE -> { + image(true) + clickType = NotificationActivity.Companion.NotificationClickType.USER + id = notification.user?.id ?: 0 + } + NotificationType.ACTIVITY_REPLY_LIKE -> { + binding.notificationCover.loadImage(notification.user?.avatar?.large) + image(true) + clickType = NotificationActivity.Companion.NotificationClickType.USER + id = notification.user?.id ?: 0 + } + NotificationType.THREAD_LIKE -> { + binding.notificationCover.loadImage(notification.user?.avatar?.large) + image(true) + clickType = NotificationActivity.Companion.NotificationClickType.USER + id = notification.user?.id ?: 0 + } + NotificationType.THREAD_COMMENT_LIKE -> { + binding.notificationCover.loadImage(notification.user?.avatar?.large) + image(true) + clickType = NotificationActivity.Companion.NotificationClickType.USER + id = notification.user?.id ?: 0 + } + NotificationType.ACTIVITY_REPLY_SUBSCRIBED -> { + binding.notificationCover.loadImage(notification.user?.avatar?.large) + image(true) + clickType = NotificationActivity.Companion.NotificationClickType.USER + id = notification.user?.id ?: 0 + } + NotificationType.RELATED_MEDIA_ADDITION -> { + binding.notificationCover.loadImage(notification.media?.coverImage?.large) + image() + clickType = NotificationActivity.Companion.NotificationClickType.MEDIA + id = notification.media?.id ?: 0 + } + NotificationType.MEDIA_DATA_CHANGE -> { + binding.notificationCover.loadImage(notification.media?.coverImage?.large) + image() + clickType = NotificationActivity.Companion.NotificationClickType.MEDIA + id = notification.media?.id ?: 0 + } + NotificationType.MEDIA_MERGE -> { + binding.notificationCover.loadImage(notification.media?.coverImage?.large) + image() + 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 + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/profile/activity/NotificationItemBuilder.kt b/app/src/main/java/ani/dantotsu/profile/activity/NotificationItemBuilder.kt new file mode 100644 index 00000000..c894f65a --- /dev/null +++ b/app/src/main/java/ani/dantotsu/profile/activity/NotificationItemBuilder.kt @@ -0,0 +1,170 @@ +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(timestamp: Int): String { + + val targetDate = Date(timestamp * 1000L) + + if (targetDate < Date(946684800000L)) { // January 1, 2000 (who want dates before that?) + return "" + } + + val currentDate = Date() + val difference = currentDate.time - targetDate.time + + return when (val daysDifference = difference / (1000 * 60 * 60 * 24)) { + 0L -> { + val hoursDifference = difference / (1000 * 60 * 60) + val minutesDifference = (difference / (1000 * 60)) % 60 + + when { + hoursDifference > 0 -> "$hoursDifference hour${if (hoursDifference > 1) "s" else ""} ago" + minutesDifference > 0 -> "$minutesDifference minute${if (minutesDifference > 1) "s" else ""} ago" + else -> "Just now" + } + } + 1L -> "1 day ago" + in 2..6 -> "$daysDifference days ago" + else -> SimpleDateFormat("dd MMM yyyy", Locale.getDefault()).format(targetDate) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/settings/SettingsDialogFragment.kt b/app/src/main/java/ani/dantotsu/settings/SettingsDialogFragment.kt index dfbe5be6..8a1c9be4 100644 --- a/app/src/main/java/ani/dantotsu/settings/SettingsDialogFragment.kt +++ b/app/src/main/java/ani/dantotsu/settings/SettingsDialogFragment.kt @@ -13,7 +13,6 @@ import ani.dantotsu.MainActivity import ani.dantotsu.profile.ProfileActivity import ani.dantotsu.R import ani.dantotsu.connections.anilist.Anilist -import ani.dantotsu.currContext import ani.dantotsu.databinding.BottomSheetSettingsBinding import ani.dantotsu.download.anime.OfflineAnimeFragment import ani.dantotsu.download.manga.OfflineMangaFragment @@ -24,9 +23,8 @@ import ani.dantotsu.home.MangaFragment import ani.dantotsu.home.NoInternet import ani.dantotsu.incognitoNotification import ani.dantotsu.loadImage +import ani.dantotsu.profile.activity.NotificationActivity import ani.dantotsu.offline.OfflineFragment -import ani.dantotsu.openLinkInBrowser -import ani.dantotsu.others.imagesearch.ImageSearchActivity import ani.dantotsu.setSafeOnClickListener import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefName @@ -79,32 +77,35 @@ class SettingsDialogFragment : BottomSheetDialogFragment() { Anilist.loginIntent(requireActivity()) } } + binding.settingsNotificationCount.visibility = if (Anilist.unreadNotificationCount > 0) View.VISIBLE else View.GONE + binding.settingsNotificationCount.text = Anilist.unreadNotificationCount.toString() binding.settingsUserAvatar.setOnClickListener{ ContextCompat.startActivity( - currContext()!!, Intent(currContext()!!, ProfileActivity::class.java) + requireContext(), Intent(requireContext(), ProfileActivity::class.java) .putExtra("userId", Anilist.userid), null ) } - binding.settingsIncognito.isChecked = - PrefManager.getVal(PrefName.Incognito) + binding.settingsIncognito.isChecked = PrefManager.getVal(PrefName.Incognito) binding.settingsIncognito.setOnCheckedChangeListener { _, isChecked -> PrefManager.setVal(PrefName.Incognito, isChecked) incognitoNotification(requireContext()) } + binding.settingsExtensionSettings.setSafeOnClickListener { startActivity(Intent(activity, ExtensionsActivity::class.java)) dismiss() } + binding.settingsSettings.setSafeOnClickListener { startActivity(Intent(activity, SettingsActivity::class.java)) dismiss() } - binding.settingsAnilistSettings.setOnClickListener { - openLinkInBrowser("https://anilist.co/settings/lists") + + binding.settingsNotification.setOnClickListener { + startActivity(Intent(activity, NotificationActivity::class.java)) dismiss() } - binding.settingsDownloads.isChecked = PrefManager.getVal(PrefName.OfflineMode) binding.settingsDownloads.setOnCheckedChangeListener { _, isChecked -> Timer().schedule(300) { diff --git a/app/src/main/res/drawable/inbox_empty.xml b/app/src/main/res/drawable/inbox_empty.xml new file mode 100644 index 00000000..bb6540aa --- /dev/null +++ b/app/src/main/res/drawable/inbox_empty.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/inbox_filled.xml b/app/src/main/res/drawable/inbox_filled.xml new file mode 100644 index 00000000..08ad4bf1 --- /dev/null +++ b/app/src/main/res/drawable/inbox_filled.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/notification_circle.xml b/app/src/main/res/drawable/notification_circle.xml new file mode 100644 index 00000000..d864d738 --- /dev/null +++ b/app/src/main/res/drawable/notification_circle.xml @@ -0,0 +1,8 @@ + + + + + diff --git a/app/src/main/res/layout/activity_activity.xml b/app/src/main/res/layout/activity_activity.xml new file mode 100644 index 00000000..584c5588 --- /dev/null +++ b/app/src/main/res/layout/activity_activity.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_follow.xml b/app/src/main/res/layout/activity_follow.xml index 7cd44c3f..ce25a262 100644 --- a/app/src/main/res/layout/activity_follow.xml +++ b/app/src/main/res/layout/activity_follow.xml @@ -15,6 +15,7 @@ android:visibility="gone" /> @@ -33,11 +34,11 @@ android:id="@+id/listTitle" android:layout_width="wrap_content" android:layout_height="48dp" - android:layout_marginStart="44dp" android:layout_gravity="center_vertical" - android:gravity="center|start" + android:layout_marginStart="44dp" android:ellipsize="end" android:fontFamily="@font/poppins_bold" + android:gravity="center|start" android:singleLine="true" android:textAppearance="@style/TextAppearance.Widget.AppCompat.Toolbar.Title" android:textColor="?attr/colorOnBackground" @@ -87,12 +88,11 @@ - \ No newline at end of file diff --git a/app/src/main/res/layout/activity_profile.xml b/app/src/main/res/layout/activity_profile.xml index 71011c98..1583039d 100644 --- a/app/src/main/res/layout/activity_profile.xml +++ b/app/src/main/res/layout/activity_profile.xml @@ -58,8 +58,8 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal|bottom" - android:orientation="vertical" - tools:visibility="visible"> + android:layout_marginBottom="8dp" + android:orientation="vertical"> + app:strokeColor="?attr/colorSecondaryContainer" + tools:ignore="HardcodedText,SpeakableTextPresentCheck" /> - - + android:orientation="horizontal"> + + + + + - + android:layout_height="match_parent" + android:background="@drawable/bottom_sheet_background"> + android:layout_height="match_parent" + android:orientation="vertical"> + android:layout_gravity="bottom" + android:orientation="horizontal" + android:padding="16dp"> - - - - - - - - - + android:layout_marginTop="4dp" + android:backgroundTint="@color/nav_bg_inv" + app:cardCornerRadius="26dp"> - + + + + + + + + + + + + + + + + + + + + + + + + + + + - + android:layout_height="match_parent" + android:layout_marginEnd="58dp" + android:orientation="vertical"> - - +