feat: multidimensional charts
This commit is contained in:
parent
500de4e45e
commit
5fb0204376
2 changed files with 143 additions and 62 deletions
|
@ -4,6 +4,7 @@ import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import android.widget.ArrayAdapter
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import ani.dantotsu.R
|
import ani.dantotsu.R
|
||||||
|
@ -13,7 +14,6 @@ import ani.dantotsu.databinding.FragmentStatisticsBinding
|
||||||
import com.github.aachartmodel.aainfographics.aachartcreator.AAChartAlignType
|
import com.github.aachartmodel.aainfographics.aachartcreator.AAChartAlignType
|
||||||
import com.github.aachartmodel.aainfographics.aachartcreator.AAChartLayoutType
|
import com.github.aachartmodel.aainfographics.aachartcreator.AAChartLayoutType
|
||||||
import com.github.aachartmodel.aainfographics.aachartcreator.AAChartModel
|
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.AAChartType
|
||||||
import com.github.aachartmodel.aainfographics.aachartcreator.AAChartVerticalAlignType
|
import com.github.aachartmodel.aainfographics.aachartcreator.AAChartVerticalAlignType
|
||||||
import com.github.aachartmodel.aainfographics.aachartcreator.AAChartZoomType
|
import com.github.aachartmodel.aainfographics.aachartcreator.AAChartZoomType
|
||||||
|
@ -21,24 +21,21 @@ import com.github.aachartmodel.aainfographics.aachartcreator.AADataElement
|
||||||
import com.github.aachartmodel.aainfographics.aachartcreator.AAOptions
|
import com.github.aachartmodel.aainfographics.aachartcreator.AAOptions
|
||||||
import com.github.aachartmodel.aainfographics.aachartcreator.AASeriesElement
|
import com.github.aachartmodel.aainfographics.aachartcreator.AASeriesElement
|
||||||
import com.github.aachartmodel.aainfographics.aachartcreator.aa_toAAOptions
|
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.aaoptionsmodel.AAStyle
|
||||||
import com.github.aachartmodel.aainfographics.aatools.AAColor
|
import com.github.aachartmodel.aainfographics.aatools.AAColor
|
||||||
import com.github.aachartmodel.aainfographics.aatools.AAGradientColor
|
import com.github.aachartmodel.aainfographics.aatools.AAGradientColor
|
||||||
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
|
import java.util.Locale
|
||||||
|
|
||||||
class StatsFragment(private val user: Query.UserProfile, private val activity: ProfileActivity) :
|
class StatsFragment(private val user: Query.UserProfile, private val activity: ProfileActivity) :
|
||||||
Fragment() {
|
Fragment() {
|
||||||
private lateinit var binding: FragmentStatisticsBinding
|
private lateinit var binding: FragmentStatisticsBinding
|
||||||
private var selected: Int = 0
|
private var selected: Int = 0
|
||||||
private lateinit var tabLayout: AnimatedBottomBar
|
|
||||||
private var stats: Query.StatisticsResponse? = null
|
private var stats: Query.StatisticsResponse? = null
|
||||||
|
private var type: MediaType = MediaType.ANIME
|
||||||
|
private var statType: StatType = StatType.COUNT
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
|
@ -51,42 +48,51 @@ class StatsFragment(private val user: Query.UserProfile, private val activity: P
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
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
|
binding.sourceType.setAdapter(
|
||||||
|
ArrayAdapter(
|
||||||
|
requireContext(),
|
||||||
|
R.layout.item_dropdown,
|
||||||
|
MediaType.entries.map { it.name.uppercase(Locale.ROOT) }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
binding.sourceFilter.setAdapter(
|
||||||
|
ArrayAdapter(
|
||||||
|
requireContext(),
|
||||||
|
R.layout.item_dropdown,
|
||||||
|
StatType.entries.map { it.name.uppercase(Locale.ROOT) }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
binding.filterContainer.visibility = View.GONE
|
||||||
activity.lifecycleScope.launch {
|
activity.lifecycleScope.launch {
|
||||||
stats = Anilist.query.getUserStatistics(user.id)
|
stats = Anilist.query.getUserStatistics(user.id)
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
tabLayout.visibility = View.VISIBLE
|
binding.filterContainer.visibility = View.VISIBLE
|
||||||
tabLayout.setOnTabSelectListener(object : AnimatedBottomBar.OnTabSelectListener {
|
binding.sourceType.setOnItemClickListener { _, _, i, _ ->
|
||||||
override fun onTabSelected(
|
type = MediaType.entries.toTypedArray()[i]
|
||||||
lastIndex: Int,
|
updateStats()
|
||||||
lastTab: AnimatedBottomBar.Tab?,
|
}
|
||||||
newIndex: Int,
|
binding.sourceFilter.setOnItemClickListener { _, _, i, _ ->
|
||||||
newTab: AnimatedBottomBar.Tab
|
statType = StatType.entries.toTypedArray()[i]
|
||||||
) {
|
updateStats()
|
||||||
selected = newIndex
|
}
|
||||||
when (newIndex) {
|
updateStats()
|
||||||
0 -> loadAnimeStats()
|
|
||||||
1 -> loadMangaStats()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
tabLayout.selectTabAt(selected)
|
|
||||||
loadAnimeStats()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
if (this::tabLayout.isInitialized) {
|
|
||||||
tabLayout.selectTabAt(selected)
|
|
||||||
}
|
|
||||||
super.onResume()
|
super.onResume()
|
||||||
|
updateStats()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun updateStats() {
|
||||||
|
when (type) {
|
||||||
|
MediaType.ANIME -> loadAnimeStats()
|
||||||
|
MediaType.MANGA -> loadMangaStats()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadAnimeStats() {
|
private fun loadAnimeStats() {
|
||||||
|
@ -126,32 +132,50 @@ class StatsFragment(private val user: Query.UserProfile, private val activity: P
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getFormatChartModel(anime: Boolean): AAChartModel? {
|
private fun getFormatChartModel(anime: Boolean): AAChartModel? {
|
||||||
val fotmatTypes: List<String> = if (anime) {
|
val names: List<String> = if (anime) {
|
||||||
stats?.data?.user?.statistics?.anime?.formats?.map { it.format } ?: emptyList()
|
stats?.data?.user?.statistics?.anime?.formats?.map { it.format } ?: emptyList()
|
||||||
} else {
|
} else {
|
||||||
stats?.data?.user?.statistics?.manga?.formats?.map { it.format } ?: emptyList()
|
stats?.data?.user?.statistics?.manga?.formats?.map { it.format } ?: emptyList()
|
||||||
}
|
}
|
||||||
val formatCount: List<Int> = if (anime) {
|
val values: List<Number> = if (anime) {
|
||||||
stats?.data?.user?.statistics?.anime?.formats?.map { it.count } ?: emptyList()
|
when (statType) {
|
||||||
|
StatType.COUNT -> stats?.data?.user?.statistics?.anime?.formats?.map { it.count }
|
||||||
|
StatType.TIME -> stats?.data?.user?.statistics?.anime?.formats?.map { it.minutesWatched }
|
||||||
|
StatType.MEAN_SCORE -> stats?.data?.user?.statistics?.anime?.formats?.map { it.meanScore }
|
||||||
|
} ?: emptyList()
|
||||||
} else {
|
} else {
|
||||||
stats?.data?.user?.statistics?.manga?.formats?.map { it.count } ?: emptyList()
|
when (statType) {
|
||||||
|
StatType.COUNT -> stats?.data?.user?.statistics?.manga?.formats?.map { it.count }
|
||||||
|
StatType.TIME -> stats?.data?.user?.statistics?.manga?.formats?.map { it.chaptersRead }
|
||||||
|
StatType.MEAN_SCORE -> stats?.data?.user?.statistics?.manga?.formats?.map { it.meanScore }
|
||||||
|
} ?: emptyList()
|
||||||
}
|
}
|
||||||
if (fotmatTypes.isEmpty() || formatCount.isEmpty())
|
if (names.isEmpty() || values.isEmpty())
|
||||||
return null
|
return null
|
||||||
return AAChartModel()
|
return AAChartModel()
|
||||||
.chartType(AAChartType.Pie)
|
.chartType(AAChartType.Pie)
|
||||||
.title("Format")
|
.title("Format")
|
||||||
|
.subtitle(statType.name.lowercase(Locale.ROOT))
|
||||||
.zoomType(AAChartZoomType.XY)
|
.zoomType(AAChartZoomType.XY)
|
||||||
.dataLabelsEnabled(true)
|
.dataLabelsEnabled(true)
|
||||||
.series(getElements(fotmatTypes, formatCount))
|
.series(getElements(names, values, StatType.COUNT))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getElements(types: List<String>, counts: List<Int>): Array<Any> {
|
enum class StatType {
|
||||||
val elements = AASeriesElement()
|
COUNT, TIME, MEAN_SCORE
|
||||||
val dataElements = mutableListOf<AADataElement>()
|
}
|
||||||
for (i in types.indices) {
|
|
||||||
dataElements.add(AADataElement().name(types[i]).y(counts[i]))
|
enum class MediaType {
|
||||||
|
ANIME, MANGA
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getElements(names: List<String>, statData: List<Number>, type: StatType): Array<Any> {
|
||||||
|
val statDataElements = mutableListOf<AADataElement>()
|
||||||
|
for (i in statData.indices) {
|
||||||
|
statDataElements.add(AADataElement().name(names[i]).y(statData[i]))
|
||||||
}
|
}
|
||||||
return arrayOf(elements.data(dataElements.toTypedArray()))
|
return arrayOf(
|
||||||
|
AASeriesElement().name("Count").data(statDataElements.toTypedArray()),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -4,26 +4,83 @@
|
||||||
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"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<nl.joery.animatedbottombar.AnimatedBottomBar
|
<LinearLayout
|
||||||
android:id="@+id/typeTab"
|
android:id="@+id/filterContainer"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="72dp"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="center_horizontal|bottom"
|
android:orientation="horizontal"
|
||||||
android:background="?attr/colorSurface"
|
android:padding="16dp">
|
||||||
android:padding="0dp"
|
|
||||||
app:abb_animationInterpolator="@anim/over_shoot"
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
app:abb_indicatorAppearance="round"
|
android:id="@+id/sourceTypeNameContainer"
|
||||||
app:abb_indicatorLocation="bottom"
|
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu"
|
||||||
app:abb_selectedTabType="text"
|
android:layout_width="0dp"
|
||||||
app:abb_textAppearance="@style/NavBarText"
|
android:layout_height="56dp"
|
||||||
app:itemActiveIndicatorStyle="@style/BottomNavBar"
|
android:layout_weight="1"
|
||||||
app:itemIconTint="@color/tab_layout_icon"
|
android:hint="Type"
|
||||||
app:itemRippleColor="#00000000"
|
app:boxCornerRadiusBottomEnd="8dp"
|
||||||
app:itemTextAppearanceActive="@style/NavBarText"
|
app:boxCornerRadiusBottomStart="8dp"
|
||||||
app:itemTextAppearanceInactive="@style/NavBarText"
|
app:boxCornerRadiusTopEnd="8dp"
|
||||||
app:itemTextColor="@color/tab_layout_icon" />
|
app:boxCornerRadiusTopStart="8dp"
|
||||||
|
app:boxStrokeColor="@color/text_input_layout_stroke_color"
|
||||||
|
app:hintAnimationEnabled="true"
|
||||||
|
android:paddingEnd="8dp">
|
||||||
|
|
||||||
|
<AutoCompleteTextView
|
||||||
|
android:id="@+id/sourceType"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:fontFamily="@font/poppins_bold"
|
||||||
|
android:freezesText="false"
|
||||||
|
android:inputType="none"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:text="@string/anime"
|
||||||
|
android:textAllCaps="true"
|
||||||
|
android:textColor="?android:attr/textColorSecondary"
|
||||||
|
android:textSize="14sp"
|
||||||
|
tools:ignore="LabelFor,TextContrastCheck,DuplicateSpeakableTextCheck" />
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:id="@+id/sourceFilterNameContainer"
|
||||||
|
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="56dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:hint="View"
|
||||||
|
app:boxCornerRadiusBottomEnd="8dp"
|
||||||
|
app:boxCornerRadiusBottomStart="8dp"
|
||||||
|
app:boxCornerRadiusTopEnd="8dp"
|
||||||
|
app:boxCornerRadiusTopStart="8dp"
|
||||||
|
app:boxStrokeColor="@color/text_input_layout_stroke_color"
|
||||||
|
app:hintAnimationEnabled="true"
|
||||||
|
android:paddingEnd="8dp">
|
||||||
|
|
||||||
|
<AutoCompleteTextView
|
||||||
|
android:id="@+id/sourceFilter"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:fontFamily="@font/poppins_bold"
|
||||||
|
android:freezesText="false"
|
||||||
|
android:inputType="none"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:text="COUNT"
|
||||||
|
android:textAllCaps="true"
|
||||||
|
android:textColor="?android:attr/textColorSecondary"
|
||||||
|
android:textSize="14sp"
|
||||||
|
tools:ignore="LabelFor,TextContrastCheck,DuplicateSpeakableTextCheck" />
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
<com.github.aachartmodel.aainfographics.aachartcreator.AAChartView
|
<com.github.aachartmodel.aainfographics.aachartcreator.AAChartView
|
||||||
android:id="@+id/formatChartView"
|
android:id="@+id/formatChartView"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue