feat: statistics (wip)

This commit is contained in:
rebelonion 2024-03-02 04:54:02 -06:00
parent 533148069f
commit 500de4e45e
12 changed files with 690 additions and 356 deletions

View file

@ -17,6 +17,7 @@ import android.content.res.Configuration
import android.content.res.Resources.getSystem import android.content.res.Resources.getSystem
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.Color import android.graphics.Color
import android.graphics.drawable.Drawable
import android.media.MediaScannerConnection import android.media.MediaScannerConnection
import android.net.ConnectivityManager import android.net.ConnectivityManager
import android.net.NetworkCapabilities.* import android.net.NetworkCapabilities.*
@ -59,8 +60,15 @@ import ani.dantotsu.settings.saving.internal.PreferenceKeystore
import ani.dantotsu.settings.saving.internal.PreferenceKeystore.Companion.generateSalt import ani.dantotsu.settings.saving.internal.PreferenceKeystore.Companion.generateSalt
import ani.dantotsu.subcriptions.NotificationClickReceiver import ani.dantotsu.subcriptions.NotificationClickReceiver
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.bumptech.glide.RequestBuilder
import com.bumptech.glide.RequestManager
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.engine.GlideException
import com.bumptech.glide.load.model.GlideUrl import com.bumptech.glide.load.model.GlideUrl
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withCrossFade import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withCrossFade
import com.bumptech.glide.load.resource.gif.GifDrawable
import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.target.Target
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
import com.google.android.material.bottomnavigation.BottomNavigationView import com.google.android.material.bottomnavigation.BottomNavigationView
import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetBehavior
@ -68,6 +76,17 @@ import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.google.android.material.internal.ViewUtils import com.google.android.material.internal.ViewUtils
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import eu.kanade.tachiyomi.data.notification.Notifications import eu.kanade.tachiyomi.data.notification.Notifications
import io.noties.markwon.AbstractMarkwonPlugin
import io.noties.markwon.Markwon
import io.noties.markwon.MarkwonConfiguration
import io.noties.markwon.SoftBreakAddsNewLinePlugin
import io.noties.markwon.ext.strikethrough.StrikethroughPlugin
import io.noties.markwon.ext.tables.TablePlugin
import io.noties.markwon.ext.tasklist.TaskListPlugin
import io.noties.markwon.html.HtmlPlugin
import io.noties.markwon.html.TagHandlerNoOp
import io.noties.markwon.image.AsyncDrawable
import io.noties.markwon.image.glide.GlideImagesPlugin
import kotlinx.coroutines.* import kotlinx.coroutines.*
import nl.joery.animatedbottombar.AnimatedBottomBar import nl.joery.animatedbottombar.AnimatedBottomBar
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
@ -1079,4 +1098,70 @@ fun logToFile(context: Context, message: String) {
val file = File(externalFilesDir, "notifications.log") val file = File(externalFilesDir, "notifications.log")
file.appendText(message) file.appendText(message)
file.appendText("\n") file.appendText("\n")
}
/**
* Builds the markwon instance with all the plugins
* @return the markwon instance
*/
fun buildMarkwon(activity: Activity, userInputContent: Boolean = true): Markwon {
val markwon = Markwon.builder(activity)
.usePlugin(object : AbstractMarkwonPlugin() {
override fun configureConfiguration(builder: MarkwonConfiguration.Builder) {
builder.linkResolver { view, link ->
copyToClipboard(link, true)
}
}
})
.usePlugin(SoftBreakAddsNewLinePlugin.create())
.usePlugin(StrikethroughPlugin.create())
.usePlugin(TablePlugin.create(activity))
.usePlugin(TaskListPlugin.create(activity))
.usePlugin(HtmlPlugin.create { plugin ->
if (!userInputContent) {
plugin.addHandler(
TagHandlerNoOp.create("h1", "h2", "h3", "h4", "h5", "h6", "hr", "pre", "a")
)
}
})
.usePlugin(GlideImagesPlugin.create(object : GlideImagesPlugin.GlideStore {
private val requestManager: RequestManager =
Glide.with(activity).apply {
addDefaultRequestListener(object : RequestListener<Any> {
override fun onResourceReady(
resource: Any,
model: Any,
target: Target<Any>,
dataSource: DataSource,
isFirstResource: Boolean
): Boolean {
if (resource is GifDrawable) {
resource.start()
}
return false
}
override fun onLoadFailed(
e: GlideException?,
model: Any?,
target: Target<Any>,
isFirstResource: Boolean
): Boolean {
return false
}
})
}
override fun load(drawable: AsyncDrawable): RequestBuilder<Drawable> {
return requestManager.load(drawable.destination)
}
override fun cancel(target: Target<*>) {
requestManager.clear(target)
}
}))
.build()
return markwon
} }

View file

