Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NSabirov HW #53

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
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
41 changes: 27 additions & 14 deletions app/build.gradle
Original file line number Diff line number Diff line change
@@ -1,28 +1,36 @@
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-parcelize'
id 'kotlin-kapt'
}

android {
compileSdkVersion 30
compileSdkVersion 33
buildToolsVersion "30.0.3"

defaultConfig {
applicationId "otus.homework.customview"
minSdkVersion 23
targetSdkVersion 30
applicationId "com.otus.securehomework"
minSdkVersion 21
targetSdkVersion 33
versionCode 1
versionName "1.0"

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
debug {
debuggable true
}
}

buildFeatures {
viewBinding true
}

compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
Expand All @@ -34,12 +42,17 @@ android {

dependencies {

implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.3.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
implementation 'androidx.core:core-ktx:1.10.0'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'androidx.annotation:annotation:1.6.0'
implementation 'com.google.android.material:material:1.8.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation "androidx.fragment:fragment-ktx:1.5.6"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4"
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
implementation 'com.google.code.gson:gson:2.10.1'
implementation 'joda-time:joda-time:2.12.5'
}
3 changes: 2 additions & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
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
52 changes: 52 additions & 0 deletions app/src/main/java/otus/homework/customview/ColorGenerator.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package otus.homework.customview


import android.graphics.Color
import androidx.annotation.ColorInt
import java.util.*

object ColorGenerator {

@ColorInt
fun generateColor(): Int {
val rnd = Random()
return Color.argb(255, rnd.nextInt(256), rnd.nextInt(256), rnd.nextInt(256))
}

fun generatePalette(
size: Int,
baseColor: Int,
adjacentColors: Boolean
): IntArray {
val colors = IntArray(size)
colors[0] = baseColor

val hsv = FloatArray(3)
Color.RGBToHSV(
Color.red(baseColor), Color.green(baseColor), Color.blue(baseColor),
hsv
)

val step = 240.0 / size
val baseHue = hsv[0]

for (i in 1 until size) {
val nextColorHue = (baseHue + step * i) % 240.0
colors[i] = Color.HSVToColor(floatArrayOf(nextColorHue.toFloat(), hsv[1], hsv[2]))
}

if (!adjacentColors && size > 2) {
var i = 0
var j = size / 2

while (j < size) {
colors[i] = colors[j].also { colors[j] = colors[i] }

i += 2
j += 2
}
}

return colors
}
}
7 changes: 7 additions & 0 deletions app/src/main/java/otus/homework/customview/Ext.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package otus.homework.customview

import android.content.res.Resources
import android.util.DisplayMetrics

fun Int.dpToPx(res: Resources): Int =
(this.toFloat() * (res.displayMetrics.densityDpi.toFloat() / DisplayMetrics.DENSITY_DEFAULT)).toInt()
143 changes: 143 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,143 @@
package otus.homework.customview

import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.RectF
import android.os.Parcel
import android.os.Parcelable
import android.util.AttributeSet
import android.view.View
import otus.homework.customview.models.Spend

class LineChartView(context: Context, attrs: AttributeSet?) : View(context, attrs) {
private var minChartSizeX = 400.dpToPx(context.resources)
private var minChartSizeY = 400.dpToPx(context.resources)

private var groupedItems: Map<String, List<Spend>> = emptyMap()

private var rectF: RectF = RectF()
private val strokeSize = 16.dpToPx(context.resources).toFloat()

private val axisPaint = Paint().apply {
isAntiAlias = true
isDither = true
style = Paint.Style.STROKE
color = Color.BLACK
strokeWidth = strokeSize
}
private val linePaint = Paint().apply {
isAntiAlias = true
isDither = true
style = Paint.Style.STROKE
color = Color.BLUE
strokeWidth = strokeSize / 4
}
private val dotPaint = Paint().apply {
isAntiAlias = true
isDither = true
style = Paint.Style.FILL_AND_STROKE
color = Color.BLUE
strokeWidth = strokeSize / 2
}

private var selectedIndex = -1

fun setItems(list: Map<String, List<Spend>>) {
groupedItems = list
}

fun setSelectedIndex(i: Int){
selectedIndex = i
invalidate()
}

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val chartWidth =
Integer.max(minChartSizeX, suggestedMinimumWidth + paddingLeft + paddingRight)
val chartHeight =
Integer.max(minChartSizeY, suggestedMinimumHeight + paddingTop + paddingBottom)

val sizeX = resolveSize(chartWidth, widthMeasureSpec)
val sizeY = resolveSize(chartHeight, heightMeasureSpec)

setMeasuredDimension(sizeX, sizeY)
}

override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
val startTop = 0f
val startLeft = 0f
val endBottom = height.toFloat()
val rightBottom = height.toFloat()
rectF.set(
startLeft + strokeSize, startTop, endBottom,
rightBottom - strokeSize
)
canvas.drawLine(0f, 0f, 0f, height.toFloat(), axisPaint)
canvas.drawLine(0f, height.toFloat(), width.toFloat(), height.toFloat(), axisPaint)
if (selectedIndex != -1 && groupedItems.isNotEmpty()) {
val key = groupedItems.keys.toList()[selectedIndex]
val item = groupedItems[key]
if (item?.isNotEmpty() == true) {
val maxTime = item.maxBy{ it.time }
val maxAmount = item.maxBy { it.amount }
var startX = 0f
var startY = height.toFloat()
item.forEach {
val pxX = width.toFloat()*it.time/maxTime.time
val pxY = height.toFloat()*it.amount/maxAmount.amount
canvas.drawLine(startX, startY, pxX, pxY, linePaint)
canvas.drawPoint(pxX, pxY, dotPaint)
startX = pxX
startY = pxY
}
}
}
}

override fun onSaveInstanceState(): Parcelable {
val superState = super.onSaveInstanceState()

val savedState = SavedState(superState)

savedState.selectedIndex = selectedIndex
savedState.groupedItems = groupedItems

return savedState
}

override fun onRestoreInstanceState(state: Parcelable?) {
if (state !is SavedState) {
super.onRestoreInstanceState(state)
return
}

super.onRestoreInstanceState(state.superState)

groupedItems = state.groupedItems
selectedIndex = state.selectedIndex
invalidate()
}

internal class SavedState : BaseSavedState {
var groupedItems: Map<String, List<Spend>> = emptyMap()
var selectedIndex: Int = -1

constructor(superState: Parcelable?) : super(superState)

private constructor(parcel: Parcel) : super(parcel) {
groupedItems = emptyMap()
parcel.readMap(groupedItems, Map::class.java.classLoader)
selectedIndex = parcel.readInt()

}

override fun writeToParcel(out: Parcel, flags: Int) {
super.writeToParcel(out, flags)
out.writeMap(groupedItems)
out.writeInt(selectedIndex)
}
}
}
80 changes: 79 additions & 1 deletion app/src/main/java/otus/homework/customview/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -1,11 +1,89 @@
package otus.homework.customview

