diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 8590ff4d..12e7eccc 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -115,7 +115,7 @@ android:windowSoftInputMode="adjustResize|stateHidden" android:parentActivityName=".MainActivity" /> diff --git a/app/src/main/java/ani/dantotsu/Functions.kt b/app/src/main/java/ani/dantotsu/Functions.kt index 683263a7..67af749c 100644 --- a/app/src/main/java/ani/dantotsu/Functions.kt +++ b/app/src/main/java/ani/dantotsu/Functions.kt @@ -28,6 +28,7 @@ import android.telephony.TelephonyManager import android.text.InputFilter import android.text.Spanned import android.util.AttributeSet +import android.util.Log import android.util.TypedValue import android.view.* import android.view.ViewGroup.LayoutParams.WRAP_CONTENT @@ -129,7 +130,8 @@ var loadIsMAL = false fun logger(e: Any?, print: Boolean = true) { if (print) - println(e) + //println(e) + Log.d("Logger", e.toString()) } 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 91244488..e1b52dba 100644 --- a/app/src/main/java/ani/dantotsu/connections/anilist/AnilistQueries.kt +++ b/app/src/main/java/ani/dantotsu/connections/anilist/AnilistQueries.kt @@ -8,10 +8,10 @@ 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 import ani.dantotsu.connections.anilist.api.Page import ani.dantotsu.connections.anilist.api.Query +import ani.dantotsu.connections.anilist.api.ToggleLike import ani.dantotsu.currContext import ani.dantotsu.isOnline import ani.dantotsu.logError @@ -1265,10 +1265,15 @@ Page(page:$page,perPage:50) { } suspend fun toggleFollow(id: Int): Query.ToggleFollow? { - val response = executeQuery( + return executeQuery( """mutation{ToggleFollow(userId:$id){id, isFollowing, isFollower}}""" ) - return response + } + + suspend fun toggleLike(id: Int, type: String): ToggleLike? { + return executeQuery( + """mutation Like{ToggleLikeV2(id:$id,type:$type){__typename}}""" + ) } suspend fun getUserProfile(id: Int): Query.UserProfileResponse? { @@ -1342,8 +1347,8 @@ Page(page:$page,perPage:50) { 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) + suspend fun getNotifications(id: Int, page: Int = 1): NotificationResponse? { + val res = executeQuery("""{User(id:$id){unreadNotificationCount}Page(page:$page,perPage:$ITEMS_PER_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 } @@ -1354,7 +1359,11 @@ Page(page:$page,perPage:50) { 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}}}}}}""") + val res = executeQuery("""{Page(page:$page,perPage:$ITEMS_PER_PAGE){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 } + + companion object { + const val ITEMS_PER_PAGE = 25 + } } \ 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 index ca31d6f9..b8cc5cc9 100644 --- a/app/src/main/java/ani/dantotsu/connections/anilist/api/Feed.kt +++ b/app/src/main/java/ani/dantotsu/connections/anilist/api/Feed.kt @@ -7,19 +7,19 @@ import kotlinx.serialization.Serializable data class FeedResponse( @SerialName("data") val data: Data -) { +) : java.io.Serializable { @Serializable data class Data( @SerialName("Page") val page: ActivityPage - ) + ) : java.io.Serializable } @Serializable data class ActivityPage( @SerialName("activities") val activities: List -) +) : java.io.Serializable @Serializable data class Activity( @@ -52,9 +52,9 @@ data class Activity( @SerialName("isSubscribed") val isSubscribed: Boolean, @SerialName("likeCount") - val likeCount: Int?, + var likeCount: Int?, @SerialName("isLiked") - val isLiked: Boolean?, + var isLiked: Boolean?, @SerialName("isPinned") val isPinned: Boolean?, @SerialName("isPrivate") @@ -69,7 +69,7 @@ data class Activity( val replies: List?, @SerialName("likes") val likes: List?, -) +) : java.io.Serializable @Serializable data class Reply( @@ -89,4 +89,22 @@ data class Reply( val user: User, @SerialName("likes") val likes: List?, -) \ No newline at end of file +) : java.io.Serializable + +@Serializable +data class ToggleLike( + @SerialName("data") + val data: Data +) : java.io.Serializable { + @Serializable + data class Data( + @SerialName("ToggleLikeV2") + val toggleLike: LikeData + ) : java.io.Serializable +} + +@Serializable +data class LikeData( + @SerialName("__typename") + val typename: String +) : java.io.Serializable \ 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 fa30f6a3..daa984bc 100644 --- a/app/src/main/java/ani/dantotsu/profile/FollowActivity.kt +++ b/app/src/main/java/ani/dantotsu/profile/FollowActivity.kt @@ -14,6 +14,7 @@ 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,6 +37,7 @@ class FollowActivity : AppCompatActivity(){ initActivity(this) binding = ActivityFollowBinding.inflate(layoutInflater) binding.listToolbar.updateLayoutParams { topMargin = statusBarHeight } + binding.listFrameLayout.updateLayoutParams { bottomMargin = navBarHeight } setContentView(binding.root) val layoutType = PrefManager.getVal(PrefName.FollowerLayout) selected = getSelected(layoutType) diff --git a/app/src/main/java/ani/dantotsu/profile/ProfileActivity.kt b/app/src/main/java/ani/dantotsu/profile/ProfileActivity.kt index b180a833..a7f8216d 100644 --- a/app/src/main/java/ani/dantotsu/profile/ProfileActivity.kt +++ b/app/src/main/java/ani/dantotsu/profile/ProfileActivity.kt @@ -22,7 +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.profile.activity.FeedFragment import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.snackString @@ -36,8 +36,8 @@ import nl.joery.animatedbottombar.AnimatedBottomBar class ProfileActivity : AppCompatActivity() { - private lateinit var binding: ActivityProfileBinding - private var selected: Int = 0 + lateinit var binding: ActivityProfileBinding + private var selected: Int = 1 private lateinit var navBar: AnimatedBottomBar @SuppressLint("SetTextI18n") @@ -49,8 +49,10 @@ class ProfileActivity : AppCompatActivity() { setContentView(binding.root) navBar = binding.profileNavBar navBar.updateLayoutParams { bottomMargin = navBarHeight } + val feedTab = navBar.createTab(R.drawable.ic_round_filter_24, "Feed") val profileTab = navBar.createTab(R.drawable.ic_round_person_24, "Profile") val statsTab = navBar.createTab(R.drawable.ic_stats_24, "Stats") + navBar.addTab(feedTab) navBar.addTab(profileTab) navBar.addTab(statsTab) navBar.visibility = View.GONE @@ -70,6 +72,7 @@ class ProfileActivity : AppCompatActivity() { } binding.profileViewPager.adapter = ViewPagerAdapter(supportFragmentManager, lifecycle, user) + binding.profileViewPager.setCurrentItem(selected, false) navBar.visibility = View.VISIBLE navBar.selectTabAt(selected) navBar.setOnTabSelectListener(object : AnimatedBottomBar.OnTabSelectListener { @@ -106,15 +109,6 @@ 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) @@ -161,7 +155,6 @@ class ProfileActivity : AppCompatActivity() { binding.profileBannerImage.updateLayoutParams { height += statusBarHeight } binding.profileBannerGradient.updateLayoutParams { height += statusBarHeight } binding.profileMenuButton.updateLayoutParams { topMargin += statusBarHeight } - binding.profileActivityButton.updateLayoutParams { topMargin += statusBarHeight } binding.profileBannerImage.setOnLongClickListener { ImageViewDialog.newInstance( this@ProfileActivity, @@ -188,10 +181,11 @@ class ProfileActivity : AppCompatActivity() { ) : FragmentStateAdapter(fragmentManager, lifecycle) { - override fun getItemCount(): Int = 2 + override fun getItemCount(): Int = 3 override fun createFragment(position: Int): Fragment = when (position) { - 0 -> ProfileFragment.newInstance(user) - 1 -> StatsFragment.newInstance(user) + 0 -> FeedFragment.newInstance(user.id, false) + 1 -> ProfileFragment.newInstance(user) + 2 -> StatsFragment.newInstance(user) else -> ProfileFragment.newInstance(user) } } diff --git a/app/src/main/java/ani/dantotsu/profile/ProfileFragment.kt b/app/src/main/java/ani/dantotsu/profile/ProfileFragment.kt index afb62d82..340c415f 100644 --- a/app/src/main/java/ani/dantotsu/profile/ProfileFragment.kt +++ b/app/src/main/java/ani/dantotsu/profile/ProfileFragment.kt @@ -32,7 +32,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch -class ProfileFragment() : Fragment() { +class ProfileFragment : Fragment() { lateinit var binding: FragmentProfileBinding private lateinit var activity: ProfileActivity private lateinit var user: Query.UserProfile diff --git a/app/src/main/java/ani/dantotsu/profile/activity/ActivityActivity.kt b/app/src/main/java/ani/dantotsu/profile/activity/ActivityActivity.kt deleted file mode 100644 index a9482820..00000000 --- a/app/src/main/java/ani/dantotsu/profile/activity/ActivityActivity.kt +++ /dev/null @@ -1,60 +0,0 @@ -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 5744c6c6..c69d5950 100644 --- a/app/src/main/java/ani/dantotsu/profile/activity/ActivityItem.kt +++ b/app/src/main/java/ani/dantotsu/profile/activity/ActivityItem.kt @@ -6,21 +6,29 @@ import android.view.View import androidx.core.content.ContextCompat import ani.dantotsu.R import ani.dantotsu.buildMarkwon +import ani.dantotsu.connections.anilist.Anilist import ani.dantotsu.connections.anilist.api.Activity import ani.dantotsu.databinding.ItemActivityBinding import ani.dantotsu.loadImage +import ani.dantotsu.snackString 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 +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext class ActivityItem( private val activity: Activity, - val clickCallback: (Int) -> Unit -): BindableItem() { + val clickCallback: (Int, type: String) -> Unit +) : BindableItem() { private lateinit var binding: ItemActivityBinding + @SuppressLint("SetTextI18n") override fun bind(viewBinding: ItemActivityBinding, position: Int) { binding = viewBinding @@ -28,24 +36,48 @@ class ActivityItem( 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.commentRepliesContainer.visibility = if (activity.replyCount > 0) View.VISIBLE else View.GONE + val likeColor = ContextCompat.getColor(binding.root.context, R.color.yt_red) + val notLikeColor = ContextCompat.getColor(binding.root.context, R.color.bg_opp) + binding.activityLike.setColorFilter(if (activity.isLiked == true) likeColor else notLikeColor) + binding.commentRepliesContainer.visibility = + if (activity.replyCount > 0) View.VISIBLE else View.GONE + binding.activityLikeCount.text = activity.likeCount.toString() + + binding.activityLike.setOnClickListener { + val scope = CoroutineScope(Dispatchers.IO + SupervisorJob()) + scope.launch { + val res = Anilist.query.toggleLike(activity.id, "ACTIVITY") + withContext(Dispatchers.Main) { + if (res != null) { + + if (activity.isLiked == true) { + activity.likeCount = activity.likeCount?.minus(1) + } else { + activity.likeCount = activity.likeCount?.plus(1) + } + binding.activityLikeCount.text = activity.likeCount.toString() + activity.isLiked = !activity.isLiked!! + binding.activityLike.setColorFilter(if (activity.isLiked == true) likeColor else notLikeColor) + + } else { + snackString("Failed to like activity") + } + } + } + } val context = binding.root.context when (activity.typename) { - "ListActivity" ->{ + "ListActivity" -> { binding.activityContent.visibility = View.GONE binding.activityBannerContainer.visibility = View.VISIBLE binding.activityMediaName.text = activity.media?.title?.userPreferred - binding.activityText.text = "${activity.user!!.name} ${activity.status} ${activity.media!!.title!!.userPreferred}" - binding.activityCover.loadImage(activity.media.coverImage?.medium) - val banner = activity.media.bannerImage + binding.activityText.text = + """${activity.user!!.name} ${activity.status} ${activity.progress ?: ""}""" + binding.activityCover.loadImage(activity.media?.coverImage?.medium) + val banner = activity.media?.bannerImage if (banner != null) { if (!(context as android.app.Activity).isDestroyed) { Glide.with(context as Context) @@ -58,6 +90,7 @@ class ActivityItem( binding.activityBannerImage.setImageResource(R.drawable.linear_gradient_bg) } } + "TextActivity" -> { binding.activityBannerContainer.visibility = View.GONE binding.activityContent.visibility = View.VISIBLE diff --git a/app/src/main/java/ani/dantotsu/profile/activity/FeedActivity.kt b/app/src/main/java/ani/dantotsu/profile/activity/FeedActivity.kt new file mode 100644 index 00000000..8513ceb2 --- /dev/null +++ b/app/src/main/java/ani/dantotsu/profile/activity/FeedActivity.kt @@ -0,0 +1,81 @@ +package ani.dantotsu.profile.activity + +import android.os.Bundle +import android.view.ViewGroup +import androidx.appcompat.app.AppCompatActivity +import androidx.core.view.updateLayoutParams +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentManager +import androidx.lifecycle.Lifecycle +import androidx.viewpager2.adapter.FragmentStateAdapter +import ani.dantotsu.R +import ani.dantotsu.databinding.ActivityFeedBinding +import ani.dantotsu.initActivity +import ani.dantotsu.navBarHeight +import ani.dantotsu.statusBarHeight +import ani.dantotsu.themes.ThemeManager +import nl.joery.animatedbottombar.AnimatedBottomBar + +class FeedActivity: AppCompatActivity() { + private lateinit var binding: ActivityFeedBinding + private var selected: Int = 0 + private lateinit var navBar: AnimatedBottomBar + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + ThemeManager(this).applyTheme() + initActivity(this) + binding = ActivityFeedBinding.inflate(layoutInflater) + setContentView(binding.root) + navBar = binding.feedNavBar + navBar.updateLayoutParams { bottomMargin += navBarHeight } + val personalTab = navBar.createTab(R.drawable.ic_round_person_24, "Personal") + val globalTab = navBar.createTab(R.drawable.ic_globe_24, "Global") + navBar.addTab(personalTab) + navBar.addTab(globalTab) + binding.listTitle.text = "Activities" + binding.feedViewPager.updateLayoutParams { + bottomMargin += navBarHeight + topMargin += statusBarHeight + } + binding.listToolbar.updateLayoutParams { topMargin += statusBarHeight } + binding.feedViewPager.adapter = ViewPagerAdapter(supportFragmentManager, lifecycle) + binding.feedViewPager.setCurrentItem(selected, false) + binding.feedViewPager.isUserInputEnabled = false + navBar.selectTabAt(selected) + navBar.setOnTabSelectListener(object : AnimatedBottomBar.OnTabSelectListener { + override fun onTabSelected( + lastIndex: Int, + lastTab: AnimatedBottomBar.Tab?, + newIndex: Int, + newTab: AnimatedBottomBar.Tab + ) { + selected = newIndex + binding.feedViewPager.setCurrentItem(selected, true) + } + }) + binding.listBack.setOnClickListener { + onBackPressed() + } + } + + override fun onResume() { + super.onResume() + navBar.selectTabAt(selected) + } + + + private class ViewPagerAdapter( + fragmentManager: FragmentManager, + lifecycle: Lifecycle + ) : FragmentStateAdapter(fragmentManager, lifecycle) { + override fun getItemCount(): Int = 2 + + override fun createFragment(position: Int): Fragment { + return when (position) { + 0 -> FeedFragment.newInstance(null, false) + else -> FeedFragment.newInstance(null, true) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/profile/activity/FeedFragment.kt b/app/src/main/java/ani/dantotsu/profile/activity/FeedFragment.kt new file mode 100644 index 00000000..f6751d53 --- /dev/null +++ b/app/src/main/java/ani/dantotsu/profile/activity/FeedFragment.kt @@ -0,0 +1,111 @@ +package ani.dantotsu.profile.activity + +import android.annotation.SuppressLint +import android.os.Bundle +import android.view.LayoutInflater +import android.view.MotionEvent +import android.view.View +import android.view.ViewGroup +import androidx.core.view.isVisible +import androidx.fragment.app.Fragment +import androidx.lifecycle.lifecycleScope +import androidx.recyclerview.widget.LinearLayoutManager +import ani.dantotsu.connections.anilist.Anilist +import ani.dantotsu.connections.anilist.AnilistQueries +import ani.dantotsu.connections.anilist.api.Activity +import ani.dantotsu.databinding.FragmentFeedBinding +import ani.dantotsu.logger +import ani.dantotsu.profile.ProfileActivity +import ani.dantotsu.snackString +import com.xwray.groupie.GroupieAdapter +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +class FeedFragment : Fragment() { + private lateinit var binding: FragmentFeedBinding + private var adapter: GroupieAdapter = GroupieAdapter() + private var activityList: List = emptyList() + private lateinit var activity: androidx.activity.ComponentActivity + private var page: Int = 1 + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + binding = FragmentFeedBinding.inflate(inflater, container, false) + return binding.root + } + + @SuppressLint("ClickableViewAccessibility") + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + activity = requireActivity() + binding.listRecyclerView.adapter = adapter + binding.listRecyclerView.layoutManager = + LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false) + binding.listProgressBar.visibility = ViewGroup.VISIBLE + var userId: Int? = arguments?.getInt("userId", -1) + if (userId == -1) userId = null + val global = arguments?.getBoolean("global", false) ?: false + + activity.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 + val scrollView = if (activity is ProfileActivity) { + (activity as ProfileActivity).binding.profileScrollView + } else { + binding.listRecyclerView + } + binding.listRecyclerView.setOnTouchListener { _, event -> + if (event?.action == MotionEvent.ACTION_UP) { + if (adapter.itemCount % AnilistQueries.ITEMS_PER_PAGE != 0) { + snackString("No more activities") + } else if (!scrollView.canScrollVertically(1) && !binding.feedRefresh.isVisible + && binding.listRecyclerView.adapter!!.itemCount != 0 && + (binding.listRecyclerView.layoutManager as LinearLayoutManager).findLastVisibleItemPosition() == (binding.listRecyclerView.adapter!!.itemCount - 1) + ) { + page++ + binding.feedRefresh.visibility = ViewGroup.VISIBLE + activity.lifecycleScope.launch(Dispatchers.IO) { + val res = Anilist.query.getFeed(userId, global, page) + withContext(Dispatchers.Main) { + res?.data?.page?.activities?.let { activities -> + activityList += activities + adapter.addAll(activities.map { ActivityItem(it) { _, _ -> } }) + } + binding.feedRefresh.visibility = ViewGroup.GONE + } + } + } + } + false + } + } + } + } + + override fun onResume() { + super.onResume() + if (this::binding.isInitialized) { + binding.root.requestLayout() + } + } + + companion object { + fun newInstance(userId: Int?, global: Boolean): FeedFragment { + val fragment = FeedFragment() + val args = Bundle() + args.putInt("userId", userId ?: -1) + args.putBoolean("global", global) + fragment.arguments = args + return fragment + } + } +} \ 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 index f699b274..881c9975 100644 --- a/app/src/main/java/ani/dantotsu/profile/activity/NotificationActivity.kt +++ b/app/src/main/java/ani/dantotsu/profile/activity/NotificationActivity.kt @@ -3,18 +3,23 @@ package ani.dantotsu.profile.activity import android.annotation.SuppressLint import android.content.Intent import android.os.Bundle +import android.view.MotionEvent import android.view.ViewGroup import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat +import androidx.core.view.isVisible 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.AnilistQueries import ani.dantotsu.connections.anilist.api.Notification import ani.dantotsu.databinding.ActivityFollowBinding import ani.dantotsu.initActivity import ani.dantotsu.media.MediaDetailsActivity +import ani.dantotsu.navBarHeight import ani.dantotsu.profile.ProfileActivity +import ani.dantotsu.snackString import ani.dantotsu.statusBarHeight import ani.dantotsu.themes.ThemeManager import com.xwray.groupie.GroupieAdapter @@ -26,8 +31,9 @@ class NotificationActivity : AppCompatActivity() { private lateinit var binding: ActivityFollowBinding private var adapter: GroupieAdapter = GroupieAdapter() private var notificationList: List = emptyList() + private var page: Int = 1 - @SuppressLint("SetTextI18n") + @SuppressLint("SetTextI18n", "ClickableViewAccessibility") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) ThemeManager(this).applyTheme() @@ -36,6 +42,7 @@ class NotificationActivity : AppCompatActivity() { setContentView(binding.root) binding.listTitle.text = "Notifications" binding.listToolbar.updateLayoutParams { topMargin = statusBarHeight } + binding.listFrameLayout.updateLayoutParams { bottomMargin = navBarHeight } binding.listRecyclerView.adapter = adapter binding.listRecyclerView.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false) binding.followerGrid.visibility = ViewGroup.GONE @@ -52,6 +59,30 @@ class NotificationActivity : AppCompatActivity() { } withContext(Dispatchers.Main){ binding.listProgressBar.visibility = ViewGroup.GONE + binding.listRecyclerView.setOnTouchListener { _, event -> + if (event?.action == MotionEvent.ACTION_UP) { + if (adapter.itemCount % AnilistQueries.ITEMS_PER_PAGE != 0) { + snackString("No more notifications") + } else if (!binding.listRecyclerView.canScrollVertically(1) && !binding.followRefresh.isVisible + && binding.listRecyclerView.adapter!!.itemCount != 0 && + (binding.listRecyclerView.layoutManager as LinearLayoutManager).findLastVisibleItemPosition() == (binding.listRecyclerView.adapter!!.itemCount - 1) + ) { + page++ + binding.followRefresh.visibility = ViewGroup.VISIBLE + lifecycleScope.launch(Dispatchers.IO) { + val res = Anilist.query.getNotifications(Anilist.userid?:0, page) + withContext(Dispatchers.Main) { + res?.data?.page?.notifications?.let { notifications -> + notificationList += notifications + adapter.addAll(notifications.map { NotificationItem(it, ::onNotificationClick) }) + } + binding.followRefresh.visibility = ViewGroup.GONE + } + } + } + } + false + } } } } diff --git a/app/src/main/java/ani/dantotsu/settings/SettingsDialogFragment.kt b/app/src/main/java/ani/dantotsu/settings/SettingsDialogFragment.kt index 6638b151..b2aa43e6 100644 --- a/app/src/main/java/ani/dantotsu/settings/SettingsDialogFragment.kt +++ b/app/src/main/java/ani/dantotsu/settings/SettingsDialogFragment.kt @@ -25,6 +25,7 @@ import ani.dantotsu.incognitoNotification import ani.dantotsu.loadImage import ani.dantotsu.profile.activity.NotificationActivity import ani.dantotsu.offline.OfflineFragment +import ani.dantotsu.profile.activity.FeedActivity import ani.dantotsu.setSafeOnClickListener import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefName @@ -108,6 +109,11 @@ class SettingsDialogFragment : BottomSheetDialogFragment() { dismiss() } + binding.settingsActivity.setSafeOnClickListener { + startActivity(Intent(activity, FeedActivity::class.java)) + dismiss() + } + binding.settingsNotification.setOnClickListener { startActivity(Intent(activity, NotificationActivity::class.java)) dismiss() diff --git a/app/src/main/res/drawable/ic_globe_24.xml b/app/src/main/res/drawable/ic_globe_24.xml new file mode 100644 index 00000000..ff6e71b0 --- /dev/null +++ b/app/src/main/res/drawable/ic_globe_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/layout/activity_activity.xml b/app/src/main/res/layout/activity_feed.xml similarity index 50% rename from app/src/main/res/layout/activity_activity.xml rename to app/src/main/res/layout/activity_feed.xml index 584c5588..295a8a10 100644 --- a/app/src/main/res/layout/activity_activity.xml +++ b/app/src/main/res/layout/activity_feed.xml @@ -1,19 +1,13 @@ - + - - @@ -38,18 +32,38 @@ android:fontFamily="@font/poppins_bold" android:gravity="center|start" android:singleLine="true" - android:text="Activity" android:textAppearance="@style/TextAppearance.Widget.AppCompat.Toolbar.Title" android:textColor="?attr/colorOnBackground" android:textSize="18sp" - tools:ignore="HardcodedText" /> + tools:text="Activities" /> + - - \ No newline at end of file + android:layout_height="match_parent" + android:layout_marginBottom="72dp" + android:layout_marginTop="48dp" + tools:ignore="SpeakableTextPresentCheck" /> + + + + \ 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 59358670..9dfe9846 100644 --- a/app/src/main/res/layout/activity_follow.xml +++ b/app/src/main/res/layout/activity_follow.xml @@ -93,11 +93,26 @@ - + android:layout_height="wrap_content"> + + + + + \ 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 1583039d..409e6860 100644 --- a/app/src/main/res/layout/activity_profile.xml +++ b/app/src/main/res/layout/activity_profile.xml @@ -20,6 +20,7 @@ - - - + android:layout_height="match_parent" + android:orientation="vertical"> - \ No newline at end of file + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_activity.xml b/app/src/main/res/layout/item_activity.xml index 361df9d2..a21d7e05 100644 --- a/app/src/main/res/layout/item_activity.xml +++ b/app/src/main/res/layout/item_activity.xml @@ -70,14 +70,14 @@ android:orientation="vertical">