diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index b809120..70f0da1 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -61,6 +61,7 @@ dependencies {
implementation(project(path = ":feature:authorization"))
implementation(project(path = ":feature:registration"))
implementation(project(path = ":feature:graph"))
+ implementation(project(path = ":feature:customview"))
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.appcompat)
diff --git a/app/src/main/java/com/example/androiddevelopment2/nav/NavMainImpl.kt b/app/src/main/java/com/example/androiddevelopment2/nav/NavMainImpl.kt
index 202b5d1..759efab 100644
--- a/app/src/main/java/com/example/androiddevelopment2/nav/NavMainImpl.kt
+++ b/app/src/main/java/com/example/androiddevelopment2/nav/NavMainImpl.kt
@@ -33,4 +33,8 @@ class NavMainImpl @Inject constructor(
override fun goToGraphPage() {
navigatorDelegate.navigate(action = R.id.action_global_graph)
}
+
+ override fun goToCustomPage() {
+ navigatorDelegate.navigate(action = R.id.action_global_custom)
+ }
}
diff --git a/app/src/main/res/navigation/graph_global.xml b/app/src/main/res/navigation/graph_global.xml
index b36ff83..46d75b5 100644
--- a/app/src/main/res/navigation/graph_global.xml
+++ b/app/src/main/res/navigation/graph_global.xml
@@ -29,4 +29,9 @@
android:id="@+id/action_global_graph"
app:destination="@id/nav_graph"/>
+
+
+
\ No newline at end of file
diff --git a/core/base/src/main/res/values/strings.xml b/core/base/src/main/res/values/strings.xml
index 3d78c03..d728980 100644
--- a/core/base/src/main/res/values/strings.xml
+++ b/core/base/src/main/res/values/strings.xml
@@ -72,4 +72,15 @@
Данные получены из кэша
Данные получены из API
+
+ Overlapping Pie Chart
+ Tap on sectors to select them
+ 3
+ 4
+ 5
+ 6
+ Clear Selection
+
+
+
\ No newline at end of file
diff --git a/core/navigation/src/main/java/com/example/androiddevelopment2/navigation/NavMain.kt b/core/navigation/src/main/java/com/example/androiddevelopment2/navigation/NavMain.kt
index 1288738..0dc66e8 100644
--- a/core/navigation/src/main/java/com/example/androiddevelopment2/navigation/NavMain.kt
+++ b/core/navigation/src/main/java/com/example/androiddevelopment2/navigation/NavMain.kt
@@ -9,4 +9,6 @@ interface NavMain {
fun goToDetailsPage(recipeId: Int)
fun goToGraphPage()
+
+ fun goToCustomPage()
}
\ No newline at end of file
diff --git a/feature/customview/.gitignore b/feature/customview/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/feature/customview/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/feature/customview/build.gradle.kts b/feature/customview/build.gradle.kts
new file mode 100644
index 0000000..28662dd
--- /dev/null
+++ b/feature/customview/build.gradle.kts
@@ -0,0 +1,64 @@
+plugins {
+ alias(libs.plugins.android.library)
+ alias(libs.plugins.kotlin.android)
+ alias(libs.plugins.ksp)
+ alias(libs.plugins.hilt)
+ id("androidx.navigation.safeargs.kotlin")
+}
+
+android {
+ namespace = "com.example.androiddevelopment2.customview"
+ compileSdk = libs.versions.compileSdk.get().toInt()
+
+ defaultConfig {
+ minSdk = libs.versions.minSdk.get().toInt()
+
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles("consumer-rules.pro")
+ }
+
+ buildTypes {
+ release {
+ isMinifyEnabled = false
+ proguardFiles(
+ getDefaultProguardFile("proguard-android-optimize.txt"),
+ "proguard-rules.pro"
+ )
+ }
+ }
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_11
+ targetCompatibility = JavaVersion.VERSION_11
+ }
+ kotlinOptions {
+ jvmTarget = "11"
+ }
+ buildFeatures {
+ viewBinding = true
+ }
+}
+
+dependencies {
+
+ implementation(project(path = ":core:base"))
+ implementation(project(path = ":core:domain"))
+ implementation(project(path = ":core:utils"))
+ implementation(project(path = ":core:navigation"))
+
+ implementation(libs.androidx.core.ktx)
+ implementation(libs.androidx.appcompat)
+ implementation(libs.material)
+ testImplementation(libs.junit)
+ androidTestImplementation(libs.androidx.junit)
+ androidTestImplementation(libs.androidx.espresso.core)
+
+ // Hilt
+ implementation(libs.hilt)
+ ksp(libs.hilt.compiler)
+
+ // VBPD
+ implementation(libs.viewbindingpropertydelegate.noreflection)
+
+ implementation(libs.navigation.fragment)
+ implementation(libs.navigation.ui)
+}
\ No newline at end of file
diff --git a/feature/customview/consumer-rules.pro b/feature/customview/consumer-rules.pro
new file mode 100644
index 0000000..e69de29
diff --git a/feature/customview/proguard-rules.pro b/feature/customview/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/feature/customview/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/feature/customview/src/androidTest/java/com/example/androiddevelopment2/customview/ExampleInstrumentedTest.kt b/feature/customview/src/androidTest/java/com/example/androiddevelopment2/customview/ExampleInstrumentedTest.kt
new file mode 100644
index 0000000..16569de
--- /dev/null
+++ b/feature/customview/src/androidTest/java/com/example/androiddevelopment2/customview/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package com.example.androiddevelopment2.customview
+
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getInstrumentation().targetContext
+ assertEquals("com.example.androiddevelopment2.customview.test", appContext.packageName)
+ }
+}
\ No newline at end of file
diff --git a/feature/customview/src/main/AndroidManifest.xml b/feature/customview/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..a5918e6
--- /dev/null
+++ b/feature/customview/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/feature/customview/src/main/java/com/example/androiddevelopment2/customview/PieChartFragment.kt b/feature/customview/src/main/java/com/example/androiddevelopment2/customview/PieChartFragment.kt
new file mode 100644
index 0000000..150ef5d
--- /dev/null
+++ b/feature/customview/src/main/java/com/example/androiddevelopment2/customview/PieChartFragment.kt
@@ -0,0 +1,83 @@
+package com.example.androiddevelopment2.customview
+
+import android.annotation.SuppressLint
+import android.os.Bundle
+import android.view.View
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.viewModels
+import androidx.lifecycle.lifecycleScope
+import by.kirich1409.viewbindingdelegate.viewBinding
+import com.example.androiddevelopment2.customview.PieChartView.Companion.NO_SELECTION
+import com.example.androiddevelopment2.customview.databinding.FragmentPieChartBinding
+import com.example.androiddevelopment2.customview.state.PieChartEvent
+import com.example.androiddevelopment2.customview.state.PieChartState
+import dagger.hilt.android.AndroidEntryPoint
+import kotlinx.coroutines.launch
+
+
+@AndroidEntryPoint
+class PieChartFragment : Fragment(R.layout.fragment_pie_chart) {
+ private val viewBinding: FragmentPieChartBinding by viewBinding(FragmentPieChartBinding::bind)
+
+ private val viewModel: PieChartViewModel by viewModels()
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ setupPieChart()
+ setupClickListeners()
+ observeViewModel()
+ }
+
+ private fun setupPieChart() {
+ viewBinding.pieChartView.apply {
+ onSelectionChanged = { index ->
+ if (index == NO_SELECTION) {
+ viewModel.reduce(PieChartEvent.OnClearSelection)
+ } else {
+ viewModel.reduce(PieChartEvent.OnSectorSelected(index))
+ }
+ }
+ }
+ }
+
+ @SuppressLint("ClickableViewAccessibility")
+ private fun setupClickListeners() {
+ viewBinding.apply {
+ root.setOnClickListener {
+ viewModel.reduce(PieChartEvent.OnClearSelection)
+ }
+ btn3Sectors.setOnClickListener {
+ viewModel.reduce(PieChartEvent.OnSectorCountChanged(3))
+ }
+ btn4Sectors.setOnClickListener {
+ viewModel.reduce(PieChartEvent.OnSectorCountChanged(4))
+ }
+ btn5Sectors.setOnClickListener {
+ viewModel.reduce(PieChartEvent.OnSectorCountChanged(5))
+ }
+ btn6Sectors.setOnClickListener {
+ viewModel.reduce(PieChartEvent.OnSectorCountChanged(6))
+ }
+ btnClearSelection.setOnClickListener { viewModel.reduce(PieChartEvent.OnClearSelection) }
+ }
+ }
+
+ private fun observeViewModel() {
+ viewLifecycleOwner.lifecycleScope.launch {
+ viewModel.state.collect { state ->
+ when (state) {
+ is PieChartState.Initial -> {
+ }
+ is PieChartState.Success -> {
+ viewBinding.pieChartView.updateData(
+ count = state.sectorCount,
+ colors = state.colors,
+ selectedIndex = state.selectedSector
+ )
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/feature/customview/src/main/java/com/example/androiddevelopment2/customview/PieChartView.kt b/feature/customview/src/main/java/com/example/androiddevelopment2/customview/PieChartView.kt
new file mode 100644
index 0000000..e6cc54b
--- /dev/null
+++ b/feature/customview/src/main/java/com/example/androiddevelopment2/customview/PieChartView.kt
@@ -0,0 +1,284 @@
+package com.example.androiddevelopment2.customview
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.graphics.*
+import android.util.AttributeSet
+import android.view.MotionEvent
+import android.view.View
+import kotlin.math.atan2
+import kotlin.math.cos
+import kotlin.math.min
+import kotlin.math.pow
+import kotlin.math.sin
+import kotlin.math.sqrt
+import androidx.core.graphics.toColorInt
+
+class PieChartView @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = 0
+) : View(context, attrs, defStyleAttr) {
+
+ companion object {
+ private const val DEFAULT_SECTOR_COUNT = 3
+ const val NO_SELECTION = -1
+ private const val RADIUS_RATIO = 0.8f
+ private const val STROKE_WIDTH_RATIO = 0.6f
+ private const val CENTER_BORDER_WIDTH = 2f
+ private const val TEXT_SIZE_RATIO = 0.6f
+ private const val TEXT_VERTICAL_OFFSET = 3f
+ private const val LIGHTEN_FACTOR = 0.3f
+ private const val START_ANGLE = 270f
+ private const val DARK_GRAY_COLOR = "#333333"
+ private const val LIGHT_GRAY_COLOR = "#E0E0E0"
+ private const val SHADOW_COLOR = "#20000000"
+ }
+
+ private var sectorCount = DEFAULT_SECTOR_COUNT
+ private var sectorColors: List = emptyList()
+ private var selectedIndex = NO_SELECTION
+
+ private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
+ style = Paint.Style.FILL
+ }
+
+ private val textPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
+ textAlign = Paint.Align.CENTER
+ typeface = Typeface.create(Typeface.DEFAULT, Typeface.BOLD)
+ color = DARK_GRAY_COLOR.toColorInt()
+ }
+
+ private val shadowPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
+ color = SHADOW_COLOR.toColorInt()
+ maskFilter = BlurMaskFilter(8f, BlurMaskFilter.Blur.NORMAL)
+ }
+
+ private val centerPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
+ style = Paint.Style.FILL
+ color = Color.WHITE
+ }
+
+ private val centerBorderPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
+ style = Paint.Style.STROKE
+ color = LIGHT_GRAY_COLOR.toColorInt()
+ strokeWidth = CENTER_BORDER_WIDTH
+ }
+
+ init {
+ isClickable = true
+
+ attrs?.let { attributeSet ->
+ val typedArray = context.obtainStyledAttributes(
+ attributeSet,
+ R.styleable.PieChartView,
+ defStyleAttr,
+ 0
+ )
+
+ try {
+ val count =
+ typedArray.getInt(R.styleable.PieChartView_sectorsCount, DEFAULT_SECTOR_COUNT)
+ val colorsResId = typedArray.getResourceId(R.styleable.PieChartView_sectorColors, 0)
+
+ val colors = if (colorsResId != 0) {
+ context.resources.getIntArray(colorsResId).toList()
+ } else {
+ emptyList()
+ }
+
+ updateData(count, colors)
+ } finally {
+ typedArray.recycle()
+ }
+ }
+ }
+
+ fun updateData(count: Int, colors: List, selectedIndex: Int = NO_SELECTION) {
+ if (count < 0 || colors.isEmpty()) return
+
+ this.sectorCount = count
+ this.sectorColors = colors
+ this.selectedIndex = selectedIndex.coerceIn(NO_SELECTION, count - 1)
+ invalidate()
+ }
+
+ override fun onDraw(canvas: Canvas) {
+ super.onDraw(canvas)
+ if (sectorCount <= 0 || sectorColors.isEmpty()) return
+
+ val centerX = width / 2f
+ val centerY = height / 2f
+ val radius = min(centerX, centerY) * RADIUS_RATIO
+ val strokeWidth = radius * STROKE_WIDTH_RATIO
+ val innerRadius = radius - strokeWidth
+
+ drawShadow(canvas, centerX, centerY, radius + 4f, innerRadius - 4f)
+
+ for (i in sectorCount - 1 downTo 0) {
+ drawSectorBody(canvas, centerX, centerY, radius, innerRadius, strokeWidth, i)
+ }
+
+ for (i in sectorCount - 1 downTo 0) {
+ drawSectorCap(canvas, centerX, centerY, radius, innerRadius, strokeWidth, i)
+ }
+
+ drawCenter(canvas, centerX, centerY, innerRadius)
+ }
+
+ private fun drawSectorBody(
+ canvas: Canvas,
+ centerX: Float,
+ centerY: Float,
+ outerRadius: Float,
+ innerRadius: Float,
+ strokeWidth: Float,
+ sectorIndex: Int
+ ) {
+ val sweepAngle = 360f / sectorCount
+ val startAngle = START_ANGLE + sectorIndex * sweepAngle
+ val endAngle = startAngle + sweepAngle
+
+ paint.color = getSectorColor(sectorIndex)
+
+ val path = Path()
+
+ val startAngleRad = Math.toRadians(startAngle.toDouble()).toFloat()
+ val endAngleRad = Math.toRadians(endAngle.toDouble()).toFloat()
+
+ val startOuterX = centerX + outerRadius * cos(startAngleRad)
+ val startOuterY = centerY + outerRadius * sin(startAngleRad)
+ val startInnerX = centerX + innerRadius * cos(startAngleRad)
+ val startInnerY = centerY + innerRadius * sin(startAngleRad)
+ val endInnerX = centerX + innerRadius * cos(endAngleRad)
+ val endInnerY = centerY + innerRadius * sin(endAngleRad)
+
+ path.moveTo(startInnerX, startInnerY)
+ path.lineTo(startOuterX, startOuterY)
+ path.arcTo(
+ RectF(
+ centerX - outerRadius,
+ centerY - outerRadius,
+ centerX + outerRadius,
+ centerY + outerRadius
+ ),
+ startAngle,
+ sweepAngle,
+ false
+ )
+ path.lineTo(endInnerX, endInnerY)
+ path.arcTo(
+ RectF(
+ centerX - innerRadius,
+ centerY - innerRadius,
+ centerX + innerRadius,
+ centerY + innerRadius
+ ),
+ endAngle,
+ -sweepAngle,
+ false
+ )
+ path.close()
+ canvas.drawPath(path, paint)
+ }
+
+ private fun drawSectorCap(
+ canvas: Canvas,
+ centerX: Float,
+ centerY: Float,
+ outerRadius: Float,
+ innerRadius: Float,
+ strokeWidth: Float,
+ sectorIndex: Int
+ ) {
+ val sweepAngle = 360f / sectorCount
+ val startAngle = 270f + sectorIndex * sweepAngle
+ val endAngle = startAngle + sweepAngle
+
+ val endAngleRad = Math.toRadians(endAngle.toDouble()).toFloat()
+
+ val capRadius = strokeWidth / 2f
+ val capMidRadius = innerRadius + capRadius
+
+ val capCenterX = centerX + capMidRadius * cos(endAngleRad)
+ val capCenterY = centerY + capMidRadius * sin(endAngleRad)
+
+ paint.color = getSectorColor(sectorIndex)
+ canvas.drawCircle(capCenterX, capCenterY, capRadius, paint)
+ }
+
+ private fun drawShadow(
+ canvas: Canvas,
+ centerX: Float,
+ centerY: Float,
+ outerRadius: Float,
+ innerRadius: Float
+ ) {
+ Path().apply {
+ addCircle(centerX, centerY, outerRadius, Path.Direction.CW)
+ addCircle(centerX, centerY, innerRadius, Path.Direction.CCW)
+ fillType = Path.FillType.EVEN_ODD
+ canvas.drawPath(this, shadowPaint)
+ }
+ }
+
+ private fun drawCenter(canvas: Canvas, centerX: Float, centerY: Float, innerRadius: Float) {
+ canvas.drawCircle(centerX, centerY, innerRadius, centerPaint)
+ canvas.drawCircle(centerX, centerY, innerRadius, centerBorderPaint)
+
+ textPaint.textSize = innerRadius * TEXT_SIZE_RATIO
+ canvas.drawText(
+ sectorCount.toString(),
+ centerX,
+ centerY + textPaint.textSize / TEXT_VERTICAL_OFFSET,
+ textPaint
+ )
+ }
+
+ var onSelectionChanged: ((Int) -> Unit)? = null
+
+ @SuppressLint("ClickableViewAccessibility")
+ override fun onTouchEvent(event: MotionEvent): Boolean {
+ when (event.action) {
+ MotionEvent.ACTION_DOWN -> {
+ val touchedSector = getTouchedSector(event.x, event.y)
+ selectedIndex = touchedSector
+ invalidate()
+ onSelectionChanged?.invoke(touchedSector)
+ return true
+ }
+ }
+ return super.onTouchEvent(event)
+ }
+
+ private fun getTouchedSector(x: Float, y: Float): Int {
+ val centerX = width / 2f
+ val centerY = height / 2f
+ val radius = min(centerX, centerY) * RADIUS_RATIO
+ val strokeWidth = radius * STROKE_WIDTH_RATIO
+ val innerRadius = radius - strokeWidth
+
+ val distance = sqrt((x - centerX).pow(2) + (y - centerY).pow(2))
+
+ if (distance < innerRadius || distance > radius) {
+ return NO_SELECTION
+ }
+
+ var angle = Math.toDegrees(atan2(y - centerY, x - centerX).toDouble()).toFloat()
+ angle = (angle + 360 + 90) % 360
+ return (angle / (360f / sectorCount)).toInt().coerceIn(0, sectorCount - 1)
+ }
+
+ private fun getSectorColor(index: Int): Int {
+ val color = sectorColors[index % sectorColors.size]
+ return if (index == selectedIndex) lightenColor(color) else color
+ }
+
+ private fun lightenColor(color: Int): Int {
+ return Color.rgb(
+ (Color.red(color) + (255 - Color.red(color)) * LIGHTEN_FACTOR).toInt(),
+ (Color.green(color) + (255 - Color.green(color)) * LIGHTEN_FACTOR).toInt(),
+ (Color.blue(color) + (255 - Color.blue(color)) * LIGHTEN_FACTOR).toInt()
+ )
+ }
+}
\ No newline at end of file
diff --git a/feature/customview/src/main/java/com/example/androiddevelopment2/customview/PieChartViewModel.kt b/feature/customview/src/main/java/com/example/androiddevelopment2/customview/PieChartViewModel.kt
new file mode 100644
index 0000000..3018fdc
--- /dev/null
+++ b/feature/customview/src/main/java/com/example/androiddevelopment2/customview/PieChartViewModel.kt
@@ -0,0 +1,72 @@
+package com.example.androiddevelopment2.customview
+
+import androidx.lifecycle.ViewModel
+import com.example.androiddevelopment2.customview.state.PieChartEvent
+import com.example.androiddevelopment2.customview.state.PieChartState
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import javax.inject.Inject
+import androidx.core.graphics.toColorInt
+
+@HiltViewModel
+class PieChartViewModel @Inject constructor() : ViewModel() {
+ private val _state = MutableStateFlow(PieChartState.Initial)
+ val state: StateFlow = _state.asStateFlow()
+
+ private val availableColors = listOf(
+ "#FF6B6B", "#4ECDC4", "#45B7D1", "#FFA726",
+ "#66BB6A", "#AB47BC", "#E91E63", "#9C27B0"
+ ).map { it.toColorInt() }
+
+ init {
+ generateInitialData()
+ }
+
+ fun reduce(event: PieChartEvent) {
+ when (event) {
+ is PieChartEvent.OnSectorCountChanged -> generateSectorData(event.count)
+ is PieChartEvent.OnSectorSelected -> updateSelection(event.index)
+ PieChartEvent.OnClearSelection -> clearSelection()
+ }
+ }
+
+ private fun generateInitialData() {
+ generateSectorData(4)
+ }
+
+ private fun generateSectorData(count: Int) {
+ val colors = generateDistinctColors(count)
+ _state.value = PieChartState.Success(count, colors)
+ }
+
+ private fun generateDistinctColors(count: Int): List {
+ val colors = mutableListOf()
+ val shuffledColors = availableColors.shuffled()
+
+ for (i in 0 until count) {
+ var color = shuffledColors[i % shuffledColors.size]
+ if (colors.isNotEmpty() && color == colors.last()) {
+ color = shuffledColors.first { it != colors.last() }
+ }
+ colors.add(color)
+ }
+
+ if (colors.size > 1 && colors.first() == colors.last()) {
+ colors[colors.lastIndex] = shuffledColors.first { it != colors.first() }
+ }
+
+ return colors
+ }
+
+ private fun updateSelection(index: Int) {
+ val currentState = _state.value as? PieChartState.Success ?: return
+ _state.value = currentState.copy(selectedSector = index)
+ }
+
+ private fun clearSelection() {
+ val currentState = _state.value as? PieChartState.Success ?: return
+ _state.value = currentState.copy(selectedSector = -1)
+ }
+}
diff --git a/feature/customview/src/main/java/com/example/androiddevelopment2/customview/state/PieChartEvent.kt b/feature/customview/src/main/java/com/example/androiddevelopment2/customview/state/PieChartEvent.kt
new file mode 100644
index 0000000..e364e69
--- /dev/null
+++ b/feature/customview/src/main/java/com/example/androiddevelopment2/customview/state/PieChartEvent.kt
@@ -0,0 +1,7 @@
+package com.example.androiddevelopment2.customview.state
+
+sealed class PieChartEvent {
+ data class OnSectorCountChanged(val count: Int) : PieChartEvent()
+ data class OnSectorSelected(val index: Int) : PieChartEvent()
+ object OnClearSelection : PieChartEvent()
+}
\ No newline at end of file
diff --git a/feature/customview/src/main/java/com/example/androiddevelopment2/customview/state/PieChartState.kt b/feature/customview/src/main/java/com/example/androiddevelopment2/customview/state/PieChartState.kt
new file mode 100644
index 0000000..ecc6084
--- /dev/null
+++ b/feature/customview/src/main/java/com/example/androiddevelopment2/customview/state/PieChartState.kt
@@ -0,0 +1,10 @@
+package com.example.androiddevelopment2.customview.state
+
+sealed class PieChartState {
+ object Initial : PieChartState()
+ data class Success(
+ val sectorCount: Int,
+ val colors: List,
+ val selectedSector: Int = -1
+ ) : PieChartState()
+}
\ No newline at end of file
diff --git a/feature/customview/src/main/res/drawable/button_selector.xml b/feature/customview/src/main/res/drawable/button_selector.xml
new file mode 100644
index 0000000..cef3d8a
--- /dev/null
+++ b/feature/customview/src/main/res/drawable/button_selector.xml
@@ -0,0 +1,16 @@
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+
+
diff --git a/feature/customview/src/main/res/drawable/clear_button_selector.xml b/feature/customview/src/main/res/drawable/clear_button_selector.xml
new file mode 100644
index 0000000..ac6d7be
--- /dev/null
+++ b/feature/customview/src/main/res/drawable/clear_button_selector.xml
@@ -0,0 +1,15 @@
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+
\ No newline at end of file
diff --git a/feature/customview/src/main/res/layout/fragment_pie_chart.xml b/feature/customview/src/main/res/layout/fragment_pie_chart.xml
new file mode 100644
index 0000000..16b2548
--- /dev/null
+++ b/feature/customview/src/main/res/layout/fragment_pie_chart.xml
@@ -0,0 +1,125 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/feature/customview/src/main/res/navigation/nav_custom.xml b/feature/customview/src/main/res/navigation/nav_custom.xml
new file mode 100644
index 0000000..65fcc85
--- /dev/null
+++ b/feature/customview/src/main/res/navigation/nav_custom.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/feature/customview/src/main/res/values/arrays.xml b/feature/customview/src/main/res/values/arrays.xml
new file mode 100644
index 0000000..22dd3d5
--- /dev/null
+++ b/feature/customview/src/main/res/values/arrays.xml
@@ -0,0 +1,10 @@
+
+
+
+ - @color/red
+ - @color/black
+ - @color/green
+ - @color/yellow
+ - @color/gray_button
+
+
\ No newline at end of file
diff --git a/feature/customview/src/main/res/values/attrs.xml b/feature/customview/src/main/res/values/attrs.xml
new file mode 100644
index 0000000..372a939
--- /dev/null
+++ b/feature/customview/src/main/res/values/attrs.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/feature/customview/src/test/java/com/example/androiddevelopment2/customview/ExampleUnitTest.kt b/feature/customview/src/test/java/com/example/androiddevelopment2/customview/ExampleUnitTest.kt
new file mode 100644
index 0000000..05d71bc
--- /dev/null
+++ b/feature/customview/src/test/java/com/example/androiddevelopment2/customview/ExampleUnitTest.kt
@@ -0,0 +1,17 @@
+package com.example.androiddevelopment2.customview
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
\ No newline at end of file
diff --git a/feature/search/src/main/java/com/example/androiddevelopment2/search/SearchFragment.kt b/feature/search/src/main/java/com/example/androiddevelopment2/search/SearchFragment.kt
index 12b97dd..e444003 100644
--- a/feature/search/src/main/java/com/example/androiddevelopment2/search/SearchFragment.kt
+++ b/feature/search/src/main/java/com/example/androiddevelopment2/search/SearchFragment.kt
@@ -40,6 +40,13 @@ class SearchFragment: Fragment(searchR.layout.fragment_recipes) {
observeErrors()
observeUiEvents()
setupFab()
+ setUpCustomButton()
+ }
+
+ private fun setUpCustomButton() {
+ viewBinding.customButton.setOnClickListener {
+ viewModel.navigateToCustom()
+ }
}
private fun setupRecyclerView() {
diff --git a/feature/search/src/main/java/com/example/androiddevelopment2/search/SearchViewModel.kt b/feature/search/src/main/java/com/example/androiddevelopment2/search/SearchViewModel.kt
index 2bf78ef..4a0913c 100644
--- a/feature/search/src/main/java/com/example/androiddevelopment2/search/SearchViewModel.kt
+++ b/feature/search/src/main/java/com/example/androiddevelopment2/search/SearchViewModel.kt
@@ -126,6 +126,10 @@ class SearchViewModel @Inject constructor(
navMain.goToGraphPage()
}
+ fun navigateToCustom() {
+ navMain.goToCustomPage()
+ }
+
private fun navigateToDetails(recipeId: Int) {
viewModelScope.launch {
featureFlagsRepository.fetchFeatureFlags()
diff --git a/feature/search/src/main/res/layout/fragment_recipes.xml b/feature/search/src/main/res/layout/fragment_recipes.xml
index ab7fbf6..6c95a7e 100644
--- a/feature/search/src/main/res/layout/fragment_recipes.xml
+++ b/feature/search/src/main/res/layout/fragment_recipes.xml
@@ -74,6 +74,18 @@
+
+
+
+ Custom View
+
\ No newline at end of file
diff --git a/settings.gradle.kts b/settings.gradle.kts
index a19bcc9..2ff0a78 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -33,3 +33,4 @@ include(":feature:authorization")
include(":feature:registration")
include(":core:base-feature")
include(":feature:graph")
+include(":feature:customview")