Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 11 additions & 7 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ plugins {
}

android {
compileSdkVersion 30
buildToolsVersion "30.0.3"
compileSdk 34
namespace "otus.homework.customview"

defaultConfig {
applicationId "otus.homework.customview"
minSdkVersion 23
targetSdkVersion 30
minSdkVersion 31
targetSdkVersion 34
versionCode 1
versionName "1.0"

Expand All @@ -24,11 +24,14 @@ android {
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = '1.8'
jvmTarget = "17"
}
buildFeatures {
viewBinding = true
}
}

Expand All @@ -39,6 +42,7 @@ dependencies {
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.3.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'com.google.code.gson:gson:2.10.1'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
Expand Down
4 changes: 3 additions & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.CustomView">
<activity android:name=".MainActivity">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

Expand Down
9 changes: 9 additions & 0 deletions app/src/main/java/otus/homework/customview/CostDto.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package otus.homework.customview

data class CostDto(
val id: Long,
val name: String,
val amount: Long,
val category: String,
val time: Long,
)
118 changes: 118 additions & 0 deletions app/src/main/java/otus/homework/customview/LineChartView.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package otus.homework.customview

import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.util.AttributeSet
import android.view.View
import java.time.Instant
import java.time.LocalDateTime
import java.time.ZoneId
import java.time.format.DateTimeFormatter
import kotlin.math.max

class LineChartView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {

private var data: List<CostDto> = listOf()
private var colors: List<Pair<Int, Int>> = listOf()

fun setData(values: List<CostDto>, colors: List<Pair<Int, Int>>) {
this.data = values
this.colors = colors
invalidate()
}


private val dateFormatter = DateTimeFormatter.ofPattern("dd.MM")

private val axisPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
color = Color.BLACK
strokeWidth = 5f
}

private val gridPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
color = Color.LTGRAY
strokeWidth = 2f
}

private val linePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
color = Color.BLUE
strokeWidth = 5f
style = Paint.Style.STROKE
}

private val textPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
color = Color.BLACK
textSize = 30f
textAlign = Paint.Align.CENTER
}


override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)

val padding = 100f
val width = width.toFloat()
val height = height.toFloat()

val numGridLines = 5
for (i in 0..numGridLines) {
val y = height - padding - (i * (height - 2 * padding) / numGridLines)
canvas.drawLine(padding, y, width - padding, y, gridPaint)
}

val localDates = data.associateWith {
LocalDateTime.ofInstant(
Instant.ofEpochSecond(it.time),
ZoneId.systemDefault()
)
}

val groupedByCategories = data.groupBy { it.category }

for ((_, categoryData) in groupedByCategories) {
val categoryColor = colors.random().first
linePaint.color = categoryColor

val maxY = categoryData.maxOf { it.amount } * 1.2f

val stepX = (width - 2 * padding) / max(1, categoryData.size - 1)
val stepY = (height - 2 * padding) / maxY

canvas.drawLine(
padding,
height - padding,
width - padding,
height - padding,
axisPaint
)
canvas.drawLine(padding, padding, padding, height - padding, axisPaint)

var prevX: Float? = null
var prevY: Float? = null

for ((index, entry) in categoryData.withIndex()) {
val x = padding + index * stepX
val y = height - padding - (entry.amount * stepY)

if (prevX != null && prevY != null) {
canvas.drawLine(prevX, prevY, x, y, linePaint)
}

canvas.drawCircle(x, y, 10f, linePaint)

val dateText = localDates[entry]?.format(dateFormatter) ?: ""
canvas.drawText(dateText, x, height - padding + 40f, textPaint)

prevX = x
prevY = y
}
}
}

}
57 changes: 55 additions & 2 deletions app/src/main/java/otus/homework/customview/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -1,11 +1,64 @@
package otus.homework.customview

import androidx.appcompat.app.AppCompatActivity
import android.graphics.Color
import android.os.Bundle
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import otus.homework.customview.databinding.ActivityMainBinding
import java.io.InputStreamReader

class MainActivity : AppCompatActivity() {

private lateinit var binding: ActivityMainBinding

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)

val inputStream = resources.openRawResource(R.raw.payload)
val payload = InputStreamReader(inputStream)
val values = readCosts(payload)

initPieChartView(values)
initLineChartView(values)
}

private fun initLineChartView(data: List<CostDto>) {
binding.lineChart.setData(data, getColorsForGradient())
}

private fun initPieChartView(data: List<CostDto>) {
binding.pieChart.setData(data, getColorsForGradient())
binding.pieChart.applyCallback(object : PieChartView.Callback {
override fun onSectorClick(costDto: CostDto) {
Toast.makeText(
this@MainActivity,
"${costDto.name} - ${costDto.amount} USD",
Toast.LENGTH_SHORT
).show()
}
})
}

private fun readCosts(payload: InputStreamReader): List<CostDto> {
val costListType = object : TypeToken<List<CostDto>>() {}.type
return Gson().fromJson(payload, costListType)
}

private fun getColorsForGradient() = listOf(
Color.RED to Color.rgb(255, 165, 0),
Color.BLUE to Color.CYAN,
Color.rgb(138, 43, 226) to Color.rgb(255, 105, 180),
Color.GREEN to Color.rgb(50, 205, 50),
Color.YELLOW to Color.rgb(255, 215, 0),
Color.rgb(255, 0, 0) to Color.rgb(255, 182, 193),
Color.rgb(64, 224, 208) to Color.rgb(0, 191, 255),
Color.BLACK to Color.rgb(169, 169, 169),
Color.rgb(0, 0, 139) to Color.rgb(135, 206, 250),
Color.rgb(128, 0, 128) to Color.rgb(230, 230, 250)
).shuffled()
}
Loading