feat: compare user stats

This commit is contained in:
rebelonion 2024-03-04 22:55:29 -06:00
parent d181dcf837
commit 5279b0cd65
13 changed files with 745 additions and 472 deletions

View file

@ -44,7 +44,7 @@ android {
applicationIdSuffix ".beta" // keep as beta by popular request
versionNameSuffix "-alpha02"
manifestPlaceholders = [icon_placeholder: "@mipmap/ic_launcher_alpha", icon_placeholder_round: "@mipmap/ic_launcher_alpha_round"]
debuggable System.getenv("CI") == null
debuggable true
isDefault true
}
debug {

View file

@ -608,12 +608,11 @@ class AnilistQueries {
private suspend fun bannerImage(type: String): String? {
//var image = loadData<BannerImage>("banner_$type")
val image: BannerImage? = BannerImage(
PrefManager.getCustomVal("banner_${type}_url", null),
val image = BannerImage(
PrefManager.getCustomVal("banner_${type}_url", ""),
PrefManager.getCustomVal("banner_${type}_time", 0L)
)
if (image == null || image.checkTime()) {
if (image.url.isNullOrEmpty() || image.checkTime()) {
val response =
executeQuery<Query.MediaListCollection>("""{ MediaListCollection(userId: ${Anilist.userid}, type: $type, chunk:1,perChunk:25, sort: [SCORE_DESC,UPDATED_TIME_DESC]) { lists { entries{ media { id bannerImage } } } } } """)
val random = response?.data?.mediaListCollection?.lists?.mapNotNull {

View file

@ -299,12 +299,12 @@ class Query {
data class StatisticsResponse(
@SerialName("data")
val data: Data
) {
): java.io.Serializable {
@Serializable
data class Data(
@SerialName("User")
val user: StatisticsUser?
)
): java.io.Serializable
}
@Serializable

View file

@ -1260,7 +1260,7 @@ class ExoplayerView : AppCompatActivity(), Player.Listener, SessionAvailabilityL
subtitle = intent.getSerialized("subtitle")
?: when (val subLang: String? =
PrefManager.getCustomVal("subLang_${media.id}", null as String?)) {
PrefManager.getNullableCustomVal("subLang_${media.id}", null, String::class.java)) {
null -> {
when (episode.selectedSubtitle) {
null -> null

View file

@ -68,7 +68,7 @@ class SubtitleDialogFragment : BottomSheetDialogFragment() {
binding.subtitleTitle.setText(R.string.none)
model.getMedia().observe(viewLifecycleOwner) { media ->
val mediaID: Int = media.id
val selSubs = PrefManager.getCustomVal<String?>("subLang_${mediaID}", null)
val selSubs = PrefManager.getNullableCustomVal("subLang_${mediaID}", null, String::class.java)
if (episode.selectedSubtitle != null && selSubs != "None") {
binding.root.setCardBackgroundColor(TRANSPARENT)
}
@ -108,7 +108,7 @@ class SubtitleDialogFragment : BottomSheetDialogFragment() {
model.getMedia().observe(viewLifecycleOwner) { media ->
val mediaID: Int = media.id
val selSubs: String? =
PrefManager.getCustomVal<String?>("subLang_${mediaID}", null)
PrefManager.getNullableCustomVal("subLang_${mediaID}", null, String::class.java)
if (episode.selectedSubtitle != position - 1 && selSubs != subtitles[position - 1].language) {
binding.root.setCardBackgroundColor(TRANSPARENT)
}

View file

@ -15,6 +15,8 @@ import ani.dantotsu.loadImage
import ani.dantotsu.others.ImageViewDialog
import ani.dantotsu.profile.ProfileActivity
import ani.dantotsu.snackString
import ani.dantotsu.util.ColorEditor.Companion.adjustColorForContrast
import ani.dantotsu.util.ColorEditor.Companion.getContrastRatio
import com.xwray.groupie.GroupieAdapter
import com.xwray.groupie.Section
import com.xwray.groupie.viewbinding.BindableItem
@ -300,25 +302,6 @@ class CommentItem(val comment: Comment,
}
}
private fun getLuminance(color: Int): Double {
val r = Color.red(color) / 255.0
val g = Color.green(color) / 255.0
val b = Color.blue(color) / 255.0
val rL = if (r <= 0.03928) r / 12.92 else ((r + 0.055) / 1.055).pow(2.4)
val gL = if (g <= 0.03928) g / 12.92 else ((g + 0.055) / 1.055).pow(2.4)
val bL = if (b <= 0.03928) b / 12.92 else ((b + 0.055) / 1.055).pow(2.4)
return 0.2126 * rL + 0.7152 * gL + 0.0722 * bL
}
private fun getContrastRatio(color1: Int, color2: Int): Double {
val l1 = getLuminance(color1)
val l2 = getLuminance(color2)
return if (l1 > l2) (l1 + 0.05) / (l2 + 0.05) else (l2 + 0.05) / (l1 + 0.05)
}
private fun getAvatarColor(voteCount: Int, backgroundColor: Int): Pair<Int, Int> {
val level = if (voteCount < 0) 0 else sqrt(abs(voteCount.toDouble()) / 0.8).toInt()
val colorString = if (level > usernameColors.size - 1) usernameColors[usernameColors.size - 1] else usernameColors[level]
@ -331,37 +314,6 @@ class CommentItem(val comment: Comment,
return Pair(color, level)
}
private fun adjustColorForContrast(originalColor: Int, backgroundColor: Int): Int {
var adjustedColor = originalColor
var contrastRatio = getContrastRatio(adjustedColor, backgroundColor)
val isBackgroundDark = getLuminance(backgroundColor) < 0.5
while (contrastRatio < 4.5) {
// Adjust brightness by modifying the RGB values
val r = Color.red(adjustedColor)
val g = Color.green(adjustedColor)
val b = Color.blue(adjustedColor)
// Calculate the amount to adjust
val adjustment = if (isBackgroundDark) 10 else -10
// Adjust the color
val newR = (r + adjustment).coerceIn(0, 255)
val newG = (g + adjustment).coerceIn(0, 255)
val newB = (b + adjustment).coerceIn(0, 255)
adjustedColor = Color.rgb(newR, newG, newB)
contrastRatio = getContrastRatio(adjustedColor, backgroundColor)
// Break the loop if the color adjustment does not change (to avoid infinite loop)
if (newR == r && newG == g && newB == b) {
break
}
}
return adjustedColor
}
/**
* Builds the dialog for yes/no confirmation
* no doesn't do anything, yes calls the callback

View file

@ -440,7 +440,7 @@ class MangaReadAdapter(
if (media.manga?.chapters != null) {
val chapters = media.manga.chapters!!.keys.toTypedArray()
val anilistEp = (media.userProgress ?: 0).plus(1)
val appEp = PrefManager.getCustomVal<String?>("${media.id}_current_chp", null)
val appEp = PrefManager.getNullableCustomVal("${media.id}_current_chp", null, String::class.java)
?.toIntOrNull() ?: 1
var continueEp = (if (anilistEp > appEp) anilistEp else appEp).toString()
val filteredChapters = chapters.filter { chapterKey ->

View file

@ -291,7 +291,7 @@ class NovelReaderActivity : AppCompatActivity(), EbookReaderEventListener {
applySettings()
}
val cfi = PrefManager.getCustomVal("${sanitizedBookId}_progress", null as String?)
val cfi = PrefManager.getNullableCustomVal("${sanitizedBookId}_progress", null, String::class.java)
cfi?.let { binding.bookReader.goto(it) }
binding.progress.visibility = View.GONE

View file

@ -3,6 +3,7 @@ package ani.dantotsu.profile
import android.content.Context
import android.graphics.Color
import android.util.TypedValue
import ani.dantotsu.util.ColorEditor
import com.github.aachartmodel.aainfographics.aachartcreator.AAChartModel
import com.github.aachartmodel.aainfographics.aachartcreator.AAChartStackingType
import com.github.aachartmodel.aainfographics.aachartcreator.AAChartType
@ -17,6 +18,7 @@ import com.github.aachartmodel.aainfographics.aaoptionsmodel.AAScrollablePlotAre
import com.github.aachartmodel.aainfographics.aaoptionsmodel.AAStyle
import com.github.aachartmodel.aainfographics.aaoptionsmodel.AAYAxis
import com.github.aachartmodel.aainfographics.aatools.AAColor
import com.github.aachartmodel.aainfographics.aatools.AAGradientColor
class ChartBuilder {
companion object {
@ -25,25 +27,30 @@ class ChartBuilder {
}
enum class StatType {
COUNT, TIME, MEAN_SCORE
COUNT, TIME, AVG_SCORE
}
enum class MediaType {
ANIME, MANGA
}
data class ChartPacket(
val username: String,
val names: List<Any>,
val statData: List<Number>
)
fun buildChart(
context: Context,
chartType: ChartType,
aaChartType: AAChartType,
passedChartType: ChartType,
passedAaChartType: AAChartType,
statType: StatType,
mediaType: MediaType,
names: List<Any>,
statData: List<Number>,
chartPackets: List<ChartPacket>,
xAxisName: String = "X Axis",
xAxisTickInterval: Int? = null,
polar: Boolean = false,
categories: List<String>? = null,
passedCategories: List<String>? = null,
scrollPos: Float? = null,
): AAOptions {
val typedValue = TypedValue()
@ -53,7 +60,18 @@ class ChartBuilder {
true
)
val primaryColor = typedValue.data
val palette = generateColorPalette(primaryColor, names.size)
var chartType = passedChartType
var aaChartType = passedAaChartType
var categories = passedCategories
if (chartType == ChartType.OneDimensional && chartPackets.size != 1) {
//need to convert to 2D
chartType = ChartType.TwoDimensional
aaChartType = AAChartType.Column
categories = chartPackets[0].names.map { it.toString() }
}
val namesMax = chartPackets.maxOf { it.names.size }
val palette = ColorEditor.generateColorPalette(primaryColor, namesMax)
val aaChartModel = when (chartType) {
ChartType.OneDimensional -> {
val chart = AAChartModel()
@ -61,14 +79,26 @@ class ChartBuilder {
.subtitle(getTypeName(statType, mediaType))
.zoomType(AAChartZoomType.None)
.dataLabelsEnabled(true)
.series(
val elements: MutableList<Any> = mutableListOf()
chartPackets.forEachIndexed { index, chartPacket ->
val element = AASeriesElement()
.name(chartPacket.username)
.data(
get1DElements(
names,
statData,
palette,
primaryColor
chartPacket.names,
chartPacket.statData,
palette
)
)
if (index == 0) {
element.color(primaryColor)
} else {
element.color(ColorEditor.oppositeColor(primaryColor))
}
elements.add(element)
}
chart.series(elements.toTypedArray())
xAxisTickInterval?.let { chart.xAxisTickInterval(it) }
categories?.let { chart.categories(it.toTypedArray()) }
chart
@ -83,9 +113,31 @@ class ChartBuilder {
.zoomType(AAChartZoomType.None)
.dataLabelsEnabled(false)
.yAxisTitle(getTypeName(statType, mediaType))
.stacking(AAChartStackingType.Normal)
.series(get2DElements(names, statData, primaryColor))
.colorsTheme(hexColorsArray)
if (chartPackets.size == 1) {
chart.colorsTheme(hexColorsArray)
}
val elements: MutableList<AASeriesElement> = mutableListOf()
chartPackets.forEachIndexed { index, chartPacket ->
val element = get2DElements(
chartPacket.names,
chartPacket.statData,
chartPackets.size == 1
)
element.name(chartPacket.username)
if (index == 0) {
element.color(AAColor.rgbaColor(Color.red(primaryColor), Color.green(primaryColor), Color.blue(primaryColor), 0.9f))
} else {
element.color(AAColor.rgbaColor(Color.red(ColorEditor.oppositeColor(primaryColor)), Color.green(ColorEditor.oppositeColor(primaryColor)), Color.blue(ColorEditor.oppositeColor(primaryColor)), 0.9f))
}
if (chartPackets.size == 1) {
element.fillColor(AAColor.rgbaColor(Color.red(primaryColor), Color.green(primaryColor), Color.blue(primaryColor), 0.9f))
}
elements.add(element)
}
chart.series(elements.toTypedArray())
xAxisTickInterval?.let { chart.xAxisTickInterval(it) }
categories?.let { chart.categories(it.toTypedArray()) }
@ -101,24 +153,32 @@ class ChartBuilder {
getToolTipFunction(
chartType,
xAxisName,
getTypeName(statType, mediaType)
getTypeName(statType, mediaType),
chartPackets.size
)
)
if (chartPackets.size > 1) {
useHTML(true)
}
}
aaOptions.legend?.apply {
enabled(true)
.labelFormat = "{name}: {y}"
.labelFormat = "{name}"
}
aaOptions.plotOptions?.series?.connectNulls(true)
aaOptions.plotOptions?.series?.connectNulls(false)
aaOptions.plotOptions?.series?.stacking(AAChartStackingType.False)
aaOptions.chart?.panning = true
scrollPos?.let {
aaOptions.chart?.scrollablePlotArea(AAScrollablePlotArea().scrollPositionX(scrollPos))
aaOptions.chart?.scrollablePlotArea?.minWidth((context.resources.displayMetrics.widthPixels.toFloat() / context.resources.displayMetrics.density) * (names.size.toFloat() / 18.0f))
aaOptions.chart?.scrollablePlotArea?.minWidth((context.resources.displayMetrics.widthPixels.toFloat() / context.resources.displayMetrics.density) * (namesMax.toFloat() / 18.0f))
}
val min = ((statData.minOfOrNull { it.toDouble() } ?: 0.0) - 1.0).coerceAtLeast(0.0)
val max = statData.maxOfOrNull { it.toDouble() } ?: 0.0
val aaYaxis = AAYAxis().min(min).max(max)
val allStatData = chartPackets.flatMap { it.statData }
val min = (allStatData.minOfOrNull { it.toDouble() } ?: 0.0) - 1.0
val coercedMin = min.coerceAtLeast(0.0)
val max = allStatData.maxOfOrNull { it.toDouble() } ?: 0.0
val aaYaxis = AAYAxis().min(coercedMin).max(max)
val tickInterval = when (max) {
in 0.0..10.0 -> 1.0
in 10.0..30.0 -> 5.0
@ -138,34 +198,25 @@ class ChartBuilder {
private fun get2DElements(
names: List<Any>,
statData: List<Any>,
primaryColor: Int
): Array<Any> {
colorByPoint: Boolean
): AASeriesElement {
val statValues = mutableListOf<Array<Any>>()
for (i in statData.indices) {
statValues.add(arrayOf(names[i], statData[i], statData[i]))
}
return arrayOf(
AASeriesElement().name("Score")
return AASeriesElement()
.data(statValues.toTypedArray())
.dataLabels(
AADataLabels()
.enabled(false)
)
.colorByPoint(true)
.fillColor(AAColor.rgbaColor(
Color.red(primaryColor),
Color.green(primaryColor),
Color.blue(primaryColor),
0.9f
))
)
.colorByPoint(colorByPoint)
}
private fun get1DElements(
names: List<Any>,
statData: List<Number>,
colors: List<Int>,
primaryColor: Int
colors: List<Int>
): Array<Any> {
val statDataElements = mutableListOf<AADataElement>()
for (i in statData.indices) {
@ -193,44 +244,17 @@ class ChartBuilder {
}
statDataElements.add(element)
}
return arrayOf(
AASeriesElement().name("Score").color(primaryColor)
.data(statDataElements.toTypedArray())
)
return statDataElements.toTypedArray()
}
private fun getTypeName(statType: StatType, mediaType: MediaType): String {
return when (statType) {
StatType.COUNT -> "Count"
StatType.TIME -> if (mediaType == MediaType.ANIME) "Hours Watched" else "Chapters Read"
StatType.MEAN_SCORE -> "Mean Score"
StatType.AVG_SCORE -> "Mean Score"
}
}
private fun generateColorPalette(
baseColor: Int,
size: Int,
hueDelta: Float = 8f,
saturationDelta: Float = 2.02f,
valueDelta: Float = 2.02f
): List<Int> {
val palette = mutableListOf<Int>()
val hsv = FloatArray(3)
Color.colorToHSV(baseColor, hsv)
for (i in 0 until size) {
val newHue =
(hsv[0] + hueDelta * i) % 360 // Ensure hue stays within the 0-360 range
val newSaturation = (hsv[1] + saturationDelta * i).coerceIn(0f, 1f)
val newValue = (hsv[2] + valueDelta * i).coerceIn(0f, 1f)
val newHsv = floatArrayOf(newHue, newSaturation, newValue)
palette.add(Color.HSVToColor(newHsv))
}
return palette
}
private fun setColors(aaOptions: AAOptions, context: Context, primaryColor: Int) {
val backgroundColor = TypedValue()
context.theme.resolveAttribute(
@ -265,15 +289,15 @@ class ChartBuilder {
aaOptions.chart?.backgroundColor(backgroundStyle.color)
aaOptions.tooltip?.backgroundColor(
AAColor.rgbaColor(
Color.red(primaryColor),
Color.green(primaryColor),
Color.blue(primaryColor),
Color.red(backgroundColor.data),
Color.green(backgroundColor.data),
Color.blue(backgroundColor.data),
0.9f
)
)
aaOptions.title?.style(onBackgroundStyle)
aaOptions.subtitle?.style(onBackgroundStyle)
aaOptions.tooltip?.style(backgroundStyle)
aaOptions.tooltip?.style(onBackgroundStyle)
aaOptions.credits?.style(onBackgroundStyle)
aaOptions.xAxis?.labels?.style(onBackgroundStyle)
aaOptions.yAxis?.labels?.style(onBackgroundStyle)
@ -287,7 +311,8 @@ class ChartBuilder {
private fun getToolTipFunction(
chartType: ChartType,
type: String,
typeName: String
typeName: String,
chartSize: Int
): String {
return when (chartType) {
ChartType.OneDimensional -> {
@ -305,6 +330,7 @@ class ChartBuilder {
}
ChartType.TwoDimensional -> {
if (chartSize == 1) {
"""
function () {
return '$type: ' +
@ -313,9 +339,35 @@ class ChartBuilder {
' $typeName ' +
this.y
}
""".trimIndent()
} else {
"""
function() {
let wholeContentStr = '<span style=\"' + 'color:gray; font-size:13px\"' + '>◉${type}: ' + this.x + '</span><br/>';
if (this.points) {
let length = this.points.length;
for (let i = 0; i < length; i++) {
let thisPoint = this.points[i];
let yValue = thisPoint.y;
if (yValue != 0) {
let spanStyleStartStr = '<span style=\"' + 'color: ' + thisPoint.color + '; font-size:13px\"' + '>◉ ';
let spanStyleEndStr = '</span> <br/>';
wholeContentStr += spanStyleStartStr + thisPoint.series.name + ': ' + yValue + spanStyleEndStr;
}
}
} else {
let spanStyleStartStr = '<span style=\"' + 'color: ' + this.point.color + '; font-size:13px\"' + '>◉ ';
let spanStyleEndStr = '</span> <br/>';
wholeContentStr += spanStyleStartStr + this.point.series.name + ': ' + this.point.y + spanStyleEndStr;
}
return wholeContentStr;
}
""".trimIndent()
}
}
}
}
}
}

View file

@ -15,19 +15,19 @@ import ani.dantotsu.databinding.FragmentStatisticsBinding
import ani.dantotsu.profile.ChartBuilder.Companion.ChartType
import ani.dantotsu.profile.ChartBuilder.Companion.StatType
import ani.dantotsu.profile.ChartBuilder.Companion.MediaType
import ani.dantotsu.profile.ChartBuilder.Companion.ChartPacket
import com.github.aachartmodel.aainfographics.aachartcreator.AAChartType
import com.github.aachartmodel.aainfographics.aaoptionsmodel.AAYAxis
import com.xwray.groupie.GroupieAdapter
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.util.Locale
class StatsFragment() :
class StatsFragment :
Fragment() {
private lateinit var binding: FragmentStatisticsBinding
private var adapter: GroupieAdapter = GroupieAdapter()
private var stats: Query.StatisticsResponse? = null
private var stats: MutableList<Query.StatisticsUser?> = mutableListOf()
private var type: MediaType = MediaType.ANIME
private var statType: StatType = StatType.COUNT
private lateinit var user: Query.UserProfile
@ -52,25 +52,55 @@ class StatsFragment() :
binding.statisticList.isNestedScrollingEnabled = false
binding.statisticList.layoutManager = LinearLayoutManager(requireContext())
binding.statisticProgressBar.visibility = View.VISIBLE
binding.compare.visibility = if (user.id == Anilist.userid) View.GONE else View.VISIBLE
binding.sourceType.setAdapter(
ArrayAdapter(
requireContext(),
R.layout.item_dropdown,
MediaType.entries.map { it.name.uppercase(Locale.ROOT) }
MediaType.entries.map { it.name.uppercase(Locale.ROOT).replace("_", " ") }
)
)
binding.sourceFilter.setAdapter(
ArrayAdapter(
requireContext(),
R.layout.item_dropdown,
StatType.entries.map { it.name.uppercase(Locale.ROOT) }
StatType.entries.map { it.name.uppercase(Locale.ROOT).replace("_", " ") }
)
)
binding.compare.setOnCheckedChangeListener { _, isChecked ->
if (isChecked) {
activity.lifecycleScope.launch {
if (Anilist.userid != null) {
withContext(Dispatchers.Main) {
binding.statisticProgressBar.visibility = View.VISIBLE
binding.statisticList.visibility = View.GONE
}
val userStats =
Anilist.query.getUserStatistics(Anilist.userid!!)?.data?.user
if (userStats != null) {
stats.add(userStats)
withContext(Dispatchers.Main) {
loadStats(type == MediaType.ANIME)
binding.statisticProgressBar.visibility = View.GONE
binding.statisticList.visibility = View.VISIBLE
}
}
}
}
} else {
stats.removeAll(
stats.filter { it?.id == Anilist.userid }
)
loadStats(type == MediaType.ANIME)
}
}
binding.filterContainer.visibility = View.GONE
activity.lifecycleScope.launch {
stats = Anilist.query.getUserStatistics(user.id)
stats.clear()
stats.add(Anilist.query.getUserStatistics(user.id)?.data?.user)
withContext(Dispatchers.Main) {
binding.filterContainer.visibility = View.VISIBLE
binding.sourceType.setOnItemClickListener { _, _, i, _ ->
@ -116,99 +146,114 @@ class StatsFragment() :
}
private fun loadFormatChart(anime: Boolean) {
val chartPackets = mutableListOf<ChartPacket>()
stats.forEach {stat ->
val names: List<String> = if (anime) {
stats?.data?.user?.statistics?.anime?.formats?.map { it.format } ?: emptyList()
stat?.statistics?.anime?.formats?.map { it.format } ?: emptyList()
} else {
stats?.data?.user?.statistics?.manga?.formats?.map { it.format } ?: emptyList()
stat?.statistics?.manga?.formats?.map { it.format } ?: emptyList()
}
val values: List<Number> = if (anime) {
when (statType) {
StatType.COUNT -> stats?.data?.user?.statistics?.anime?.formats?.map { it.count }
StatType.TIME -> stats?.data?.user?.statistics?.anime?.formats?.map { it.minutesWatched / 60 }
StatType.MEAN_SCORE -> stats?.data?.user?.statistics?.anime?.formats?.map { it.meanScore }
StatType.COUNT -> stat?.statistics?.anime?.formats?.map { it.count }
StatType.TIME -> stat?.statistics?.anime?.formats?.map { it.minutesWatched / 60 }
StatType.AVG_SCORE -> stat?.statistics?.anime?.formats?.map { it.meanScore }
} ?: emptyList()
} else {
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 }
StatType.COUNT -> stat?.statistics?.manga?.formats?.map { it.count }
StatType.TIME -> stat?.statistics?.manga?.formats?.map { it.chaptersRead }
StatType.AVG_SCORE -> stat?.statistics?.manga?.formats?.map { it.meanScore }
} ?: emptyList()
}
if (names.isNotEmpty() || values.isNotEmpty()) {
if (names.isNotEmpty() && values.isNotEmpty()) {
chartPackets.add(ChartPacket(stat?.name?:"Unknown", names, values))
}
}
if (chartPackets.isNotEmpty()) {
val formatChart = ChartBuilder.buildChart(
activity,
ChartType.OneDimensional,
AAChartType.Pie,
statType,
type,
names,
values
chartPackets,
)
adapter.add(ChartItem("Format", formatChart, activity))
}
}
private fun loadStatusChart(anime: Boolean) {
val chartPackets = mutableListOf<ChartPacket>()
stats.forEach { stat ->
val names: List<String> = if (anime) {
stats?.data?.user?.statistics?.anime?.statuses?.map { it.status } ?: emptyList()
stat?.statistics?.anime?.statuses?.map { it.status } ?: emptyList()
} else {
stats?.data?.user?.statistics?.manga?.statuses?.map { it.status } ?: emptyList()
stat?.statistics?.manga?.statuses?.map { it.status } ?: emptyList()
}
val values: List<Number> = if (anime) {
when (statType) {
StatType.COUNT -> stats?.data?.user?.statistics?.anime?.statuses?.map { it.count }
StatType.TIME -> stats?.data?.user?.statistics?.anime?.statuses?.map { it.minutesWatched / 60 }
StatType.MEAN_SCORE -> stats?.data?.user?.statistics?.anime?.statuses?.map { it.meanScore }
StatType.COUNT -> stat?.statistics?.anime?.statuses?.map { it.count }
StatType.TIME -> stat?.statistics?.anime?.statuses?.map { it.minutesWatched / 60 }
StatType.AVG_SCORE -> stat?.statistics?.anime?.statuses?.map { it.meanScore }
} ?: emptyList()
} else {
when (statType) {
StatType.COUNT -> stats?.data?.user?.statistics?.manga?.statuses?.map { it.count }
StatType.TIME -> stats?.data?.user?.statistics?.manga?.statuses?.map { it.chaptersRead }
StatType.MEAN_SCORE -> stats?.data?.user?.statistics?.manga?.statuses?.map { it.meanScore }
StatType.COUNT -> stat?.statistics?.manga?.statuses?.map { it.count }
StatType.TIME -> stat?.statistics?.manga?.statuses?.map { it.chaptersRead }
StatType.AVG_SCORE -> stat?.statistics?.manga?.statuses?.map { it.meanScore }
} ?: emptyList()
}
if (names.isNotEmpty() || values.isNotEmpty()) {
if (names.isNotEmpty() && values.isNotEmpty()) {
chartPackets.add(ChartPacket(stat?.name ?: "Unknown", names, values))
}
}
if (chartPackets.isNotEmpty()) {
val statusChart = ChartBuilder.buildChart(
activity,
ChartType.OneDimensional,
AAChartType.Funnel,
statType,
type,
names,
values
chartPackets
)
adapter.add(ChartItem("Status", statusChart, activity))
}
}
private fun loadScoreChart(anime: Boolean) {
val chartPackets = mutableListOf<ChartPacket>()
stats.forEach { stat ->
val names: List<Int> = if (anime) {
stats?.data?.user?.statistics?.anime?.scores?.map { it.score } ?: emptyList()
stat?.statistics?.anime?.scores?.map { it.score } ?: emptyList()
} else {
stats?.data?.user?.statistics?.manga?.scores?.map { it.score } ?: emptyList()
stat?.statistics?.manga?.scores?.map { it.score } ?: emptyList()
}
val values: List<Number> = if (anime) {
when (statType) {
StatType.COUNT -> stats?.data?.user?.statistics?.anime?.scores?.map { it.count }
StatType.TIME -> stats?.data?.user?.statistics?.anime?.scores?.map { it.minutesWatched / 60 }
StatType.MEAN_SCORE -> stats?.data?.user?.statistics?.anime?.scores?.map { it.meanScore }
StatType.COUNT -> stat?.statistics?.anime?.scores?.map { it.count }
StatType.TIME -> stat?.statistics?.anime?.scores?.map { it.minutesWatched / 60 }
StatType.AVG_SCORE -> stat?.statistics?.anime?.scores?.map { it.meanScore }
} ?: emptyList()
} else {
when (statType) {
StatType.COUNT -> stats?.data?.user?.statistics?.manga?.scores?.map { it.count }
StatType.TIME -> stats?.data?.user?.statistics?.manga?.scores?.map { it.chaptersRead }
StatType.MEAN_SCORE -> stats?.data?.user?.statistics?.manga?.scores?.map { it.meanScore }
StatType.COUNT -> stat?.statistics?.manga?.scores?.map { it.count }
StatType.TIME -> stat?.statistics?.manga?.scores?.map { it.chaptersRead }
StatType.AVG_SCORE -> stat?.statistics?.manga?.scores?.map { it.meanScore }
} ?: emptyList()
}
if (names.isNotEmpty() || values.isNotEmpty()) {
chartPackets.add(ChartPacket(stat?.name ?: "Unknown", names, values))
}
}
if (chartPackets.isNotEmpty()) {
val scoreChart = ChartBuilder.buildChart(
activity,
ChartType.TwoDimensional,
AAChartType.Column,
statType,
type,
names,
values,
chartPackets,
xAxisName = "Score",
)
adapter.add(ChartItem("Score", scoreChart, activity))
@ -216,35 +261,40 @@ class StatsFragment() :
}
private fun loadLengthChart(anime: Boolean) {
val chartPackets = mutableListOf<ChartPacket>()
stats.forEach { stat ->
val names: List<String> = if (anime) {
stats?.data?.user?.statistics?.anime?.lengths?.map { it.length ?: "unknown" }
stat?.statistics?.anime?.lengths?.map { it.length ?: "unknown" }
?: emptyList()
} else {
stats?.data?.user?.statistics?.manga?.lengths?.map { it.length ?: "unknown" }
stat?.statistics?.manga?.lengths?.map { it.length ?: "unknown" }
?: emptyList()
}
val values: List<Number> = if (anime) {
when (statType) {
StatType.COUNT -> stats?.data?.user?.statistics?.anime?.lengths?.map { it.count }
StatType.TIME -> stats?.data?.user?.statistics?.anime?.lengths?.map { it.minutesWatched / 60 }
StatType.MEAN_SCORE -> stats?.data?.user?.statistics?.anime?.lengths?.map { it.meanScore }
StatType.COUNT -> stat?.statistics?.anime?.lengths?.map { it.count }
StatType.TIME -> stat?.statistics?.anime?.lengths?.map { it.minutesWatched / 60 }
StatType.AVG_SCORE -> stat?.statistics?.anime?.lengths?.map { it.meanScore }
} ?: emptyList()
} else {
when (statType) {
StatType.COUNT -> stats?.data?.user?.statistics?.manga?.lengths?.map { it.count }
StatType.TIME -> stats?.data?.user?.statistics?.manga?.lengths?.map { it.chaptersRead }
StatType.MEAN_SCORE -> stats?.data?.user?.statistics?.manga?.lengths?.map { it.meanScore }
StatType.COUNT -> stat?.statistics?.manga?.lengths?.map { it.count }
StatType.TIME -> stat?.statistics?.manga?.lengths?.map { it.chaptersRead }
StatType.AVG_SCORE -> stat?.statistics?.manga?.lengths?.map { it.meanScore }
} ?: emptyList()
}
if (names.isNotEmpty() || values.isNotEmpty()) {
chartPackets.add(ChartPacket(stat?.name ?: "Unknown", names, values))
}
}
if (chartPackets.isNotEmpty()) {
val lengthChart = ChartBuilder.buildChart(
activity,
ChartType.OneDimensional,
AAChartType.Pyramid,
statType,
type,
names,
values,
chartPackets,
xAxisName = "Length",
)
adapter.add(ChartItem("Length", lengthChart, activity))
@ -252,35 +302,40 @@ class StatsFragment() :
}
private fun loadReleaseYearChart(anime: Boolean) {
val chartPackets = mutableListOf<ChartPacket>()
stats.forEach { stat ->
val names: List<Number> = if (anime) {
stats?.data?.user?.statistics?.anime?.releaseYears?.map { it.releaseYear }
stat?.statistics?.anime?.releaseYears?.map { it.releaseYear }
?: emptyList()
} else {
stats?.data?.user?.statistics?.manga?.releaseYears?.map { it.releaseYear }
stat?.statistics?.manga?.releaseYears?.map { it.releaseYear }
?: emptyList()
}
val values: List<Number> = if (anime) {
when (statType) {
StatType.COUNT -> stats?.data?.user?.statistics?.anime?.releaseYears?.map { it.count }
StatType.TIME -> stats?.data?.user?.statistics?.anime?.releaseYears?.map { it.minutesWatched / 60 }
StatType.MEAN_SCORE -> stats?.data?.user?.statistics?.anime?.releaseYears?.map { it.meanScore }
StatType.COUNT -> stat?.statistics?.anime?.releaseYears?.map { it.count }
StatType.TIME -> stat?.statistics?.anime?.releaseYears?.map { it.minutesWatched / 60 }
StatType.AVG_SCORE -> stat?.statistics?.anime?.releaseYears?.map { it.meanScore }
} ?: emptyList()
} else {
when (statType) {
StatType.COUNT -> stats?.data?.user?.statistics?.manga?.releaseYears?.map { it.count }
StatType.TIME -> stats?.data?.user?.statistics?.manga?.releaseYears?.map { it.chaptersRead }
StatType.MEAN_SCORE -> stats?.data?.user?.statistics?.manga?.releaseYears?.map { it.meanScore }
StatType.COUNT -> stat?.statistics?.manga?.releaseYears?.map { it.count }
StatType.TIME -> stat?.statistics?.manga?.releaseYears?.map { it.chaptersRead }
StatType.AVG_SCORE -> stat?.statistics?.manga?.releaseYears?.map { it.meanScore }
} ?: emptyList()
}
if (names.isNotEmpty() || values.isNotEmpty()) {
chartPackets.add(ChartPacket(stat?.name ?: "Unknown", names, values))
}
}
if (chartPackets.isNotEmpty()) {
val releaseYearChart = ChartBuilder.buildChart(
activity,
ChartType.TwoDimensional,
AAChartType.Bubble,
statType,
type,
names,
values,
chartPackets,
xAxisName = "Year",
)
adapter.add(ChartItem("Release Year", releaseYearChart, activity))
@ -288,33 +343,38 @@ class StatsFragment() :
}
private fun loadStartYearChart(anime: Boolean) {
val chartPackets = mutableListOf<ChartPacket>()
stats.forEach { stat ->
val names: List<Number> = if (anime) {
stats?.data?.user?.statistics?.anime?.startYears?.map { it.startYear } ?: emptyList()
stat?.statistics?.anime?.startYears?.map { it.startYear } ?: emptyList()
} else {
stats?.data?.user?.statistics?.manga?.startYears?.map { it.startYear } ?: emptyList()
stat?.statistics?.manga?.startYears?.map { it.startYear } ?: emptyList()
}
val values: List<Number> = if (anime) {
when (statType) {
StatType.COUNT -> stats?.data?.user?.statistics?.anime?.startYears?.map { it.count }
StatType.TIME -> stats?.data?.user?.statistics?.anime?.startYears?.map { it.minutesWatched / 60 }
StatType.MEAN_SCORE -> stats?.data?.user?.statistics?.anime?.startYears?.map { it.meanScore }
StatType.COUNT -> stat?.statistics?.anime?.startYears?.map { it.count }
StatType.TIME -> stat?.statistics?.anime?.startYears?.map { it.minutesWatched / 60 }
StatType.AVG_SCORE -> stat?.statistics?.anime?.startYears?.map { it.meanScore }
} ?: emptyList()
} else {
when (statType) {
StatType.COUNT -> stats?.data?.user?.statistics?.manga?.startYears?.map { it.count }
StatType.TIME -> stats?.data?.user?.statistics?.manga?.startYears?.map { it.chaptersRead }
StatType.MEAN_SCORE -> stats?.data?.user?.statistics?.manga?.startYears?.map { it.meanScore }
StatType.COUNT -> stat?.statistics?.manga?.startYears?.map { it.count }
StatType.TIME -> stat?.statistics?.manga?.startYears?.map { it.chaptersRead }
StatType.AVG_SCORE -> stat?.statistics?.manga?.startYears?.map { it.meanScore }
} ?: emptyList()
}
if (names.isNotEmpty() || values.isNotEmpty()) {
chartPackets.add(ChartPacket(stat?.name ?: "Unknown", names, values))
}
}
if (chartPackets.isNotEmpty()) {
val startYearChart = ChartBuilder.buildChart(
activity,
ChartType.TwoDimensional,
AAChartType.Bar,
statType,
type,
names,
values,
chartPackets,
xAxisName = "Year",
)
adapter.add(ChartItem("Start Year", startYearChart, activity))
@ -322,112 +382,160 @@ class StatsFragment() :
}
private fun loadGenreChart(anime: Boolean) {
val chartPackets = mutableListOf<ChartPacket>()
stats.forEach { stat ->
val names: List<String> = if (anime) {
stats?.data?.user?.statistics?.anime?.genres?.map { it.genre } ?: emptyList()
stat?.statistics?.anime?.genres?.map { it.genre } ?: emptyList()
} else {
stats?.data?.user?.statistics?.manga?.genres?.map { it.genre } ?: emptyList()
stat?.statistics?.manga?.genres?.map { it.genre } ?: emptyList()
}
val values: List<Number> = if (anime) {
when (statType) {
StatType.COUNT -> stats?.data?.user?.statistics?.anime?.genres?.map { it.count }
StatType.TIME -> stats?.data?.user?.statistics?.anime?.genres?.map { it.minutesWatched / 60 }
StatType.MEAN_SCORE -> stats?.data?.user?.statistics?.anime?.genres?.map { it.meanScore }
StatType.COUNT -> stat?.statistics?.anime?.genres?.map { it.count }
StatType.TIME -> stat?.statistics?.anime?.genres?.map { it.minutesWatched / 60 }
StatType.AVG_SCORE -> stat?.statistics?.anime?.genres?.map { it.meanScore }
} ?: emptyList()
} else {
when (statType) {
StatType.COUNT -> stats?.data?.user?.statistics?.manga?.genres?.map { it.count }
StatType.TIME -> stats?.data?.user?.statistics?.manga?.genres?.map { it.chaptersRead }
StatType.MEAN_SCORE -> stats?.data?.user?.statistics?.manga?.genres?.map { it.meanScore }
StatType.COUNT -> stat?.statistics?.manga?.genres?.map { it.count }
StatType.TIME -> stat?.statistics?.manga?.genres?.map { it.chaptersRead }
StatType.AVG_SCORE -> stat?.statistics?.manga?.genres?.map { it.meanScore }
} ?: emptyList()
}
if (names.isNotEmpty() || values.isNotEmpty()) {
chartPackets.add(ChartPacket(stat?.name ?: "Unknown", names, values))
}
}
if (chartPackets.isNotEmpty()) {
val referenceNames = chartPackets.first().names.map { it.toString() }
val standardizedPackets = chartPackets.map { packet ->
val valuesMap = packet.names.map { it.toString() }.zip(packet.statData).toMap()
val standardizedValues = referenceNames.map { name ->
valuesMap[name] ?: 0
}
// Create a new ChartPacket with standardized names and values.
ChartPacket(packet.username, referenceNames, standardizedValues)
}.toMutableList()
chartPackets.clear()
chartPackets.addAll(standardizedPackets)
val genreChart = ChartBuilder.buildChart(
activity,
ChartType.TwoDimensional,
AAChartType.Areaspline,
statType,
type,
names,
values,
chartPackets,
xAxisName = "Genre",
polar = true,
categories = names
passedCategories = chartPackets[0].names as List<String>,
)
adapter.add(ChartItem("Genre", genreChart, activity))
}
}
private fun loadTagChart(anime: Boolean) {
val chartPackets = mutableListOf<ChartPacket>()
stats.forEach { stat ->
val names: List<String> = if (anime) {
stats?.data?.user?.statistics?.anime?.tags?.map { it.tag.name } ?: emptyList()
stat?.statistics?.anime?.tags?.map { it.tag.name } ?: emptyList()
} else {
stats?.data?.user?.statistics?.manga?.tags?.map { it.tag.name } ?: emptyList()
stat?.statistics?.manga?.tags?.map { it.tag.name } ?: emptyList()
}
val values: List<Number> = if (anime) {
when (statType) {
StatType.COUNT -> stats?.data?.user?.statistics?.anime?.tags?.map { it.count }
StatType.TIME -> stats?.data?.user?.statistics?.anime?.tags?.map { it.minutesWatched / 60 }
StatType.MEAN_SCORE -> stats?.data?.user?.statistics?.anime?.tags?.map { it.meanScore }
StatType.COUNT -> stat?.statistics?.anime?.tags?.map { it.count }
StatType.TIME -> stat?.statistics?.anime?.tags?.map { it.minutesWatched / 60 }
StatType.AVG_SCORE -> stat?.statistics?.anime?.tags?.map { it.meanScore }
} ?: emptyList()
} else {
when (statType) {
StatType.COUNT -> stats?.data?.user?.statistics?.manga?.tags?.map { it.count }
StatType.TIME -> stats?.data?.user?.statistics?.manga?.tags?.map { it.chaptersRead }
StatType.MEAN_SCORE -> stats?.data?.user?.statistics?.manga?.tags?.map { it.meanScore }
StatType.COUNT -> stat?.statistics?.manga?.tags?.map { it.count }
StatType.TIME -> stat?.statistics?.manga?.tags?.map { it.chaptersRead }
StatType.AVG_SCORE -> stat?.statistics?.manga?.tags?.map { it.meanScore }
} ?: emptyList()
}
if (names.isNotEmpty() || values.isNotEmpty()) {
val min = values.minOf { it.toInt() }
val max = values.maxOf { it.toInt() }
chartPackets.add(ChartPacket(stat?.name ?: "Unknown", names, values))
}
}
if (chartPackets.isNotEmpty()) {
val referenceNames = chartPackets.first().names.map { it.toString() }
val standardizedPackets = chartPackets.map { packet ->
val valuesMap = packet.names.map { it.toString() }.zip(packet.statData).toMap()
val standardizedValues = referenceNames.map { name ->
valuesMap[name] ?: 0
}
// Create a new ChartPacket with standardized names and values.
ChartPacket(packet.username, referenceNames, standardizedValues)
}.toMutableList()
chartPackets.clear()
chartPackets.addAll(standardizedPackets)
val tagChart = ChartBuilder.buildChart(
activity,
ChartType.TwoDimensional,
AAChartType.Areaspline,
statType,
type,
names,
values,
chartPackets,
xAxisName = "Tag",
polar = false,
categories = names,
passedCategories = chartPackets[0].names as List<String>,
scrollPos = 0.0f
)
tagChart.yAxis = AAYAxis().min(min).max(max).tickInterval(if (max > 100) 20 else 10)
adapter.add(ChartItem("Tag", tagChart, activity))
}
}
private fun loadCountryChart(anime: Boolean) {
val chartPackets = mutableListOf<ChartPacket>()
stats.forEach { stat ->
val names: List<String> = if (anime) {
stats?.data?.user?.statistics?.anime?.countries?.map { it.country } ?: emptyList()
stat?.statistics?.anime?.countries?.map { it.country } ?: emptyList()
} else {
stats?.data?.user?.statistics?.manga?.countries?.map { it.country } ?: emptyList()
stat?.statistics?.manga?.countries?.map { it.country } ?: emptyList()
}
val values: List<Number> = if (anime) {
when (statType) {
StatType.COUNT -> stats?.data?.user?.statistics?.anime?.countries?.map { it.count }
StatType.TIME -> stats?.data?.user?.statistics?.anime?.countries?.map { it.minutesWatched / 60 }
StatType.MEAN_SCORE -> stats?.data?.user?.statistics?.anime?.countries?.map { it.meanScore }
StatType.COUNT -> stat?.statistics?.anime?.countries?.map { it.count }
StatType.TIME -> stat?.statistics?.anime?.countries?.map { it.minutesWatched / 60 }
StatType.AVG_SCORE -> stat?.statistics?.anime?.countries?.map { it.meanScore }
} ?: emptyList()
} else {
when (statType) {
StatType.COUNT -> stats?.data?.user?.statistics?.manga?.countries?.map { it.count }
StatType.TIME -> stats?.data?.user?.statistics?.manga?.countries?.map { it.chaptersRead }
StatType.MEAN_SCORE -> stats?.data?.user?.statistics?.manga?.countries?.map { it.meanScore }
StatType.COUNT -> stat?.statistics?.manga?.countries?.map { it.count }
StatType.TIME -> stat?.statistics?.manga?.countries?.map { it.chaptersRead }
StatType.AVG_SCORE -> stat?.statistics?.manga?.countries?.map { it.meanScore }
} ?: emptyList()
}
if (names.isNotEmpty() || values.isNotEmpty()) {
chartPackets.add(ChartPacket(stat?.name ?: "Unknown", names, values))
}
}
if (chartPackets.isNotEmpty()) {
val referenceNames = chartPackets.first().names.map { it.toString() }
val standardizedPackets = chartPackets.map { packet ->
val valuesMap = packet.names.map { it.toString() }.zip(packet.statData).toMap()
val standardizedValues = referenceNames.map { name ->
valuesMap[name] ?: 0
}
// Create a new ChartPacket with standardized names and values.
ChartPacket(packet.username, referenceNames, standardizedValues)
}.toMutableList()
chartPackets.clear()
chartPackets.addAll(standardizedPackets)
val countryChart = ChartBuilder.buildChart(
activity,
ChartType.OneDimensional,
AAChartType.Pie,
statType,
type,
names,
values,
chartPackets,
xAxisName = "Country",
polar = false,
categories = names,
passedCategories = chartPackets[0].names as List<String>,
scrollPos = null
)
adapter.add(ChartItem("Country", countryChart, activity))
@ -435,116 +543,167 @@ class StatsFragment() :
}
private fun loadVoiceActorsChart(anime: Boolean) {
val chartPackets = mutableListOf<ChartPacket>()
stats.forEach { stat ->
val names: List<String> = if (anime) {
stats?.data?.user?.statistics?.anime?.voiceActors?.map { it.voiceActor.name.full?:"unknown" } ?: emptyList()
stat?.statistics?.anime?.voiceActors?.map { it.voiceActor.name.full?:"unknown" } ?: emptyList()
} else {
stats?.data?.user?.statistics?.manga?.voiceActors?.map { it.voiceActor.name.full?:"unknown" } ?: emptyList()
stat?.statistics?.manga?.voiceActors?.map { it.voiceActor.name.full?:"unknown" } ?: emptyList()
}
val values: List<Number> = if (anime) {
when (statType) {
StatType.COUNT -> stats?.data?.user?.statistics?.anime?.voiceActors?.map { it.count }
StatType.TIME -> stats?.data?.user?.statistics?.anime?.voiceActors?.map { it.minutesWatched / 60 }
StatType.MEAN_SCORE -> stats?.data?.user?.statistics?.anime?.voiceActors?.map { it.meanScore }
StatType.COUNT -> stat?.statistics?.anime?.voiceActors?.map { it.count }
StatType.TIME -> stat?.statistics?.anime?.voiceActors?.map { it.minutesWatched / 60 }
StatType.AVG_SCORE -> stat?.statistics?.anime?.voiceActors?.map { it.meanScore }
} ?: emptyList()
} else {
when (statType) {
StatType.COUNT -> stats?.data?.user?.statistics?.manga?.voiceActors?.map { it.count }
StatType.TIME -> stats?.data?.user?.statistics?.manga?.voiceActors?.map { it.chaptersRead }
StatType.MEAN_SCORE -> stats?.data?.user?.statistics?.manga?.voiceActors?.map { it.meanScore }
StatType.COUNT -> stat?.statistics?.manga?.voiceActors?.map { it.count }
StatType.TIME -> stat?.statistics?.manga?.voiceActors?.map { it.chaptersRead }
StatType.AVG_SCORE -> stat?.statistics?.manga?.voiceActors?.map { it.meanScore }
} ?: emptyList()
}
if (names.isNotEmpty() || values.isNotEmpty()) {
chartPackets.add(ChartPacket(stat?.name ?: "Unknown", names, values))
}
}
if (chartPackets.isNotEmpty()) {
val referenceNames = chartPackets.first().names.map { it.toString() }
val standardizedPackets = chartPackets.map { packet ->
val valuesMap = packet.names.map { it.toString() }.zip(packet.statData).toMap()
val standardizedValues = referenceNames.map { name ->
valuesMap[name] ?: 0
}
// Create a new ChartPacket with standardized names and values.
ChartPacket(packet.username, referenceNames, standardizedValues)
}.toMutableList()
chartPackets.clear()
chartPackets.addAll(standardizedPackets)
val voiceActorsChart = ChartBuilder.buildChart(
activity,
ChartType.TwoDimensional,
AAChartType.Column,
statType,
type,
names,
values,
chartPackets,
xAxisName = "Voice Actor",
polar = false,
categories = names,
passedCategories = chartPackets[0].names as List<String>,
scrollPos = 0.0f
)
adapter.add(ChartItem("Voice Actor", voiceActorsChart, activity))
}
}
private fun loadStaffChart(anime: Boolean) {
private fun loadStudioChart(anime: Boolean) {
val chartPackets = mutableListOf<ChartPacket>()
stats.forEach { stat ->
val names: List<String> = if (anime) {
stats?.data?.user?.statistics?.anime?.staff?.map { it.staff.name.full?:"unknown" } ?: emptyList()
stat?.statistics?.anime?.studios?.map { it.studio.name } ?: emptyList()
} else {
stats?.data?.user?.statistics?.manga?.staff?.map { it.staff.name.full?:"unknown" } ?: emptyList()
stat?.statistics?.manga?.studios?.map { it.studio.name } ?: emptyList()
}
val values: List<Number> = if (anime) {
when (statType) {
StatType.COUNT -> stats?.data?.user?.statistics?.anime?.staff?.map { it.count }
StatType.TIME -> stats?.data?.user?.statistics?.anime?.staff?.map { it.minutesWatched / 60 }
StatType.MEAN_SCORE -> stats?.data?.user?.statistics?.anime?.staff?.map { it.meanScore }
StatType.COUNT -> stat?.statistics?.anime?.studios?.map { it.count }
StatType.TIME -> stat?.statistics?.anime?.studios?.map { it.minutesWatched / 60 }
StatType.AVG_SCORE -> stat?.statistics?.anime?.studios?.map { it.meanScore }
} ?: emptyList()
} else {
when (statType) {
StatType.COUNT -> stats?.data?.user?.statistics?.manga?.staff?.map { it.count }
StatType.TIME -> stats?.data?.user?.statistics?.manga?.staff?.map { it.chaptersRead }
StatType.MEAN_SCORE -> stats?.data?.user?.statistics?.manga?.staff?.map { it.meanScore }
StatType.COUNT -> stat?.statistics?.manga?.studios?.map { it.count }
StatType.TIME -> stat?.statistics?.manga?.studios?.map { it.chaptersRead }
StatType.AVG_SCORE -> stat?.statistics?.manga?.studios?.map { it.meanScore }
} ?: emptyList()
}
if (names.isNotEmpty() || values.isNotEmpty()) {
val staffChart = ChartBuilder.buildChart(
activity,
ChartType.TwoDimensional,
AAChartType.Line,
statType,
type,
names,
values,
xAxisName = "Staff",
polar = false,
categories = names,
scrollPos = 0.0f
)
adapter.add(ChartItem("Staff", staffChart, activity))
chartPackets.add(ChartPacket(stat?.name ?: "Unknown", names, values))
}
}
if (chartPackets.isNotEmpty()) {
val referenceNames = chartPackets.first().names.map { it.toString() }
val standardizedPackets = chartPackets.map { packet ->
val valuesMap = packet.names.map { it.toString() }.zip(packet.statData).toMap()
val standardizedValues = referenceNames.map { name ->
valuesMap[name] ?: 0
}
private fun loadStudioChart(anime: Boolean) {
val names: List<String> = if (anime) {
stats?.data?.user?.statistics?.anime?.studios?.map { it.studio.name } ?: emptyList()
} else {
stats?.data?.user?.statistics?.manga?.studios?.map { it.studio.name } ?: emptyList()
}
val values: List<Number> = if (anime) {
when (statType) {
StatType.COUNT -> stats?.data?.user?.statistics?.anime?.studios?.map { it.count }
StatType.TIME -> stats?.data?.user?.statistics?.anime?.studios?.map { it.minutesWatched / 60 }
StatType.MEAN_SCORE -> stats?.data?.user?.statistics?.anime?.studios?.map { it.meanScore }
} ?: emptyList()
} else {
when (statType) {
StatType.COUNT -> stats?.data?.user?.statistics?.manga?.studios?.map { it.count }
StatType.TIME -> stats?.data?.user?.statistics?.manga?.studios?.map { it.chaptersRead }
StatType.MEAN_SCORE -> stats?.data?.user?.statistics?.manga?.studios?.map { it.meanScore }
} ?: emptyList()
}
if (names.isNotEmpty() || values.isNotEmpty()) {
// Create a new ChartPacket with standardized names and values.
ChartPacket(packet.username, referenceNames, standardizedValues)
}.toMutableList()
chartPackets.clear()
chartPackets.addAll(standardizedPackets)
val studioChart = ChartBuilder.buildChart(
activity,
ChartType.TwoDimensional,
AAChartType.Spline,
statType,
type,
names.take(15),
values.take(15),
chartPackets,
xAxisName = "Studio",
polar = true,
categories = names,
passedCategories = chartPackets[0].names as List<String>,
scrollPos = null
)
adapter.add(ChartItem("Studio", studioChart, activity))
}
}
private fun loadStaffChart(anime: Boolean) {
val chartPackets = mutableListOf<ChartPacket>()
stats.forEach { stat ->
val names: List<String> = if (anime) {
stat?.statistics?.anime?.staff?.map { it.staff.name.full?:"unknown" } ?: emptyList()
} else {
stat?.statistics?.manga?.staff?.map { it.staff.name.full?:"unknown" } ?: emptyList()
}
val values: List<Number> = if (anime) {
when (statType) {
StatType.COUNT -> stat?.statistics?.anime?.staff?.map { it.count }
StatType.TIME -> stat?.statistics?.anime?.staff?.map { it.minutesWatched / 60 }
StatType.AVG_SCORE -> stat?.statistics?.anime?.staff?.map { it.meanScore }
} ?: emptyList()
} else {
when (statType) {
StatType.COUNT -> stat?.statistics?.manga?.staff?.map { it.count }
StatType.TIME -> stat?.statistics?.manga?.staff?.map { it.chaptersRead }
StatType.AVG_SCORE -> stat?.statistics?.manga?.staff?.map { it.meanScore }
} ?: emptyList()
}
if (names.isNotEmpty() || values.isNotEmpty()) {
chartPackets.add(ChartPacket(stat?.name ?: "Unknown", names, values))
}
}
if (chartPackets.isNotEmpty()) {
val referenceNames = chartPackets.first().names.map { it.toString() }
val standardizedPackets = chartPackets.map { packet ->
val valuesMap = packet.names.map { it.toString() }.zip(packet.statData).toMap()
val standardizedValues = referenceNames.map { name ->
valuesMap[name] ?: 0
}
// Create a new ChartPacket with standardized names and values.
ChartPacket(packet.username, referenceNames, standardizedValues)
}.toMutableList()
chartPackets.clear()
chartPackets.addAll(standardizedPackets)
val staffChart = ChartBuilder.buildChart(
activity,
ChartType.TwoDimensional,
AAChartType.Line,
statType,
type,
chartPackets,
xAxisName = "Staff",
polar = false,
passedCategories = chartPackets[0].names as List<String>,
scrollPos = 0.0f
)
adapter.add(ChartItem("Staff", staffChart, activity))
}
}
companion object {
fun newInstance(user: Query.UserProfile): StatsFragment {
val args = Bundle().apply {

View file

@ -0,0 +1,88 @@
package ani.dantotsu.util
import android.graphics.Color
import kotlin.math.pow
class ColorEditor {
companion object {
fun oppositeColor(color: Int): Int {
val hsv = FloatArray(3)
Color.colorToHSV(color, hsv)
hsv[0] = (hsv[0] + 180) % 360
return adjustColorForContrast(Color.HSVToColor(hsv), color)
}
fun generateColorPalette(
baseColor: Int,
size: Int,
hueDelta: Float = 8f,
saturationDelta: Float = 2.02f,
valueDelta: Float = 2.02f
): List<Int> {
val palette = mutableListOf<Int>()
val hsv = FloatArray(3)
Color.colorToHSV(baseColor, hsv)
for (i in 0 until size) {
val newHue =
(hsv[0] + hueDelta * i) % 360 // Ensure hue stays within the 0-360 range
val newSaturation = (hsv[1] + saturationDelta * i).coerceIn(0f, 1f)
val newValue = (hsv[2] + valueDelta * i).coerceIn(0f, 1f)
val newHsv = floatArrayOf(newHue, newSaturation, newValue)
palette.add(Color.HSVToColor(newHsv))
}
return palette
}
fun getLuminance(color: Int): Double {
val r = Color.red(color) / 255.0
val g = Color.green(color) / 255.0
val b = Color.blue(color) / 255.0
val rL = if (r <= 0.03928) r / 12.92 else ((r + 0.055) / 1.055).pow(2.4)
val gL = if (g <= 0.03928) g / 12.92 else ((g + 0.055) / 1.055).pow(2.4)
val bL = if (b <= 0.03928) b / 12.92 else ((b + 0.055) / 1.055).pow(2.4)
return 0.2126 * rL + 0.7152 * gL + 0.0722 * bL
}
fun getContrastRatio(color1: Int, color2: Int): Double {
val l1 = getLuminance(color1)
val l2 = getLuminance(color2)
return if (l1 > l2) (l1 + 0.05) / (l2 + 0.05) else (l2 + 0.05) / (l1 + 0.05)
}
fun adjustColorForContrast(originalColor: Int, backgroundColor: Int): Int {
var adjustedColor = originalColor
var contrastRatio = getContrastRatio(adjustedColor, backgroundColor)
val isBackgroundDark = getLuminance(backgroundColor) < 0.5
while (contrastRatio < 4.5) {
// Adjust brightness by modifying the RGB values
val r = Color.red(adjustedColor)
val g = Color.green(adjustedColor)
val b = Color.blue(adjustedColor)
// Calculate the amount to adjust
val adjustment = if (isBackgroundDark) 10 else -10
// Adjust the color
val newR = (r + adjustment).coerceIn(0, 255)
val newG = (g + adjustment).coerceIn(0, 255)
val newB = (b + adjustment).coerceIn(0, 255)
adjustedColor = Color.rgb(newR, newG, newB)
contrastRatio = getContrastRatio(adjustedColor, backgroundColor)
// Break the loop if the color adjustment does not change (to avoid infinite loop)
if (newR == r && newG == g && newB == b) {
break
}
}
return adjustedColor
}
}
}

View file

@ -13,9 +13,15 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:baselineAligned="false"
android:orientation="horizontal"
android:orientation="vertical"
android:padding="16dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:baselineAligned="false"
android:orientation="horizontal">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/sourceTypeNameContainer"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu"
@ -81,6 +87,22 @@
android:textSize="14sp"
tools:ignore="LabelFor,TextContrastCheck,DuplicateSpeakableTextCheck" />
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
<CheckBox
android:id="@+id/compare"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:enabled="true"
android:fontFamily="@font/poppins_bold"
android:textColor="?attr/colorPrimary"
android:layout_gravity="center_horizontal"
android:paddingTop="4dp"
android:insetTop="0dp"
android:insetBottom="0dp"
android:text="@string/compare"
android:textSize="12sp"
android:focusable="true" />
</LinearLayout>

View file

@ -665,6 +665,7 @@
<string name="oldest">Oldest</string>
<string name="highest_rated">Highest rated</string>
<string name="lowest_rated">Lowest rated</string>
<string name="compare"><u>Compare</u></string>
</resources>