import androidx.appcompat.app.AppCompatActivity
import android.os.Build
import android.os.Bundle
import android.view.View
import android.widget.Button
import android.widget.TextView
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
import com.google.gson.GsonBuilder
import com.google.gson.reflect.TypeToken
import otus.homework.customview.models.Spend
import java.io.IOException
import java.io.InputStream

class MainActivity : AppCompatActivity() {
private var index = 0

@RequiresApi(Build.VERSION_CODES.O)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val textView = findViewById<TextView>(R.id.tvCategory)
val btnPrev = findViewById<Button>(R.id.btnPrev)
val btnNext = findViewById<Button>(R.id.btnNext)
val chart = findViewById<LineChartView>(R.id.chart)
val list = readJson()
val groupedItems = list.groupBy { it.category }

btnPrev.apply {
if (groupedItems.isEmpty()) {
visibility = View.GONE
} else {
visibility = View.VISIBLE
setOnClickListener {
if (index == 0) {
index = groupedItems.size-1
} else {
index--
}
chart.setSelectedIndex(index)
textView.text = groupedItems.keys.toList()[index]
}
}
}
btnNext.apply {
if (groupedItems.isEmpty()) {
visibility = View.GONE
} else {
visibility = View.VISIBLE
setOnClickListener {
if (index == groupedItems.size-1) {
index = 0
} else {
index++
}
chart.setSelectedIndex(index)
textView.text = groupedItems.keys.toList()[index]
}
}
}

chart.setItems(groupedItems)
if (groupedItems.isNotEmpty()) {
chart.setSelectedIndex(index)
textView.text = groupedItems.keys.toList()[index]
}
}

private fun readJson(): List<Spend> {
return try {
var json: String? = null
val inputStream: InputStream = resources.openRawResource(
resources.getIdentifier(
"payload",
"raw", packageName
)
)

json = inputStream.bufferedReader().use { it.readText() }
val itemType = object : TypeToken<List<Spend>>() {}.type
val gson = GsonBuilder().create()
gson.fromJson<List<Spend>>(json, itemType)

} catch (e: IOException) {
emptyList()
}
}
}
Loading