diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index fc0721be..8590ff4d 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -115,12 +115,12 @@ android:windowSoftInputMode="adjustResize|stateHidden" android:parentActivityName=".MainActivity" /> 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 070146c4..91244488 100644 --- a/app/src/main/java/ani/dantotsu/connections/anilist/AnilistQueries.kt +++ b/app/src/main/java/ani/dantotsu/connections/anilist/AnilistQueries.kt @@ -6,6 +6,7 @@ import ani.dantotsu.checkGenreTime import ani.dantotsu.checkId import ani.dantotsu.connections.anilist.Anilist.authorRoles import ani.dantotsu.connections.anilist.Anilist.executeQuery +import ani.dantotsu.connections.anilist.api.FeedResponse import ani.dantotsu.connections.anilist.api.FuzzyDate import ani.dantotsu.connections.anilist.api.Notification import ani.dantotsu.connections.anilist.api.NotificationResponse @@ -1348,4 +1349,12 @@ Page(page:$page,perPage:50) { } return res } + + suspend fun getFeed(userId: Int?, global: Boolean = false, page: Int = 1): FeedResponse? { + val filter = if (userId != null) "userId:$userId," + else if (global) "isFollowing:false," + else "isFollowing:true," + val res = executeQuery("""{Page(page:$page,perPage:25){activities(${filter}sort:ID_DESC){__typename ... on TextActivity{id userId type replyCount text(asHtml:true)siteUrl isLocked isSubscribed likeCount isLiked isPinned createdAt user{id name bannerImage avatar{medium large}}replies{id userId activityId text(asHtml:true)likeCount isLiked createdAt user{id name bannerImage avatar{medium large}}likes{id name bannerImage avatar{medium large}}}likes{id name bannerImage avatar{medium large}}}... on ListActivity{id userId type replyCount status progress siteUrl isLocked isSubscribed likeCount isLiked isPinned createdAt user{id name bannerImage avatar{medium large}}media{id title{english romaji native userPreferred}bannerImage coverImage{medium large}}replies{id userId activityId text(asHtml:true)likeCount isLiked createdAt user{id name bannerImage avatar{medium large}}likes{id name bannerImage avatar{medium large}}}likes{id name bannerImage avatar{medium large}}}... on MessageActivity{id recipientId messengerId type replyCount message(asHtml:true)isLocked isSubscribed isLiked isPrivate siteUrl createdAt recipient{id name bannerImage avatar{medium large}}messenger{id name bannerImage avatar{medium large}}replies{id userId activityId text(asHtml:true)likeCount isLiked createdAt user{id name bannerImage avatar{medium large}}likes{id name bannerImage avatar{medium large}}}likes{id name bannerImage avatar{medium large}}}}}}""") + return res + } } \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/connections/anilist/AnilistViewModel.kt b/app/src/main/java/ani/dantotsu/connections/anilist/AnilistViewModel.kt index 9fb04d02..f9535263 100644 --- a/app/src/main/java/ani/dantotsu/connections/anilist/AnilistViewModel.kt +++ b/app/src/main/java/ani/dantotsu/connections/anilist/AnilistViewModel.kt @@ -353,6 +353,11 @@ class ProfileViewModel : ViewModel(){ mangaFav.postValue(Anilist.query.userFavMedia(false, id)) animeFav.postValue(Anilist.query.userFavMedia(true, id)) listImages.postValue(Anilist.query.getUserBannerImages(id)) + } + fun refresh() { + mangaFav.postValue(mangaFav.value) + animeFav.postValue(animeFav.value) + listImages.postValue(listImages.value) } } \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/connections/anilist/api/Feed.kt b/app/src/main/java/ani/dantotsu/connections/anilist/api/Feed.kt new file mode 100644 index 00000000..ca31d6f9 --- /dev/null +++ b/app/src/main/java/ani/dantotsu/connections/anilist/api/Feed.kt @@ -0,0 +1,92 @@ +package ani.dantotsu.connections.anilist.api + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class FeedResponse( + @SerialName("data") + val data: Data +) { + @Serializable + data class Data( + @SerialName("Page") + val page: ActivityPage + ) +} + +@Serializable +data class ActivityPage( + @SerialName("activities") + val activities: List +) + +@Serializable +data class Activity( + @SerialName("__typename") + val typename: String, + @SerialName("id") + val id: Int, + @SerialName("recipientId") + val recipientId: Int?, + @SerialName("messengerId") + val messengerId: Int?, + @SerialName("userId") + val userId: Int?, + @SerialName("type") + val type: String, + @SerialName("replyCount") + val replyCount: Int, + @SerialName("status") + val status: String?, + @SerialName("progress") + val progress: String?, + @SerialName("text") + val text: String?, + @SerialName("message") + val message: String?, + @SerialName("siteUrl") + val siteUrl: String?, + @SerialName("isLocked") + val isLocked: Boolean, + @SerialName("isSubscribed") + val isSubscribed: Boolean, + @SerialName("likeCount") + val likeCount: Int?, + @SerialName("isLiked") + val isLiked: Boolean?, + @SerialName("isPinned") + val isPinned: Boolean?, + @SerialName("isPrivate") + val isPrivate: Boolean?, + @SerialName("createdAt") + val createdAt: Int, + @SerialName("user") + val user: User?, + @SerialName("media") + val media: Media?, + @SerialName("replies") + val replies: List?, + @SerialName("likes") + val likes: List?, +) + +@Serializable +data class Reply( + @SerialName("id") + val id: Int, + @SerialName("userId") + val userId: Int, + @SerialName("text") + val text: String, + @SerialName("likeCount") + val likeCount: Int, + @SerialName("isLiked") + val isLiked: Boolean, + @SerialName("createdAt") + val createdAt: Int, + @SerialName("user") + val user: User, + @SerialName("likes") + val likes: List?, +) \ No newline at end of file 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 775f436e..9652c43b 100644 --- a/app/src/main/java/ani/dantotsu/home/HomeFragment.kt +++ b/app/src/main/java/ani/dantotsu/home/HomeFragment.kt @@ -32,6 +32,7 @@ import ani.dantotsu.media.Media import ani.dantotsu.media.MediaAdaptor import ani.dantotsu.media.user.ListActivity import ani.dantotsu.navBarHeight +import ani.dantotsu.profile.ProfileActivity import ani.dantotsu.setSafeOnClickListener import ani.dantotsu.setSlideIn import ani.dantotsu.setSlideUp @@ -79,8 +80,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.homeNotificationDot.visibility = if (Anilist.unreadNotificationCount > 0) View.VISIBLE else 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( @@ -120,6 +121,13 @@ class HomeFragment : Fragment() { "dialog" ) } + binding.homeUserAvatarContainer.setOnLongClickListener { + ContextCompat.startActivity( + requireContext(), Intent(requireContext(), ProfileActivity::class.java) + .putExtra("userId", Anilist.userid), null + ) + false + } binding.homeContainer.updateLayoutParams { bottomMargin = navBarHeight @@ -360,11 +368,12 @@ 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 + 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/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 deleted file mode 100644 index 30b2f685..00000000 --- a/app/src/main/java/ani/dantotsu/profile/ActivityActivity.kt +++ /dev/null @@ -1,48 +0,0 @@ -package ani.dantotsu.profile - -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 ani.dantotsu.R -import ani.dantotsu.databinding.ActivityActivityBinding -import ani.dantotsu.initActivity -import ani.dantotsu.settings.saving.PrefManager -import ani.dantotsu.settings.saving.PrefName -import ani.dantotsu.statusBarHeight -import ani.dantotsu.themes.ThemeManager - - -class ActivityActivity : AppCompatActivity() { - private lateinit var binding: ActivityActivityBinding - - override fun onCreate(savedInstanceState: Bundle?) { - val immersiveMode = PrefManager.getVal(PrefName.ImmersiveMode) - if (immersiveMode) { - requestWindowFeature(Window.FEATURE_NO_TITLE) - } - super.onCreate(savedInstanceState) - ThemeManager(this).applyTheme() - initActivity(this) - binding = ActivityActivityBinding.inflate(layoutInflater) - if (!immersiveMode) { - this.window.statusBarColor = - ContextCompat.getColor(this, R.color.nav_bg_inv) - binding.root.fitsSystemWindows = true - - } else { - binding.root.fitsSystemWindows = false - window.setFlags( - WindowManager.LayoutParams.FLAG_FULLSCREEN, - WindowManager.LayoutParams.FLAG_FULLSCREEN - ) - binding.listTitle.updateLayoutParams { - topMargin = statusBarHeight - } - } - setContentView(binding.root) - } -} \ 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 0c528118..fa30f6a3 100644 --- a/app/src/main/java/ani/dantotsu/profile/FollowActivity.kt +++ b/app/src/main/java/ani/dantotsu/profile/FollowActivity.kt @@ -2,23 +2,18 @@ package ani.dantotsu.profile import android.content.Intent import android.os.Bundle -import android.view.ViewGroup +import android.view.View 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 @@ -36,31 +31,11 @@ class FollowActivity : AppCompatActivity(){ private lateinit var selected: ImageButton override fun onCreate(savedInstanceState: Bundle?) { - val immersiveMode = PrefManager.getVal(PrefName.ImmersiveMode) - if (immersiveMode) { - requestWindowFeature(Window.FEATURE_NO_TITLE) - } super.onCreate(savedInstanceState) ThemeManager(this).applyTheme() initActivity(this) binding = ActivityFollowBinding.inflate(layoutInflater) - - if (!immersiveMode) { - this.window.statusBarColor = - ContextCompat.getColor(this, R.color.nav_bg_inv) - binding.root.fitsSystemWindows = true - - } else { - binding.root.fitsSystemWindows = false - window.setFlags( - WindowManager.LayoutParams.FLAG_FULLSCREEN, - WindowManager.LayoutParams.FLAG_FULLSCREEN - ) - binding.listTitle.updateLayoutParams { - topMargin = statusBarHeight - } - } - + binding.listToolbar.updateLayoutParams { topMargin = statusBarHeight } setContentView(binding.root) val layoutType = PrefManager.getVal(PrefName.FollowerLayout) selected = getSelected(layoutType) @@ -73,7 +48,7 @@ class FollowActivity : AppCompatActivity(){ false ) binding.listRecyclerView.adapter = adapter - + binding.listProgressBar.visibility = View.VISIBLE binding.listBack.setOnClickListener { finish() } val title = intent.getStringExtra("title") @@ -89,6 +64,7 @@ class FollowActivity : AppCompatActivity(){ users = respond withContext(Dispatchers.Main) { fillList() + binding.listProgressBar.visibility = View.GONE } } binding.followerList.setOnClickListener { @@ -112,7 +88,7 @@ class FollowActivity : AppCompatActivity(){ } users?.forEach { user -> if (getLayoutType(selected) == 0) { - adapter.add(FollowerItem(user.id, user.name ?: "Unknown", user.avatar?.medium, user.bannerImage) { onUserClick(it) }) + adapter.add(FollowerItem(user.id, user.name ?: "Unknown", user.avatar?.medium, user.bannerImage ?: user.avatar?.medium ) { onUserClick(it) }) } else { adapter.add(GridFollowerItem(user.id, user.name ?: "Unknown", user.avatar?.medium) { onUserClick(it) }) } diff --git a/app/src/main/java/ani/dantotsu/profile/FollowerItem.kt b/app/src/main/java/ani/dantotsu/profile/FollowerItem.kt index 518e6164..f09c2435 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,9 +25,16 @@ 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) } 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, 2))) + .into(binding.profileBannerImage) } else { binding.profileBannerImage.setImageResource(R.drawable.linear_gradient_bg) } diff --git a/app/src/main/java/ani/dantotsu/profile/ProfileActivity.kt b/app/src/main/java/ani/dantotsu/profile/ProfileActivity.kt index a7afa0f5..b180a833 100644 --- a/app/src/main/java/ani/dantotsu/profile/ProfileActivity.kt +++ b/app/src/main/java/ani/dantotsu/profile/ProfileActivity.kt @@ -22,6 +22,7 @@ import ani.dantotsu.initActivity import ani.dantotsu.loadImage import ani.dantotsu.navBarHeight import ani.dantotsu.others.ImageViewDialog +import ani.dantotsu.profile.activity.ActivityActivity import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.snackString diff --git a/app/src/main/java/ani/dantotsu/profile/ProfileFragment.kt b/app/src/main/java/ani/dantotsu/profile/ProfileFragment.kt index 00135292..afb62d82 100644 --- a/app/src/main/java/ani/dantotsu/profile/ProfileFragment.kt +++ b/app/src/main/java/ani/dantotsu/profile/ProfileFragment.kt @@ -36,6 +36,8 @@ class ProfileFragment() : Fragment() { lateinit var binding: FragmentProfileBinding private lateinit var activity: ProfileActivity private lateinit var user: Query.UserProfile + private val favStaff = arrayListOf() + private val favCharacter = arrayListOf() override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -132,24 +134,27 @@ class ProfileFragment() : Fragment() { binding.profileFavManga ) - val favCharacter = arrayListOf() user.favourites?.characters?.nodes?.forEach { i -> favCharacter.add(Character(i.id, i.name.full, i.image.large, i.image.large, "")) } - if (favCharacter.isEmpty()) { - binding.profileFavCharactersContainer.visibility = View.GONE - } - binding.profileFavCharactersRecycler.adapter = CharacterAdapter(favCharacter) - binding.profileFavCharactersRecycler.layoutManager = LinearLayoutManager( - requireContext(), - LinearLayoutManager.HORIZONTAL, - false - ) - val favStaff = arrayListOf() user.favourites?.staff?.nodes?.forEach { i -> favStaff.add(Author(i.id, i.name.full, i.image.large , "" )) } + + setFavPeople() + } + + override fun onResume() { + super.onResume() + if (this::binding.isInitialized) { + binding.root.requestLayout() + setFavPeople() + model.refresh() + } + } + + private fun setFavPeople() { if (favStaff.isEmpty()) { binding.profileFavStaffContainer.visibility = View.GONE } @@ -159,15 +164,15 @@ class ProfileFragment() : Fragment() { LinearLayoutManager.HORIZONTAL, false ) - - } - - override fun onResume() { - super.onResume() - if (this::binding.isInitialized) { - binding.root.requestLayout() - + if (favCharacter.isEmpty()) { + binding.profileFavCharactersContainer.visibility = View.GONE } + binding.profileFavCharactersRecycler.adapter = CharacterAdapter(favCharacter) + binding.profileFavCharactersRecycler.layoutManager = LinearLayoutManager( + requireContext(), + LinearLayoutManager.HORIZONTAL, + false + ) } private fun convertMarkdownToHtml(markdown: String): String { diff --git a/app/src/main/java/ani/dantotsu/profile/activity/ActivityActivity.kt b/app/src/main/java/ani/dantotsu/profile/activity/ActivityActivity.kt new file mode 100644 index 00000000..a9482820 --- /dev/null +++ b/app/src/main/java/ani/dantotsu/profile/activity/ActivityActivity.kt @@ -0,0 +1,60 @@ +package ani.dantotsu.profile.activity + +import android.annotation.SuppressLint +import android.os.Bundle +import android.view.ViewGroup +import androidx.appcompat.app.AppCompatActivity +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.Activity +import ani.dantotsu.databinding.ActivityFollowBinding +import ani.dantotsu.initActivity +import ani.dantotsu.statusBarHeight +import ani.dantotsu.themes.ThemeManager +import com.xwray.groupie.GroupieAdapter +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +class ActivityActivity : AppCompatActivity() { + private lateinit var binding: ActivityFollowBinding + private var adapter: GroupieAdapter = GroupieAdapter() + private var activityList: 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 = "Activity" + 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() + } + binding.listProgressBar.visibility = ViewGroup.VISIBLE + var userId: Int? = intent.getIntExtra("userId", -1) + if (userId == -1) userId = null + val global = intent.getBooleanExtra("global", false) + + lifecycleScope.launch(Dispatchers.IO) { + val res = Anilist.query.getFeed(userId, global) + + withContext(Dispatchers.Main){ + res?.data?.page?.activities?.let { activities -> + activityList = activities + adapter.update(activityList.map { ActivityItem(it){} }) + } + binding.listProgressBar.visibility = ViewGroup.GONE + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/profile/activity/ActivityItem.kt b/app/src/main/java/ani/dantotsu/profile/activity/ActivityItem.kt index f1ced201..f76ea5d5 100644 --- a/app/src/main/java/ani/dantotsu/profile/activity/ActivityItem.kt +++ b/app/src/main/java/ani/dantotsu/profile/activity/ActivityItem.kt @@ -1,22 +1,64 @@ package ani.dantotsu.profile.activity +import android.annotation.SuppressLint +import android.content.Context import android.view.View +import androidx.core.content.ContextCompat import ani.dantotsu.R -import ani.dantotsu.databinding.ItemNotificationBinding +import ani.dantotsu.connections.anilist.api.Activity +import ani.dantotsu.databinding.ItemActivityBinding +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 ActivityItem( -): BindableItem() { - private lateinit var binding: ItemNotificationBinding - override fun bind(viewBinding: ItemNotificationBinding, position: Int) { + private val activity: Activity, + val clickCallback: (Int) -> Unit +): BindableItem() { + private lateinit var binding: ItemActivityBinding + @SuppressLint("SetTextI18n") + override fun bind(viewBinding: ItemActivityBinding, position: Int) { binding = viewBinding + when (activity.typename) { + "ListActivity" ->{ + binding.activityUserName.text = activity.user?.name + binding.activityUserAvatar.loadImage(activity.user?.avatar?.medium) + binding.activityTime.text = ActivityItemBuilder.getDateTime(activity.createdAt) + val color = if (activity.isLiked == true) + ContextCompat.getColor(binding.root.context, R.color.yt_red) + else + ContextCompat.getColor(binding.root.context, R.color.bg_opp) + binding.activityFavorite.setColorFilter(color) + binding.activityFavoriteCount.text = activity.likeCount.toString() + binding.activityMediaName.text = activity.media?.title?.userPreferred + binding.activityText.text = "${activity.user!!.name} ${activity.status} ${activity.progress ?: ""}" + binding.activityCover.loadImage(activity.media?.coverImage?.large) + val context = binding.root.context + val banner = activity.media?.bannerImage ?: activity.media?.coverImage?.large + if (banner != null) { + if (!(context as android.app.Activity).isDestroyed) { + Glide.with(context as Context) + .load(GlideUrl(banner)) + .diskCacheStrategy(DiskCacheStrategy.ALL).override(400) + .apply(RequestOptions.bitmapTransform(BlurTransformation(2, 2))) + .into(binding.activityBannerImage) + } + } else { + binding.activityBannerImage.setImageResource(R.drawable.linear_gradient_bg) + } + } + } } override fun getLayout(): Int { - return R.layout.item_notification + return R.layout.item_activity } - override fun initializeViewBinding(view: View): ItemNotificationBinding { - return ItemNotificationBinding.bind(view) + override fun initializeViewBinding(view: View): ItemActivityBinding { + return ItemActivityBinding.bind(view) } } \ 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/ActivityItemBuilder.kt similarity index 70% rename from app/src/main/java/ani/dantotsu/profile/activity/NotificationItemBuilder.kt rename to app/src/main/java/ani/dantotsu/profile/activity/ActivityItemBuilder.kt index 84911091..d810258b 100644 --- a/app/src/main/java/ani/dantotsu/profile/activity/NotificationItemBuilder.kt +++ b/app/src/main/java/ani/dantotsu/profile/activity/ActivityItemBuilder.kt @@ -5,63 +5,7 @@ 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 { +class ActivityItemBuilder { companion object { fun getContent(notification: Notification): String { @@ -138,11 +82,33 @@ interface NotificationItemBuilder { } } - 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) - } + 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/notifications/NotificationActivity.kt b/app/src/main/java/ani/dantotsu/profile/activity/NotificationActivity.kt similarity index 64% rename from app/src/main/java/ani/dantotsu/notifications/NotificationActivity.kt rename to app/src/main/java/ani/dantotsu/profile/activity/NotificationActivity.kt index 2c4f5e60..f699b274 100644 --- a/app/src/main/java/ani/dantotsu/notifications/NotificationActivity.kt +++ b/app/src/main/java/ani/dantotsu/profile/activity/NotificationActivity.kt @@ -1,74 +1,58 @@ -package ani.dantotsu.notifications +package ani.dantotsu.profile.activity +import android.annotation.SuppressLint 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.databinding.ActivityFollowBinding 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.Dispatchers import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext class NotificationActivity : AppCompatActivity() { - private lateinit var binding: ActivityNotificationBinding + private lateinit var binding: ActivityFollowBinding private var adapter: GroupieAdapter = GroupieAdapter() private var notificationList: List = emptyList() + @SuppressLint("SetTextI18n") override fun onCreate(savedInstanceState: Bundle?) { - val immersiveMode = PrefManager.getVal(PrefName.ImmersiveMode) - if (immersiveMode) { - requestWindowFeature(Window.FEATURE_NO_TITLE) - } super.onCreate(savedInstanceState) ThemeManager(this).applyTheme() initActivity(this) - binding = ActivityNotificationBinding.inflate(layoutInflater) - if (!immersiveMode) { - this.window.statusBarColor = - ContextCompat.getColor(this, R.color.nav_bg_inv) - binding.root.fitsSystemWindows = true - - } else { - binding.root.fitsSystemWindows = false - window.setFlags( - WindowManager.LayoutParams.FLAG_FULLSCREEN, - WindowManager.LayoutParams.FLAG_FULLSCREEN - ) - binding.listTitle.updateLayoutParams { - topMargin = statusBarHeight - } - } + binding = ActivityFollowBinding.inflate(layoutInflater) setContentView(binding.root) - - binding.notificationList.adapter = adapter - binding.notificationList.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false) - + 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() } - + binding.listProgressBar.visibility = ViewGroup.VISIBLE 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) }) } + withContext(Dispatchers.Main){ + binding.listProgressBar.visibility = ViewGroup.GONE + } } } diff --git a/app/src/main/java/ani/dantotsu/profile/activity/NotificationItem.kt b/app/src/main/java/ani/dantotsu/profile/activity/NotificationItem.kt index 1559be45..b6a2163a 100644 --- a/app/src/main/java/ani/dantotsu/profile/activity/NotificationItem.kt +++ b/app/src/main/java/ani/dantotsu/profile/activity/NotificationItem.kt @@ -1,13 +1,20 @@ package ani.dantotsu.profile.activity +import android.app.Activity +import android.content.Context +import android.util.TypedValue 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.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, @@ -29,107 +36,137 @@ class NotificationItem( return ItemNotificationBinding.bind(view) } + private fun image(user: Boolean = false) { + val context = binding.notificationBannerImage.context + val cover = if (user) notification.user?.bannerImage ?: notification.user?.avatar?.medium else notification.media?.bannerImage ?: notification.media?.coverImage?.large + 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, 2))) + .into(binding.notificationBannerImage) + } else { + binding.notificationBannerImage.setImageResource(R.drawable.linear_gradient_bg) + } + val defaultHeight = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 170f, context.resources.displayMetrics).toInt() + val userHeight = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 90f, context.resources.displayMetrics).toInt() + + if (user) { + binding.notificationCover.visibility = View.GONE + binding.notificationCoverUser.visibility = View.VISIBLE + binding.notificationCoverUserContainer.visibility = View.VISIBLE + binding.notificationCoverUser.loadImage(notification.user?.avatar?.large) + binding.notificationBannerImage.layoutParams.height = userHeight + } else{ + binding.notificationCover.visibility = View.VISIBLE + binding.notificationCoverUser.visibility = View.VISIBLE + binding.notificationCoverUserContainer.visibility = View.GONE + binding.notificationCover.loadImage(notification.media?.coverImage?.large) + binding.notificationBannerImage.layoutParams.height = defaultHeight + } + } + private fun setBinding() { val notificationType: NotificationType = NotificationType.valueOf(notification.notificationType) - binding.notificationText.text = NotificationItemBuilder.getContent(notification) - binding.notificationDate.text = NotificationItemBuilder.getDateTime(notification.createdAt) + binding.notificationText.text = ActivityItemBuilder.getContent(notification) + binding.notificationDate.text = ActivityItemBuilder.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) + image(true) 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) + image(true) 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) + image(true) 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) + image(true) 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) + image(true) 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) + image(true) 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) + image(true) 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) + image() 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) + image(true) 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) + image(true) 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) + image(true) 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) + image(true) 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) + image(true) 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) + image() 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) + image() 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) + image() clickType = NotificationActivity.Companion.NotificationClickType.MEDIA id = notification.media?.id ?: 0 } diff --git a/app/src/main/java/ani/dantotsu/settings/SettingsDialogFragment.kt b/app/src/main/java/ani/dantotsu/settings/SettingsDialogFragment.kt index 5692e047..6638b151 100644 --- a/app/src/main/java/ani/dantotsu/settings/SettingsDialogFragment.kt +++ b/app/src/main/java/ani/dantotsu/settings/SettingsDialogFragment.kt @@ -23,10 +23,8 @@ import ani.dantotsu.home.MangaFragment import ani.dantotsu.home.NoInternet import ani.dantotsu.incognitoNotification import ani.dantotsu.loadImage -import ani.dantotsu.notifications.NotificationActivity +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 @@ -61,6 +59,12 @@ class SettingsDialogFragment : BottomSheetDialogFragment() { val theme = requireContext().theme theme.resolveAttribute(com.google.android.material.R.attr.colorSurface, typedValue, true) window?.navigationBarColor = typedValue.data + val notificationIcon = if (Anilist.unreadNotificationCount > 0) { + R.drawable.ic_round_notifications_active_24 + } else { + R.drawable.ic_round_notifications_none_24 + } + binding.settingsNotification.setImageResource(notificationIcon) if (Anilist.token != null) { binding.settingsLogin.setText(R.string.logout) @@ -79,31 +83,31 @@ 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( 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") - dismiss() - } + binding.settingsNotification.setOnClickListener { startActivity(Intent(activity, NotificationActivity::class.java)) dismiss() diff --git a/app/src/main/res/drawable/notification_circle.xml b/app/src/main/res/drawable/notification_circle.xml index fe33dad2..d864d738 100644 --- a/app/src/main/res/drawable/notification_circle.xml +++ b/app/src/main/res/drawable/notification_circle.xml @@ -1,7 +1,7 @@ - + diff --git a/app/src/main/res/layout/activity_follow.xml b/app/src/main/res/layout/activity_follow.xml index 60df0bc2..59358670 100644 --- a/app/src/main/res/layout/activity_follow.xml +++ b/app/src/main/res/layout/activity_follow.xml @@ -6,15 +6,21 @@ android:layout_height="match_parent" android:orientation="vertical"> - + android:layout_width="match_parent" + android:layout_height="match_parent" + android:gravity="center" + tools:visibility="gone"> + + + diff --git a/app/src/main/res/layout/activity_notification.xml b/app/src/main/res/layout/activity_notification.xml deleted file mode 100644 index 047efd00..00000000 --- a/app/src/main/res/layout/activity_notification.xml +++ /dev/null @@ -1,55 +0,0 @@ - - - - - - - - - - - - - \ 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 56aa08e2..1583039d 100644 --- a/app/src/main/res/layout/activity_profile.xml +++ b/app/src/main/res/layout/activity_profile.xml @@ -67,6 +67,7 @@ android:layout_height="82dp" android:layout_gravity="center" android:backgroundTint="@color/transparent" + app:strokeColor="@color/transparent" app:cardCornerRadius="64dp"> - + 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:layout_marginEnd="16dp"> - + + + + + + + + - - - - - - - - - - + + - + android:layout_height="match_parent" + android:layout_marginEnd="58dp" + android:orientation="vertical"> - - +