feat: compare user stats
This commit is contained in:
parent
d181dcf837
commit
5279b0cd65
13 changed files with 745 additions and 472 deletions
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 ->
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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: ' +
|
||||
|
@ -312,10 +338,36 @@ class ChartBuilder {
|
|||
'<br/> ' +
|
||||
' $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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
|
88
app/src/main/java/ani/dantotsu/util/ColorEditor.kt
Normal file
88
app/src/main/java/ani/dantotsu/util/ColorEditor.kt
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue