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 b76c9530..86d2a6a9 100644 --- a/app/src/main/java/ani/dantotsu/connections/anilist/AnilistQueries.kt +++ b/app/src/main/java/ani/dantotsu/connections/anilist/AnilistQueries.kt @@ -1278,12 +1278,19 @@ Page(page:$page,perPage:50) { } return responseArray } + private fun userFavMediaQuery(anime: Boolean, page: Int, id: Int): String { return """User(id:${id}){id favourites{${if (anime) "anime" else "manga"}(page:$page){pageInfo{hasNextPage}edges{favouriteOrder node{id idMal isAdult mediaListEntry{ progress private score(format:POINT_100) status } chapters isFavourite format episodes nextAiringEpisode{episode}meanScore isFavourite format startDate{year month day} title{english romaji userPreferred}type status(version:2)bannerImage coverImage{large}}}}}}""" } + suspend fun userFollowing(id: Int): Query.Following?{ - return executeQuery("""{Following:Page {following(userId:${id},sort:[USERNAME]){id name avatar{large medium}bannerImage}}}""", force = true) + return executeQuery("""{Page {following(userId:${id},sort:[USERNAME]){id name avatar{large medium}bannerImage}}}""", force = true) } + + suspend fun userFollowers(id: Int): Query.Follower?{ + return executeQuery("""{Page {followers(userId:${id},sort:[USERNAME]){id name avatar{large medium}bannerImage}}}""", force = true) + } + private suspend fun userBannerImage(type: String,id: Int?): String? { val response = executeQuery("""{ MediaListCollection(userId: ${id}, type: $type, chunk:1,perChunk:25, sort: [SCORE_DESC,UPDATED_TIME_DESC]) { lists { entries{ media { id bannerImage } } } } } """) diff --git a/app/src/main/java/ani/dantotsu/connections/anilist/api/Data.kt b/app/src/main/java/ani/dantotsu/connections/anilist/api/Data.kt index 6429bdce..fc074559 100644 --- a/app/src/main/java/ani/dantotsu/connections/anilist/api/Data.kt +++ b/app/src/main/java/ani/dantotsu/connections/anilist/api/Data.kt @@ -143,73 +143,97 @@ class Query { data class ToggleFollow( @SerialName("data") val data: Data? - ) { + ) : java.io.Serializable { @Serializable data class Data( @SerialName("ToggleFollow") val toggleFollow: FollowData - - ) + ) : java.io.Serializable } @Serializable data class GenreCollection( @SerialName("data") val data: Data - ) { + ) : java.io.Serializable { @Serializable data class Data( @SerialName("GenreCollection") val genreCollection: List? - ) + ) : java.io.Serializable } @Serializable data class MediaTagCollection( @SerialName("data") val data: Data - ) { + ) : java.io.Serializable { @Serializable data class Data( @SerialName("MediaTagCollection") val mediaTagCollection: List? - ) + ) : java.io.Serializable } @Serializable data class User( @SerialName("data") val data: Data - ) { + ) : java.io.Serializable { @Serializable data class Data( @SerialName("User") val user: ani.dantotsu.connections.anilist.api.User? - ) + ) : java.io.Serializable } @Serializable data class UserProfileResponse( @SerialName("data") val data: Data - ) { + ) : java.io.Serializable { @Serializable data class Data( @SerialName("user") val user: UserProfile? - ) + ) : java.io.Serializable } @Serializable data class Following( @SerialName("data") val data: Data - ) { + ) : java.io.Serializable { @Serializable data class Data( - @SerialName("following") - val following: ani.dantotsu.connections.anilist.api.User? - ) + @SerialName("Page") + val page: FollowingPage? + ) : java.io.Serializable } + + @Serializable + data class Follower( + @SerialName("data") + val data: Data + ) : java.io.Serializable { + @Serializable + data class Data( + @SerialName("Page") + val page: FollowerPage? + ) : java.io.Serializable + } + + @Serializable + data class FollowerPage( + @SerialName("followers") + val followers: List? + ) : java.io.Serializable + + @Serializable + data class FollowingPage( + @SerialName("following") + val following: List? + ) : java.io.Serializable + @Serializable data class UserProfile( @SerialName("id") @@ -342,7 +366,7 @@ class Query { val mediaListOptions: MediaListOptions, @SerialName("statistics") val statistics: StatisticsTypes - ) + ) : java.io.Serializable @Serializable data class StatisticsTypes( @@ -350,7 +374,7 @@ class Query { val anime: Statistics, @SerialName("manga") val manga: Statistics - ) + ) : java.io.Serializable @Serializable data class Statistics( @@ -392,7 +416,7 @@ class Query { val staff: List, @SerialName("studios") val studios: List - ) + ) : java.io.Serializable @Serializable data class StatisticsFormat( @@ -408,7 +432,7 @@ class Query { val mediaIds: List, @SerialName("format") val format: String - ) + ) : java.io.Serializable @Serializable data class StatisticsStatus( @@ -424,7 +448,7 @@ class Query { val mediaIds: List, @SerialName("status") val status: String - ) + ) : java.io.Serializable @Serializable data class StatisticsScore( @@ -440,7 +464,7 @@ class Query { val mediaIds: List, @SerialName("score") val score: Int - ) + ) : java.io.Serializable @Serializable data class StatisticsLength( @@ -456,7 +480,7 @@ class Query { val mediaIds: List, @SerialName("length") val length: String? //can be null for manga - ) + ) : java.io.Serializable @Serializable data class StatisticsReleaseYear( @@ -472,7 +496,7 @@ class Query { val mediaIds: List, @SerialName("releaseYear") val releaseYear: Int - ) + ) : java.io.Serializable @Serializable data class StatisticsStartYear( @@ -488,7 +512,7 @@ class Query { val mediaIds: List, @SerialName("startYear") val startYear: Int - ) + ) : java.io.Serializable @Serializable data class StatisticsGenre( @@ -504,7 +528,7 @@ class Query { val mediaIds: List, @SerialName("genre") val genre: String - ) + ) : java.io.Serializable @Serializable data class StatisticsTag( @@ -520,7 +544,7 @@ class Query { val mediaIds: List, @SerialName("tag") val tag: Tag - ) + ) : java.io.Serializable @Serializable data class Tag( @@ -528,7 +552,7 @@ class Query { val id: Int, @SerialName("name") val name: String - ) + ) : java.io.Serializable @Serializable data class StatisticsCountry( @@ -544,7 +568,7 @@ class Query { val mediaIds: List, @SerialName("country") val country: String - ) + ) : java.io.Serializable @Serializable data class StatisticsVoiceActor( @@ -562,7 +586,7 @@ class Query { val voiceActor: VoiceActor, @SerialName("characterIds") val characterIds: List - ) + ) : java.io.Serializable @Serializable data class VoiceActor( @@ -570,7 +594,7 @@ class Query { val id: Int, @SerialName("name") val name: StaffName - ) + ) : java.io.Serializable @Serializable data class StaffName( @@ -588,7 +612,7 @@ class Query { val alternative: List?, @SerialName("userPreferred") val userPreferred: String? - ) + ) : java.io.Serializable @Serializable data class StatisticsStaff( @@ -604,7 +628,7 @@ class Query { val mediaIds: List, @SerialName("staff") val staff: VoiceActor - ) + ) : java.io.Serializable @Serializable data class StatisticsStudio( @@ -620,7 +644,7 @@ class Query { val mediaIds: List, @SerialName("studio") val studio: StatStudio - ) + ) : java.io.Serializable @Serializable data class StatStudio( @@ -630,7 +654,7 @@ class Query { val name: String, @SerialName("isAnimationStudio") val isAnimationStudio: Boolean - ) + ) : java.io.Serializable } diff --git a/app/src/main/java/ani/dantotsu/profile/FollowActivity.kt b/app/src/main/java/ani/dantotsu/profile/FollowActivity.kt index d61ac864..30d641b3 100644 --- a/app/src/main/java/ani/dantotsu/profile/FollowActivity.kt +++ b/app/src/main/java/ani/dantotsu/profile/FollowActivity.kt @@ -1,12 +1,24 @@ package ani.dantotsu.profile +import android.content.Intent import android.os.Bundle +import android.view.ViewGroup.MarginLayoutParams +import android.widget.ImageButton import androidx.appcompat.app.AppCompatActivity +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.themes.ThemeManager +import com.xwray.groupie.GroupieAdapter import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -14,19 +26,98 @@ import kotlinx.coroutines.withContext class FollowActivity : AppCompatActivity(){ private lateinit var binding: ActivityFollowBinding + val adapter = GroupieAdapter() + var users: List? = null + private lateinit var selected: ImageButton + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) ThemeManager(this).applyTheme() initActivity(this) binding = ActivityFollowBinding.inflate(layoutInflater) setContentView(binding.root) - binding.listTitle.text = intent.getStringExtra("title") + + val layoutType = PrefManager.getVal(PrefName.FollowerLayout) + selected = getSelected(layoutType) + binding.followerGrid.alpha = 0.33f + binding.followerList.alpha = 0.33f + selected(selected) + binding.root.updateLayoutParams { topMargin += navBarHeight } + binding.listRecyclerView.layoutManager = LinearLayoutManager( + this, + LinearLayoutManager.VERTICAL, + false + ) + binding.listRecyclerView.adapter = adapter + + binding.listBack.setOnClickListener { finish() } + + val title = intent.getStringExtra("title") + binding.listTitle.text = title lifecycleScope.launch(Dispatchers.IO) { - val respond = Anilist.query.userFollowing(intent.getIntExtra("userId", 0)) - val user = respond?.data?.following + val respond = when (title) { + "Following" -> Anilist.query.userFollowing(intent.getIntExtra("userId", 0))?.data?.page?.following + "Followers" -> Anilist.query.userFollowers(intent.getIntExtra("userId", 0))?.data?.page?.followers + else -> null + } + users = respond withContext(Dispatchers.Main) { - user?.id + fillList() + } + } + binding.followerList.setOnClickListener { + selected(it as ImageButton) + PrefManager.setVal(PrefName.FollowerLayout, 0) + fillList() + } + binding.followerGrid.setOnClickListener { + selected(it as ImageButton) + PrefManager.setVal(PrefName.FollowerLayout, 1) + fillList() + } + } + + private fun fillList() { + adapter.clear() + binding.listRecyclerView.layoutManager = when (getLayoutType(selected)) { + 0 -> LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false) + 1 -> GridLayoutManager(this, 3, GridLayoutManager.VERTICAL, false) + else -> LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false) + } + users?.forEach { user -> + if (getLayoutType(selected) == 0) { + adapter.add(FollowerItem(user.id, user.name ?: "Unknown", user.avatar?.medium, user.bannerImage) { onUserClick(it) }) + } else { + adapter.add(GridFollowerItem(user.id, user.name ?: "Unknown", user.avatar?.medium) { onUserClick(it) }) } } } + + fun selected(it: ImageButton) { + selected.alpha = 0.33f + selected = it + selected.alpha = 1f + } + + private fun getSelected(pos: Int): ImageButton { + return when (pos) { + 0 -> binding.followerList + 1 -> binding.followerGrid + else -> binding.followerList + } + } + + private fun getLayoutType(it: ImageButton): Int { + return when (it) { + binding.followerList -> 0 + binding.followerGrid -> 1 + else -> 0 + } + } + + private fun onUserClick(id: Int) { + val intent = Intent(this, ProfileActivity::class.java) + intent.putExtra("userId", id) + startActivity(intent) + } } \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/profile/FollowerItem.kt b/app/src/main/java/ani/dantotsu/profile/FollowerItem.kt new file mode 100644 index 00000000..ca419759 --- /dev/null +++ b/app/src/main/java/ani/dantotsu/profile/FollowerItem.kt @@ -0,0 +1,33 @@ +package ani.dantotsu.profile + +import android.view.View +import ani.dantotsu.R +import ani.dantotsu.databinding.ItemFollowerBinding +import ani.dantotsu.loadImage +import com.xwray.groupie.viewbinding.BindableItem + +class FollowerItem( + private val id: Int, + private val name: String, + private val avatar: String?, + private val banner: String?, + val clickCallback: (Int) -> Unit +): BindableItem() { + private lateinit var binding: ItemFollowerBinding + + override fun bind(viewBinding: ItemFollowerBinding, position: Int) { + binding = viewBinding + binding.profileUserName.text = name + avatar?.let { binding.profileUserAvatar.loadImage(it) } + banner?.let { binding.profileBannerImage.loadImage(it) } + binding.root.setOnClickListener { clickCallback(id) } + } + + override fun getLayout(): Int { + return R.layout.item_follower + } + + override fun initializeViewBinding(view: View): ItemFollowerBinding { + return ItemFollowerBinding.bind(view) + } +} \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/profile/GridFollowerItem.kt b/app/src/main/java/ani/dantotsu/profile/GridFollowerItem.kt new file mode 100644 index 00000000..f6a61fa3 --- /dev/null +++ b/app/src/main/java/ani/dantotsu/profile/GridFollowerItem.kt @@ -0,0 +1,31 @@ +package ani.dantotsu.profile + +import android.view.View +import ani.dantotsu.R +import ani.dantotsu.databinding.ItemFollowerGridBinding +import ani.dantotsu.loadImage +import com.xwray.groupie.viewbinding.BindableItem + +class GridFollowerItem ( + private val id: Int, + private val name: String, + private val avatar: String?, + val clickCallback: (Int) -> Unit +): BindableItem() { + private lateinit var binding: ItemFollowerGridBinding + + override fun bind(viewBinding: ItemFollowerGridBinding, position: Int) { + binding = viewBinding + binding.profileUserName.text = name + avatar?.let { binding.profileUserAvatar.loadImage(it) } + binding.root.setOnClickListener { clickCallback(id) } + } + + override fun getLayout(): Int { + return R.layout.item_follower_grid + } + + override fun initializeViewBinding(view: View): ItemFollowerGridBinding { + return ItemFollowerGridBinding.bind(view) + } +} \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/profile/ProfileActivity.kt b/app/src/main/java/ani/dantotsu/profile/ProfileActivity.kt index 71b455df..9006afcd 100644 --- a/app/src/main/java/ani/dantotsu/profile/ProfileActivity.kt +++ b/app/src/main/java/ani/dantotsu/profile/ProfileActivity.kt @@ -5,6 +5,7 @@ import android.content.Intent import android.os.Bundle import android.view.View import android.view.ViewGroup +import android.widget.PopupMenu import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat import androidx.core.view.updateLayoutParams @@ -82,8 +83,10 @@ class ProfileActivity : AppCompatActivity() { } }) val userLevel = intent.getStringExtra("username") ?: "" - binding.followButton.visibility = if (user.id == Anilist.userid || Anilist.userid == null) View.GONE else View.VISIBLE - binding.followButton.text = if (user.isFollowing) "Unfollow" else if(user.isFollower) "Follows you" else "Follow" + binding.followButton.visibility = + if (user.id == Anilist.userid || Anilist.userid == null) View.GONE else View.VISIBLE + binding.followButton.text = + if (user.isFollowing) "Unfollow" else if (user.isFollower) "Follows you" else "Follow" if (user.isFollowing && user.isFollower) binding.followButton.text = "Mutual" binding.followButton.setOnClickListener { lifecycleScope.launch(Dispatchers.IO) { @@ -92,8 +95,10 @@ class ProfileActivity : AppCompatActivity() { withContext(Dispatchers.Main) { snackString("Success") user.isFollowing = res.data.toggleFollow.isFollowing - binding.followButton.text = if (user.isFollowing) "Unfollow" else if(user.isFollower) "Follows you" else "Follow" - if (user.isFollowing && user.isFollower) binding.followButton.text = "Mutual" + binding.followButton.text = + if (user.isFollowing) "Unfollow" else if (user.isFollower) "Follows you" else "Follow" + if (user.isFollowing && user.isFollower) binding.followButton.text = + "Mutual" } } } @@ -101,12 +106,35 @@ class ProfileActivity : AppCompatActivity() { binding.profileProgressBar.visibility = View.GONE binding.profileTopContainer.visibility = View.VISIBLE - binding.temp.setOnClickListener { - ContextCompat.startActivity( - this@ProfileActivity, Intent(this@ProfileActivity, FollowActivity::class.java) - .putExtra("title", "Following") - .putExtra("userId", user.id), null - ) + binding.profileMenuButton.setOnClickListener { + val popup = PopupMenu(this@ProfileActivity, binding.profileMenuButton) + popup.menuInflater.inflate(R.menu.menu_profile, popup.menu) + popup.setOnMenuItemClickListener { item -> + when (item.itemId) { + R.id.action_view_following -> { + ContextCompat.startActivity( + this@ProfileActivity, + Intent(this@ProfileActivity, FollowActivity::class.java) + .putExtra("title", "Following") + .putExtra("userId", user.id), + null + ) + true + } + R.id.action_view_followers -> { + ContextCompat.startActivity( + this@ProfileActivity, + Intent(this@ProfileActivity, FollowActivity::class.java) + .putExtra("title", "Followers") + .putExtra("userId", user.id), + null + ) + true + } + else -> false + } + } + popup.show() } binding.profileUserAvatar.loadImage(user.avatar?.medium) @@ -122,6 +150,10 @@ class ProfileActivity : AppCompatActivity() { if (!(PrefManager.getVal(PrefName.BannerAnimations) as Boolean)) binding.profileBannerImage.pause() binding.profileBannerImage.loadImage(user.bannerImage) binding.profileBannerImage.updateLayoutParams { height += statusBarHeight } + binding.profileBannerGradient.updateLayoutParams { height += statusBarHeight } + binding.profileMenuButton.updateLayoutParams { + topMargin += statusBarHeight + } binding.profileBannerImage.setOnLongClickListener { ImageViewDialog.newInstance( this@ProfileActivity, @@ -132,7 +164,6 @@ class ProfileActivity : AppCompatActivity() { } } - } override fun onResume() { diff --git a/app/src/main/java/ani/dantotsu/profile/ProfileFragment.kt b/app/src/main/java/ani/dantotsu/profile/ProfileFragment.kt index bb43f8b2..50daccab 100644 --- a/app/src/main/java/ani/dantotsu/profile/ProfileFragment.kt +++ b/app/src/main/java/ani/dantotsu/profile/ProfileFragment.kt @@ -47,11 +47,11 @@ class ProfileFragment() : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) activity = requireActivity() as ProfileActivity + + user = arguments?.getSerializable("user") as Query.UserProfile viewLifecycleOwner.lifecycleScope.launch(Dispatchers.IO) { model.setData(user.id) } - - user = arguments?.getSerializable("user") as Query.UserProfile val backGroundColorTypedValue = TypedValue() val textColorTypedValue = TypedValue() activity.theme.resolveAttribute( diff --git a/app/src/main/java/ani/dantotsu/settings/saving/Preferences.kt b/app/src/main/java/ani/dantotsu/settings/saving/Preferences.kt index 7dec7e49..7c17221f 100644 --- a/app/src/main/java/ani/dantotsu/settings/saving/Preferences.kt +++ b/app/src/main/java/ani/dantotsu/settings/saving/Preferences.kt @@ -66,6 +66,7 @@ enum class PrefName(val data: Pref) { //TODO: Split this into multiple files AnimeListSortOrder(Pref(Location.UI, String::class, "score")), MangaListSortOrder(Pref(Location.UI, String::class, "score")), CommentSortOrder(Pref(Location.UI, String::class, "newest")), + FollowerLayout(Pref(Location.UI, Int::class, 0)), //Player DefaultSpeed(Pref(Location.Player, Int::class, 5)), diff --git a/app/src/main/res/layout/activity_follow.xml b/app/src/main/res/layout/activity_follow.xml index ec916119..5990652e 100644 --- a/app/src/main/res/layout/activity_follow.xml +++ b/app/src/main/res/layout/activity_follow.xml @@ -1,38 +1,98 @@ + + android:visibility="gone" /> - + android:layout_height="wrap_content" + android:orientation="horizontal"> + + + + + + + + + + + + + + + + + android:layout_marginEnd="16dp" + tools:listitem="@layout/item_follower" /> \ 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 37543e8b..021128d1 100644 --- a/app/src/main/res/layout/activity_profile.xml +++ b/app/src/main/res/layout/activity_profile.xml @@ -22,102 +22,104 @@ + android:layout_marginBottom="72dp" + android:scrollbars="none"> - - - - - - - + - + + + + + android:backgroundTint="@color/transparent" + app:cardCornerRadius="64dp"> - + - + + + +