@ -10,12 +10,8 @@ import ani.dantotsu.R
import ani.dantotsu.connections.comments.Comment import ani.dantotsu.connections.comments.Comment
import ani.dantotsu.connections.comments.CommentsAPI import ani.dantotsu.connections.comments.CommentsAPI
import ani.dantotsu.copyToClipboard import ani.dantotsu.copyToClipboard
import ani.dantotsu.currActivity
import ani.dantotsu.currContext
import ani.dantotsu.databinding.ItemCommentsBinding import ani.dantotsu.databinding.ItemCommentsBinding
import ani.dantotsu.loadImage import ani.dantotsu.loadImage
import ani.dantotsu.media.user.ListActivity
import ani.dantotsu.openLinkInBrowser
import ani.dantotsu.profile.ProfileActivity import ani.dantotsu.profile.ProfileActivity
import ani.dantotsu.snackString import ani.dantotsu.snackString
import com.xwray.groupie.GroupieAdapter import com.xwray.groupie.GroupieAdapter
@ -56,7 +52,7 @@ class CommentItem(val comment: Comment,
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
override fun bind(viewBinding: ItemCommentsBinding, position: Int) { override fun bind(viewBinding: ItemCommentsBinding, position: Int) {
binding = viewBinding binding = viewBinding
viewBinding.commentRepliesList.layoutManager = LinearLayoutManager(currActivity()) viewBinding.commentRepliesList.layoutManager = LinearLayoutManager(commentsFragment.activity)
viewBinding.commentRepliesList.adapter = adapter viewBinding.commentRepliesList.adapter = adapter
val isUserComment = CommentsAPI.userId == comment.userId val isUserComment = CommentsAPI.userId == comment.userId
val node = markwon.parse(comment.content) val node = markwon.parse(comment.content)
@ -101,7 +97,7 @@ class CommentItem(val comment: Comment,
viewBinding.commentUserName.setOnClickListener { viewBinding.commentUserName.setOnClickListener {
ContextCompat.startActivity( ContextCompat.startActivity(
currContext()!!, Intent(currContext()!!, ProfileActivity::class.java) commentsFragment.activity, Intent(commentsFragment.activity, ProfileActivity::class.java)
.putExtra("userId", comment.userId.toInt()) .putExtra("userId", comment.userId.toInt())
.putExtra("username","[${levelColor.second}]"), null .putExtra("username","[${levelColor.second}]"), null
) )
@ -215,12 +211,12 @@ class CommentItem(val comment: Comment,
} }
fun replying(isReplying: Boolean) { fun replying(isReplying: Boolean) {
binding?.commentReply?.text = if (isReplying) currActivity()!!.getString(R.string.cancel) else "Reply" binding?.commentReply?.text = if (isReplying) commentsFragment.activity.getString(R.string.cancel) else "Reply"
this.isReplying = isReplying this.isReplying = isReplying
} }
fun editing(isEditing: Boolean) { fun editing(isEditing: Boolean) {
binding?.commentEdit?.text = if (isEditing) currActivity()!!.getString(R.string.cancel) else currActivity()!!.getString(R.string.edit) binding?.commentEdit?.text = if (isEditing) commentsFragment.activity.getString(R.string.cancel) else commentsFragment.activity.getString(R.string.edit)
this.isEditing = isEditing this.isEditing = isEditing
} }

View file

@ -22,6 +22,7 @@ import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import ani.dantotsu.R import ani.dantotsu.R
import ani.dantotsu.buildMarkwon
import ani.dantotsu.connections.anilist.Anilist import ani.dantotsu.connections.anilist.Anilist
import ani.dantotsu.connections.comments.Comment import ani.dantotsu.connections.comments.Comment
import ani.dantotsu.connections.comments.CommentResponse import ani.dantotsu.connections.comments.CommentResponse
@ -100,7 +101,7 @@ class CommentsFragment : Fragment() {
this.mediaId = mediaId this.mediaId = mediaId
backgroundColor = (binding.root.background as? ColorDrawable)?.color ?: 0 backgroundColor = (binding.root.background as? ColorDrawable)?.color ?: 0
val markwon = buildMarkwon() val markwon = buildMarkwon(activity)
binding.commentUserAvatar.loadImage(Anilist.avatar) binding.commentUserAvatar.loadImage(Anilist.avatar)
val markwonEditor = MarkwonEditor.create(markwon) val markwonEditor = MarkwonEditor.create(markwon)
@ -235,7 +236,7 @@ class CommentsFragment : Fragment() {
section.add( section.add(
CommentItem( CommentItem(
comment, comment,
buildMarkwon(), buildMarkwon(activity),
section, section,
this@CommentsFragment, this@CommentsFragment,
backgroundColor, backgroundColor,
@ -386,7 +387,7 @@ class CommentsFragment : Fragment() {
section.add( section.add(
CommentItem( CommentItem(
it, it,
buildMarkwon(), buildMarkwon(activity),
section, section,
this@CommentsFragment, this@CommentsFragment,
backgroundColor, backgroundColor,
@ -416,7 +417,7 @@ class CommentsFragment : Fragment() {
section.add( section.add(
CommentItem( CommentItem(
comment, comment,
buildMarkwon(), buildMarkwon(activity),
section, section,
this@CommentsFragment, this@CommentsFragment,
backgroundColor, backgroundColor,
@ -539,7 +540,7 @@ class CommentsFragment : Fragment() {
if (depth >= comment.MAX_DEPTH) comment.registerSubComment(it.commentId) if (depth >= comment.MAX_DEPTH) comment.registerSubComment(it.commentId)
val newCommentItem = CommentItem( val newCommentItem = CommentItem(
it, it,
buildMarkwon(), buildMarkwon(activity),
section, section,
this@CommentsFragment, this@CommentsFragment,
backgroundColor, backgroundColor,
@ -664,7 +665,7 @@ class CommentsFragment : Fragment() {
if (commentWithInteraction!!.commentDepth + 1 > commentWithInteraction!!.MAX_DEPTH) 0 else section.itemCount, if (commentWithInteraction!!.commentDepth + 1 > commentWithInteraction!!.MAX_DEPTH) 0 else section.itemCount,
CommentItem( CommentItem(
it, it,
buildMarkwon(), buildMarkwon(activity),
section, section,
this@CommentsFragment, this@CommentsFragment,
backgroundColor, backgroundColor,
@ -676,7 +677,7 @@ class CommentsFragment : Fragment() {
0, 0,
CommentItem( CommentItem(
it, it,
buildMarkwon(), buildMarkwon(activity),
section, section,
this@CommentsFragment, this@CommentsFragment,
backgroundColor, backgroundColor,
@ -686,68 +687,4 @@ class CommentsFragment : Fragment() {
} }
} }
} }
/**
* Builds the markwon instance with all the plugins
* @return the markwon instance
*/
private fun buildMarkwon(): Markwon {
val markwon = Markwon.builder(activity)
.usePlugin(object : AbstractMarkwonPlugin() {
override fun configureConfiguration(builder: MarkwonConfiguration.Builder) {
builder.linkResolver { view, link ->
copyToClipboard(link, true)
}
}
})
.usePlugin(SoftBreakAddsNewLinePlugin.create())
.usePlugin(StrikethroughPlugin.create())
.usePlugin(TablePlugin.create(activity))
.usePlugin(TaskListPlugin.create(activity))
.usePlugin(HtmlPlugin.create { plugin ->
plugin.addHandler(
TagHandlerNoOp.create("h1", "h2", "h3", "h4", "h5", "h6", "hr", "pre", "a")
)
})
.usePlugin(GlideImagesPlugin.create(object : GlideImagesPlugin.GlideStore {
private val requestManager: RequestManager =
Glide.with(this@CommentsFragment).apply {
addDefaultRequestListener(object : RequestListener<Any> {
override fun onResourceReady(
resource: Any,
model: Any,
target: Target<Any>,
dataSource: DataSource,
isFirstResource: Boolean
): Boolean {
if (resource is GifDrawable) {
resource.start()
}
return false
}
override fun onLoadFailed(
e: GlideException?,
model: Any?,
target: Target<Any>,
isFirstResource: Boolean
): Boolean {
return false
}
})
}
override fun load(drawable: AsyncDrawable): RequestBuilder<Drawable> {
return requestManager.load(drawable.destination)
}
override fun cancel(target: Target<*>) {
requestManager.clear(target)
}
}))
.build()
return markwon
}
} }

View file

@ -6,58 +6,123 @@ import android.os.Bundle
import android.view.View import android.view.View
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.viewpager2.adapter.FragmentStateAdapter
import ani.dantotsu.R
import ani.dantotsu.connections.anilist.Anilist import ani.dantotsu.connections.anilist.Anilist
import ani.dantotsu.connections.anilist.api.Query
import ani.dantotsu.databinding.ActivityProfileBinding import ani.dantotsu.databinding.ActivityProfileBinding
import ani.dantotsu.initActivity
import ani.dantotsu.loadImage import ani.dantotsu.loadImage
import ani.dantotsu.media.Media
import ani.dantotsu.media.MediaDetailsActivity
import ani.dantotsu.media.user.ListActivity import ani.dantotsu.media.user.ListActivity
import ani.dantotsu.others.ImageViewDialog
import ani.dantotsu.snackString
import ani.dantotsu.themes.ThemeManager
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import nl.joery.animatedbottombar.AnimatedBottomBar
class ProfileActivity : AppCompatActivity(){ class ProfileActivity : AppCompatActivity(){
private lateinit var binding: ActivityProfileBinding private lateinit var binding: ActivityProfileBinding
private var selected: Int = 0
private lateinit var tabLayout: AnimatedBottomBar
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
ThemeManager(this).applyTheme()
initActivity(this)
binding = ActivityProfileBinding.inflate(layoutInflater) binding = ActivityProfileBinding.inflate(layoutInflater)
setContentView(binding.root) setContentView(binding.root)
tabLayout = binding.typeTab
val profileTab = tabLayout.createTab(R.drawable.ic_round_person_24, "Profile")
val statsTab = tabLayout.createTab(R.drawable.ic_stats_24, "Stats")
tabLayout.addTab(profileTab)
tabLayout.addTab(statsTab)
tabLayout.visibility = View.GONE
binding.mediaViewPager.isUserInputEnabled = false
lifecycleScope.launch(Dispatchers.IO) { lifecycleScope.launch(Dispatchers.IO) {
val userid = intent.getIntExtra("userId", 0) val userid = intent.getIntExtra("userId", 0)
val respond = Anilist.query.getUserProfile(userid) val respond = Anilist.query.getUserProfile(userid)
val user = respond?.data?.user ?: return@launch val user = respond?.data?.user
val userLevel = intent.getStringExtra("username") if (user == null) {
snackString("User not found")
finish()
return@launch
}
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
binding.mediaViewPager.adapter = ViewPagerAdapter(supportFragmentManager, lifecycle, user, this@ProfileActivity)
tabLayout.visibility = View.VISIBLE
tabLayout.selectTabAt(selected)
tabLayout.setOnTabSelectListener(object : AnimatedBottomBar.OnTabSelectListener {
override fun onTabSelected(
lastIndex: Int,
lastTab: AnimatedBottomBar.Tab?,
newIndex: Int,
newTab: AnimatedBottomBar.Tab
) {
selected = newIndex
binding.mediaViewPager.setCurrentItem(selected, true)
}
})
val userLevel = intent.getStringExtra("username")?: ""
binding.profileProgressBar.visibility = View.GONE binding.profileProgressBar.visibility = View.GONE
binding.profileBannerImage.loadImage(user.bannerImage) binding.profileBannerImage.loadImage(user.bannerImage)
binding.profileUserAvatar.loadImage(user.avatar?.medium) binding.profileUserAvatar.loadImage(user.avatar?.medium)
binding.profileUserName.text = "${user.name} $userLevel" binding.profileUserName.text = "${user.name} $userLevel"
binding.profileUserInfo.text = user.about
binding.profileAnimeList.setOnClickListener {
ContextCompat.startActivity(
this@ProfileActivity, Intent(this@ProfileActivity, ListActivity::class.java)
.putExtra("anime", true)
.putExtra("userId", user.id)
.putExtra("username", user.name), null
)
}
binding.profileMangaList.setOnClickListener {
ContextCompat.startActivity(
this@ProfileActivity, Intent(this@ProfileActivity, ListActivity::class.java)
.putExtra("anime", false)
.putExtra("userId", user.id)
.putExtra("username", user.name), null
)
}
binding.profileUserEpisodesWatched.text = user.statistics.anime.episodesWatched.toString() binding.profileUserEpisodesWatched.text = user.statistics.anime.episodesWatched.toString()
binding.profileUserChaptersRead.text = user.statistics.manga.chaptersRead.toString() binding.profileUserChaptersRead.text = user.statistics.manga.chaptersRead.toString()
binding.profileAnimeListImage.loadImage("https://bit.ly/31bsIHq") binding.profileBannerImage.loadImage(user.bannerImage)
binding.profileMangaListImage.loadImage("https://bit.ly/2ZGfcuG") binding.profileBannerImage.setOnLongClickListener {
ImageViewDialog.newInstance(
this@ProfileActivity,
user.name + " [Banner]",
user.bannerImage
)
}
binding.profileUserAvatar.loadImage(user.avatar?.medium)
binding.profileUserAvatar.setOnLongClickListener {
ImageViewDialog.newInstance(
this@ProfileActivity,
user.name + " [Avatar]",
user.avatar?.medium
)
}
} }
} }
} }
override fun onResume() {
if (this::tabLayout.isInitialized) {
tabLayout.selectTabAt(selected)
}
super.onResume()
}
private class ViewPagerAdapter(
fragmentManager: FragmentManager,
lifecycle: Lifecycle,
private val user: Query.UserProfile,
private val activity: ProfileActivity
) :
FragmentStateAdapter(fragmentManager, lifecycle) {
override fun getItemCount(): Int = 2
override fun createFragment(position: Int): Fragment = when (position) {
0 -> ProfileFragment(user, activity)
1 -> StatsFragment(user, activity)
else -> ProfileFragment(user, activity)
}
}
} }

View file

@ -0,0 +1,50 @@
package ani.dantotsu.profile
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import ani.dantotsu.buildMarkwon
import ani.dantotsu.connections.anilist.api.Query
import ani.dantotsu.databinding.FragmentProfileBinding
import ani.dantotsu.loadImage
import ani.dantotsu.media.user.ListActivity
class ProfileFragment(private val user: Query.UserProfile, private val activity: ProfileActivity): Fragment() {
lateinit var binding: FragmentProfileBinding
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentProfileBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val markwon = buildMarkwon(activity, false)
markwon.setMarkdown(binding.profileUserInfo, user.about?:"")
binding.profileAnimeList.setOnClickListener {
ContextCompat.startActivity(
activity, Intent(activity, ListActivity::class.java)
.putExtra("anime", true)
.putExtra("userId", user.id)
.putExtra("username", user.name), null
)
}
binding.profileMangaList.setOnClickListener {
ContextCompat.startActivity(
activity, Intent(activity, ListActivity::class.java)
.putExtra("anime", false)
.putExtra("userId", user.id)
.putExtra("username", user.name), null
)
}
binding.profileAnimeListImage.loadImage("https://bit.ly/31bsIHq")
binding.profileMangaListImage.loadImage("https://bit.ly/2ZGfcuG")
}
}

View file

@ -1,4 +0,0 @@
package ani.dantotsu.profile
class StatisticsActivity {
}

View file

@ -0,0 +1,157 @@
package ani.dantotsu.profile
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import ani.dantotsu.R
import ani.dantotsu.connections.anilist.Anilist
import ani.dantotsu.connections.anilist.api.Query
import ani.dantotsu.databinding.FragmentStatisticsBinding
import com.github.aachartmodel.aainfographics.aachartcreator.AAChartAlignType
import com.github.aachartmodel.aainfographics.aachartcreator.AAChartLayoutType
import com.github.aachartmodel.aainfographics.aachartcreator.AAChartModel
import com.github.aachartmodel.aainfographics.aachartcreator.AAChartStackingType
import com.github.aachartmodel.aainfographics.aachartcreator.AAChartType
import com.github.aachartmodel.aainfographics.aachartcreator.AAChartVerticalAlignType
import com.github.aachartmodel.aainfographics.aachartcreator.AAChartZoomType
import com.github.aachartmodel.aainfographics.aachartcreator.AADataElement
import com.github.aachartmodel.aainfographics.aachartcreator.AAOptions
import com.github.aachartmodel.aainfographics.aachartcreator.AASeriesElement
import com.github.aachartmodel.aainfographics.aachartcreator.aa_toAAOptions
import com.github.aachartmodel.aainfographics.aaoptionsmodel.AAChart
import com.github.aachartmodel.aainfographics.aaoptionsmodel.AALang
import com.github.aachartmodel.aainfographics.aaoptionsmodel.AAPosition
import com.github.aachartmodel.aainfographics.aaoptionsmodel.AAScrollablePlotArea
import com.github.aachartmodel.aainfographics.aaoptionsmodel.AAStyle
import com.github.aachartmodel.aainfographics.aatools.AAColor
import com.github.aachartmodel.aainfographics.aatools.AAGradientColor
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import nl.joery.animatedbottombar.AnimatedBottomBar
class StatsFragment(private val user: Query.UserProfile, private val activity: ProfileActivity) :
Fragment() {
private lateinit var binding: FragmentStatisticsBinding
private var selected: Int = 0
private lateinit var tabLayout: AnimatedBottomBar
private var stats: Query.StatisticsResponse? = null
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentStatisticsBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
tabLayout = binding.typeTab
val animeTab = tabLayout.createTab(R.drawable.ic_round_movie_filter_24, "Anime")
val mangaTab = tabLayout.createTab(R.drawable.ic_round_menu_book_24, "Manga")
tabLayout.addTab(animeTab)
tabLayout.addTab(mangaTab)
tabLayout.visibility = View.GONE
activity.lifecycleScope.launch {
stats = Anilist.query.getUserStatistics(user.id)
withContext(Dispatchers.Main) {
tabLayout.visibility = View.VISIBLE
tabLayout.setOnTabSelectListener(object : AnimatedBottomBar.OnTabSelectListener {
override fun onTabSelected(
lastIndex: Int,
lastTab: AnimatedBottomBar.Tab?,
newIndex: Int,
newTab: AnimatedBottomBar.Tab
) {
selected = newIndex
when (newIndex) {
0 -> loadAnimeStats()
1 -> loadMangaStats()
}
}
})
tabLayout.selectTabAt(selected)
loadAnimeStats()
}
}
}
override fun onResume() {
if (this::tabLayout.isInitialized) {
tabLayout.selectTabAt(selected)
}
super.onResume()
}
private fun loadAnimeStats() {
val formatChartModel = getFormatChartModel(true)
if (formatChartModel != null) {
val aaOptions = buildOptions(formatChartModel)
binding.formatChartView.aa_drawChartWithChartOptions(aaOptions)
}
}
private fun loadMangaStats() {
val formatChartModel = getFormatChartModel(false)
if (formatChartModel != null) {
val aaOptions = buildOptions(formatChartModel)
binding.formatChartView.aa_drawChartWithChartOptions(aaOptions)
}
}
private fun buildOptions(aaChartModel: AAChartModel): AAOptions {
val aaOptions = aaChartModel.aa_toAAOptions()
aaOptions.tooltip?.apply {
backgroundColor(AAGradientColor.PurpleLake)
.style(AAStyle.style(AAColor.White))
}
aaOptions.chart?.zoomType = "xy"
aaOptions.chart?.pinchType = "xy"
aaOptions.legend?.apply {
enabled(true)
.verticalAlign(AAChartVerticalAlignType.Top)
.layout(AAChartLayoutType.Vertical)
.align(AAChartAlignType.Right)
.itemMarginTop(10f)
.labelFormat = "{name}: {y}"
}
aaOptions.plotOptions?.series?.connectNulls(true)
return aaOptions
}
private fun getFormatChartModel(anime: Boolean): AAChartModel? {
val fotmatTypes: List<String> = if (anime) {
stats?.data?.user?.statistics?.anime?.formats?.map { it.format } ?: emptyList()
} else {
stats?.data?.user?.statistics?.manga?.formats?.map { it.format } ?: emptyList()
}
val formatCount: List<Int> = if (anime) {
stats?.data?.user?.statistics?.anime?.formats?.map { it.count } ?: emptyList()
} else {
stats?.data?.user?.statistics?.manga?.formats?.map { it.count } ?: emptyList()
}
if (fotmatTypes.isEmpty() || formatCount.isEmpty())
return null
return AAChartModel()
.chartType(AAChartType.Pie)
.title("Format")
.zoomType(AAChartZoomType.XY)
.dataLabelsEnabled(true)
.series(getElements(fotmatTypes, formatCount))
}
private fun getElements(types: List<String>, counts: List<Int>): Array<Any> {
val elements = AASeriesElement()
val dataElements = mutableListOf<AADataElement>()
for (i in types.indices) {
dataElements.add(AADataElement().name(types[i]).y(counts[i]))
}
return arrayOf(elements.data(dataElements.toTypedArray()))
}
}

View file

@ -1,7 +1,7 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="32dp" android:width="32dp"
android:height="32dp" android:height="32dp"
android:tint="@color/bg_opp" android:tint="?attr/colorControlNormal"
android:viewportWidth="24" android:viewportWidth="24"
android:viewportHeight="24"> android:viewportHeight="24">
<path <path

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:fillColor="#FF000000"
android:pathData="M160,800v-320h160v320L160,800ZM400,800v-640h160v640L400,800ZM640,800v-440h160v440L640,800Z"/>
</vector>

View file

@ -1,10 +1,10 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:app="http://schemas.android.com/apk/res-auto" <androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout <LinearLayout
android:id="@+id/profileProgressBar" android:id="@+id/profileProgressBar"
@ -12,255 +12,150 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:gravity="center" android:gravity="center"
tools:visibility="gone"> tools:visibility="gone">
<ProgressBar <ProgressBar
style="?android:attr/progressBarStyle" style="?android:attr/progressBarStyle"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" /> android:layout_height="wrap_content" />
</LinearLayout> </LinearLayout>
<LinearLayout <ScrollView
android:id="@+id/profileTopContainer"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="wrap_content"
android:orientation="vertical"> android:layout_marginBottom="72dp">
<ImageView
android:id="@+id/profileBannerImage"
android:layout_width="match_parent"
android:layout_height="180dp"
android:scaleType="centerCrop"
android:src="@drawable/gradient_background"
tools:ignore="ContentDescription" />
<com.google.android.material.card.MaterialCardView
android:id="@+id/profileUserAvatarContainer"
android:layout_width="82dp"
android:layout_height="82dp"
android:layout_marginTop="-52dp"
android:layout_marginEnd="32dp"
android:layout_gravity="end"
app:cardCornerRadius="40dp">
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/profileUserAvatar"
android:layout_gravity="center"
android:layout_width="78dp"
android:layout_height="78dp"
app:srcCompat="@drawable/ic_round_add_circle_24"
tools:ignore="ContentDescription,ImageContrastCheck" />
</com.google.android.material.card.MaterialCardView>
<LinearLayout <LinearLayout
android:id="@+id/profileUserDataContainer" android:id="@+id/profileTopContainer"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="18dp" android:orientation="vertical">
android:orientation="vertical"
tools:visibility="visible">
<TextView <ImageView
android:id="@+id/profileUserName" android:id="@+id/profileBannerImage"
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="180dp"
android:scaleType="centerCrop"
android:src="@drawable/gradient_background"
tools:ignore="ContentDescription" />
<com.google.android.material.card.MaterialCardView
android:id="@+id/profileUserAvatarContainer"
android:layout_width="82dp"
android:layout_height="82dp"
android:layout_gravity="end"
android:layout_marginTop="-52dp"
android:layout_marginEnd="32dp"
app:cardCornerRadius="40dp">
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/profileUserAvatar"
android:layout_width="78dp"
android:layout_height="78dp"
android:layout_gravity="center"
app:srcCompat="@drawable/ic_round_add_circle_24"
tools:ignore="ContentDescription,ImageContrastCheck" />
</com.google.android.material.card.MaterialCardView>
<LinearLayout
android:id="@+id/profileUserDataContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:fontFamily="@font/poppins_semi_bold" android:layout_marginStart="18dp"
android:text="@string/username" android:layout_marginTop="-32dp"
android:textSize="18sp" /> android:orientation="vertical"
tools:visibility="visible">
<TextView
android:id="@+id/profileUserName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/poppins_semi_bold"
android:text="@string/username"
android:textSize="18sp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:alpha="0.58"
android:fontFamily="@font/poppins"
android:text="@string/episodes_watched"
android:textSize="12sp"
tools:ignore="TextContrastCheck,TooDeepLayout" />
<TextView
android:id="@+id/profileUserEpisodesWatched"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/poppins_bold"
android:textColor="?attr/colorPrimaryVariant"
android:textSize="12sp" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:alpha="0.58"
android:fontFamily="@font/poppins"
android:text="@string/chapters_read"
android:textSize="12sp"
tools:ignore="TextContrastCheck" />
<TextView
android:id="@+id/profileUserChaptersRead"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/poppins_bold"
android:textColor="?attr/colorPrimaryVariant"
android:textSize="12sp" />
</LinearLayout>
</LinearLayout>
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="horizontal"> android:orientation="vertical"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<TextView <androidx.viewpager2.widget.ViewPager2
android:layout_width="wrap_content" android:id="@+id/mediaViewPager"
android:layout_height="wrap_content" android:layout_width="match_parent"
android:alpha="0.58" android:layout_height="match_parent"
android:fontFamily="@font/poppins" android:nestedScrollingEnabled="true"
android:text="@string/episodes_watched" tools:ignore="SpeakableTextPresentCheck" />
android:textSize="12sp"
tools:ignore="TextContrastCheck,TooDeepLayout" />
<TextView
android:id="@+id/profileUserEpisodesWatched"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/poppins_bold"
android:textColor="?attr/colorPrimaryVariant"
android:textSize="12sp" />
</LinearLayout> </LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:alpha="0.58"
android:fontFamily="@font/poppins"
android:text="@string/chapters_read"
android:textSize="12sp"
tools:ignore="TextContrastCheck" />
<TextView
android:id="@+id/profileUserChaptersRead"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/poppins_bold"
android:textColor="?attr/colorPrimaryVariant"
android:textSize="12sp" />
</LinearLayout>
</LinearLayout> </LinearLayout>
<TextView </ScrollView>
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="18dp"
android:layout_marginTop="24dp"
android:fontFamily="@font/poppins_semi_bold"
android:text="List"
android:textSize="18sp"
tools:ignore="HardcodedText" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/profileListContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToPadding="false"
android:paddingStart="24dp"
android:paddingEnd="24dp">
<com.google.android.material.card.MaterialCardView <nl.joery.animatedbottombar.AnimatedBottomBar
android:id="@+id/profileAnimeList" android:id="@+id/typeTab"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="72dp" android:layout_height="72dp"
android:layout_margin="8dp" android:layout_gravity="center_horizontal|bottom"
app:boxStrokeColor="@color/text_input_layout_stroke_color" android:background="?attr/colorSurface"
app:cardCornerRadius="16dp" android:padding="0dp"
app:layout_constrainedWidth="true" app:abb_animationInterpolator="@anim/over_shoot"
app:layout_constraintBottom_toBottomOf="parent" app:abb_indicatorAppearance="round"
app:layout_constraintEnd_toStartOf="@+id/profileMangaList" app:abb_indicatorLocation="top"
app:layout_constraintStart_toStartOf="parent" app:abb_selectedTabType="text"
app:layout_constraintTop_toTopOf="parent" app:abb_textAppearance="@style/NavBarText"
app:layout_constraintWidth_max="256dp" app:itemActiveIndicatorStyle="@style/BottomNavBar"
tools:visibility="visible"> app:itemIconTint="@color/tab_layout_icon"
app:itemRippleColor="#00000000"
<ImageView app:itemTextAppearanceActive="@style/NavBarText"
android:id="@+id/profileAnimeListImage" app:itemTextAppearanceInactive="@style/NavBarText"
android:layout_width="match_parent" app:itemTextColor="@color/tab_layout_icon" />
android:layout_height="match_parent" </androidx.coordinatorlayout.widget.CoordinatorLayout>
android:scaleType="centerCrop"
tools:ignore="ContentDescription"
tools:src="@tools:sample/backgrounds/scenic" />
<View
android:layout_width="match_parent"
android:layout_height="match_parent"
android:alpha="0.6"
android:background="@color/bg_black" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:fontFamily="@font/poppins_bold"
android:text="@string/anime_list"
android:textAllCaps="true"
android:textColor="@color/bg_white"
android:textSize="16sp" />
<View
android:layout_width="64dp"
android:layout_height="2dp"
android:layout_gravity="center"
android:background="?attr/colorPrimary" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:id="@+id/profileMangaList"
android:layout_width="match_parent"
android:layout_height="72dp"
android:layout_margin="8dp"
android:layout_marginBottom="16dp"
app:boxStrokeColor="@color/text_input_layout_stroke_color"
app:cardCornerRadius="16dp"
app:layout_constrainedWidth="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/profileAnimeList"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_max="256dp"
tools:visibility="visible">
<ImageView
android:id="@+id/profileMangaListImage"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
tools:ignore="ContentDescription"
tools:src="@tools:sample/backgrounds/scenic" />
<View
android:layout_width="match_parent"
android:layout_height="match_parent"
android:alpha="0.6"
android:background="@color/bg_black" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:fontFamily="@font/poppins_bold"
android:text="@string/manga_list"
android:textAllCaps="true"
android:textColor="@color/bg_white"
android:textSize="16sp" />
<View
android:layout_width="64dp"
android:layout_height="2dp"
android:layout_gravity="center"
android:background="?attr/colorPrimary" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</androidx.constraintlayout.widget.ConstraintLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="18dp"
android:layout_marginTop="24dp"
android:fontFamily="@font/poppins_semi_bold"
android:text="Info"
android:textSize="18sp"
tools:ignore="HardcodedText" />
<TextView
android:id="@+id/profileUserInfo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/poppins_semi_bold"
android:layout_marginStart="18dp"
android:layout_marginEnd="18dp"
android:text="info"
android:alpha="0.58"
android:textSize="12sp" />
</LinearLayout>
</LinearLayout>

View file

@ -0,0 +1,156 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="18dp"
android:layout_marginTop="24dp"
android:fontFamily="@font/poppins_semi_bold"
android:text="List"
android:textSize="18sp"
tools:ignore="HardcodedText" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/profileListContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToPadding="false"
android:paddingStart="24dp"
android:paddingEnd="24dp">
<com.google.android.material.card.MaterialCardView
android:id="@+id/profileAnimeList"
android:layout_width="match_parent"
android:layout_height="72dp"
android:layout_margin="8dp"
app:boxStrokeColor="@color/text_input_layout_stroke_color"
app:cardCornerRadius="16dp"
app:layout_constrainedWidth="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/profileMangaList"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_max="256dp"
tools:visibility="visible">
<ImageView
android:id="@+id/profileAnimeListImage"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
tools:ignore="ContentDescription"
tools:src="@tools:sample/backgrounds/scenic" />
<View
android:layout_width="match_parent"
android:layout_height="match_parent"
android:alpha="0.6"
android:background="@color/bg_black" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:fontFamily="@font/poppins_bold"
android:text="@string/anime_list"
android:textAllCaps="true"
android:textColor="@color/bg_white"
android:textSize="16sp" />
<View
android:layout_width="64dp"
android:layout_height="2dp"
android:layout_gravity="center"
android:background="?attr/colorPrimary" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:id="@+id/profileMangaList"
android:layout_width="match_parent"
android:layout_height="72dp"
android:layout_margin="8dp"
android:layout_marginBottom="16dp"
app:boxStrokeColor="@color/text_input_layout_stroke_color"
app:cardCornerRadius="16dp"
app:layout_constrainedWidth="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/profileAnimeList"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_max="256dp"
tools:visibility="visible">
<ImageView
android:id="@+id/profileMangaListImage"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
tools:ignore="ContentDescription"
tools:src="@tools:sample/backgrounds/scenic" />
<View
android:layout_width="match_parent"
android:layout_height="match_parent"
android:alpha="0.6"
android:background="@color/bg_black" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:fontFamily="@font/poppins_bold"
android:text="@string/manga_list"
android:textAllCaps="true"
android:textColor="@color/bg_white"
android:textSize="16sp" />
<View
android:layout_width="64dp"
android:layout_height="2dp"
android:layout_gravity="center"
android:background="?attr/colorPrimary" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</androidx.constraintlayout.widget.ConstraintLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="18dp"
android:layout_marginTop="24dp"
android:fontFamily="@font/poppins_semi_bold"
android:text="Info"
android:textSize="18sp"
tools:ignore="HardcodedText" />
<TextView
android:id="@+id/profileUserInfo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/poppins_semi_bold"
android:layout_marginStart="18dp"
android:layout_marginEnd="18dp"
android:text="info"
android:alpha="0.58"
android:textSize="12sp" />
</LinearLayout>

View file

@ -1,13 +1,10 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<nl.joery.animatedbottombar.AnimatedBottomBar <nl.joery.animatedbottombar.AnimatedBottomBar
android:id="@+id/typeTab" android:id="@+id/typeTab"
@ -28,15 +25,6 @@
app:itemTextAppearanceInactive="@style/NavBarText" app:itemTextAppearanceInactive="@style/NavBarText"
app:itemTextColor="@color/tab_layout_icon" /> app:itemTextColor="@color/tab_layout_icon" />
<TextView
android:id="@+id/selectedTabText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:text="Anime"
android:textSize="18sp"
android:textStyle="bold" />
<com.github.aachartmodel.aainfographics.aachartcreator.AAChartView <com.github.aachartmodel.aainfographics.aachartcreator.AAChartView
android:id="@+id/formatChartView" android:id="@+id/formatChartView"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -97,4 +85,3 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="250dp" /> android:layout_height="250dp" />
</LinearLayout> </LinearLayout>
</androidx.core.widget.NestedScrollView>