diff --git a/app/src/androidTest/kotlin/info/appdev/chartexample/StartTest.kt b/app/src/androidTest/kotlin/info/appdev/chartexample/StartTest.kt index 2c127e96d..e43d6e245 100644 --- a/app/src/androidTest/kotlin/info/appdev/chartexample/StartTest.kt +++ b/app/src/androidTest/kotlin/info/appdev/chartexample/StartTest.kt @@ -28,6 +28,7 @@ import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation import info.appdev.chartexample.compose.HorizontalBarComposeActivity import info.appdev.chartexample.compose.HorizontalBarFullComposeActivity import info.appdev.chartexample.compose.MultiLineComposeActivity +import info.appdev.chartexample.compose.TimeIntervalComposeActivity import info.appdev.chartexample.fragments.ViewPagerSimpleChartDemo import info.appdev.chartexample.notimportant.ContentItem import info.appdev.chartexample.notimportant.DemoBase @@ -42,6 +43,7 @@ import org.junit.Test import org.junit.rules.TestName import org.junit.runner.RunWith import timber.log.Timber +import kotlin.jvm.java @RunWith(AndroidJUnit4::class) @@ -294,6 +296,7 @@ class StartTest { contentItem.clazz == LineChartTimeActivity::class.java || contentItem.clazz == HorizontalBarComposeActivity::class.java || contentItem.clazz == HorizontalBarFullComposeActivity::class.java || + contentItem.clazz == TimeIntervalComposeActivity::class.java || contentItem.clazz == MultiLineComposeActivity::class.java || contentItem.clazz == GradientActivity::class.java || // contentItem.clazz == TimeIntervalChartActivity::class.java || diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a8f2a241b..a6acf71d3 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -61,6 +61,7 @@ + diff --git a/app/src/main/kotlin/info/appdev/chartexample/compose/TimeIntervalComposeActivity.kt b/app/src/main/kotlin/info/appdev/chartexample/compose/TimeIntervalComposeActivity.kt new file mode 100644 index 000000000..d6f619cb3 --- /dev/null +++ b/app/src/main/kotlin/info/appdev/chartexample/compose/TimeIntervalComposeActivity.kt @@ -0,0 +1,184 @@ +package info.appdev.chartexample.compose + +import android.graphics.Color +import android.os.Bundle +import androidx.activity.compose.setContent +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.MoreVert +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Slider +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.unit.dp +import info.appdev.chartexample.notimportant.DemoBaseCompose +import info.appdev.charting.compose.GanttChart +import info.appdev.charting.data.GanttChartData +import info.appdev.charting.data.GanttTask + +/** + * Demo activity showing Gantt-style timeline visualization using Compose. + */ +class TimeIntervalComposeActivity : DemoBaseCompose() { + + // Full list of sample tasks + private val allTasks = listOf( + GanttTask("Design", 0f, 50f, Color.rgb(255, 107, 107)), + GanttTask("Dev", 40f, 100f, Color.rgb(66, 165, 245)), + GanttTask("Testing", 120f, 40f, Color.rgb(76, 175, 80), hatched = true), + GanttTask("Launch", 150f, 20f, Color.rgb(255, 193, 7)), + ) + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContent { + MaterialTheme { + GanttChartScreen(onViewGithub = { viewGithub() }) + } + } + } + + @OptIn(ExperimentalMaterial3Api::class) + @Composable + fun GanttChartScreen(onViewGithub: () -> Unit) { + var showMenu by remember { mutableStateOf(false) } + // X: controls visible max time (range 50–300) + var seekBarXValue by remember { mutableFloatStateOf(190f) } + // Y: controls how many tasks are shown (1–allTasks.size) + var seekBarYValue by remember { mutableFloatStateOf(allTasks.size.toFloat()) } + + Scaffold( + topBar = { + TopAppBar( + title = { Text("TimeIntervalChart") }, + colors = TopAppBarDefaults.topAppBarColors( + containerColor = MaterialTheme.colorScheme.primary, + titleContentColor = MaterialTheme.colorScheme.onPrimary + ), + actions = { + Box { + IconButton( + onClick = { showMenu = true }, + modifier = Modifier.testTag("menuButton") + ) { + Icon( + Icons.Default.MoreVert, + contentDescription = "Menu", + tint = MaterialTheme.colorScheme.onPrimary + ) + } + DropdownMenu( + expanded = showMenu, + onDismissRequest = { showMenu = false }, + modifier = Modifier.testTag("dropdownMenu") + ) { + DropdownMenuItem( + text = { Text("View on GitHub") }, + onClick = { showMenu = false; onViewGithub() }, + modifier = Modifier.testTag("menuItem_View on GitHub") + ) + } + } + } + ) + } + ) { paddingValues -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues) + .background(androidx.compose.ui.graphics.Color.White) + ) { + val ganttData = remember(seekBarXValue, seekBarYValue) { + val maxTime = seekBarXValue + val taskCount = seekBarYValue.toInt().coerceAtLeast(1) + GanttChartData().apply { + allTasks.take(taskCount).forEach { addTask(it) } + minTime = 0f + this.maxTime = maxTime + } + } + + GanttChart( + data = ganttData, + modifier = Modifier + .fillMaxWidth() + .weight(1f) + ) + + // SeekBar X – controls visible time range + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "X:", + modifier = Modifier.padding(end = 8.dp), + style = MaterialTheme.typography.bodyMedium + ) + Slider( + value = seekBarXValue, + onValueChange = { seekBarXValue = it }, + valueRange = 1f..200f, + modifier = Modifier.weight(1f) + ) + Text( + text = seekBarXValue.toInt().toString(), + modifier = Modifier.padding(start = 8.dp), + style = MaterialTheme.typography.bodyMedium + ) + } + + // SeekBar Y – controls number of tasks shown + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "Y:", + modifier = Modifier.padding(end = 8.dp), + style = MaterialTheme.typography.bodyMedium + ) + Slider( + value = seekBarYValue, + onValueChange = { seekBarYValue = it }, + valueRange = 1f..allTasks.size.toFloat(), + steps = allTasks.size - 2, + modifier = Modifier.weight(1f) + ) + Text( + text = seekBarYValue.toInt().toString(), + modifier = Modifier.padding(start = 8.dp), + style = MaterialTheme.typography.bodyMedium + ) + } + } + } + } +} diff --git a/app/src/main/kotlin/info/appdev/chartexample/notimportant/MainActivity.kt b/app/src/main/kotlin/info/appdev/chartexample/notimportant/MainActivity.kt index ccae0d21c..85de01258 100644 --- a/app/src/main/kotlin/info/appdev/chartexample/notimportant/MainActivity.kt +++ b/app/src/main/kotlin/info/appdev/chartexample/notimportant/MainActivity.kt @@ -80,6 +80,7 @@ import info.appdev.chartexample.TimeLineActivity import info.appdev.chartexample.compose.HorizontalBarComposeActivity import info.appdev.chartexample.compose.HorizontalBarFullComposeActivity import info.appdev.chartexample.compose.MultiLineComposeActivity +import info.appdev.chartexample.compose.TimeIntervalComposeActivity import info.appdev.chartexample.fragments.ViewPagerSimpleChartDemo import info.appdev.charting.utils.Utils import info.appdev.charting.utils.initUtils @@ -217,10 +218,13 @@ class MainActivity : ComponentActivity() { add(ComposeItem("HorizontalFullCompose", "Render bar chart horizontally full compose.", HorizontalBarFullComposeActivity::class.java).toDemoBase()) add(ComposeItem("MultiLineCompose", "Show multiple data sets in compose.", MultiLineComposeActivity::class.java).toDemoBase()) + add(ContentItem("Timeinterval")) + add(ContentItem("Timeinterval", "Grantt chart", TimeIntervalChartActivity::class.java)) + add(ComposeItem("Timeinterval compose", "Grantt chart compose", TimeIntervalComposeActivity::class.java).toDemoBase()) + add(ContentItem("Demonstrate and fix issues")) add(ContentItem("Gradient", "Show a gradient edge case", GradientActivity::class.java)) add(ContentItem("Timeline", "Show a time line with Unix timestamp", TimeLineActivity::class.java)) - add(ContentItem("Timeinterval", "Grantt chart", TimeIntervalChartActivity::class.java)) } } } diff --git a/chartLibCompose/src/main/kotlin/info/appdev/charting/compose/GanttChartComposable.kt b/chartLibCompose/src/main/kotlin/info/appdev/charting/compose/GanttChartComposable.kt new file mode 100644 index 000000000..00b2f622c --- /dev/null +++ b/chartLibCompose/src/main/kotlin/info/appdev/charting/compose/GanttChartComposable.kt @@ -0,0 +1,35 @@ +package info.appdev.charting.compose + +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.viewinterop.AndroidView +import info.appdev.charting.charts.GanttChart +import info.appdev.charting.data.GanttChartData + +/** + * A Composable wrapper for [GanttChart]. + * + * @param data The Gantt chart data to display + * @param modifier The modifier to be applied to the chart + */ +@Composable +fun GanttChart( + data: GanttChartData?, + modifier: Modifier = Modifier, +) { + val context = LocalContext.current + + val chart = remember { GanttChart(context) } + + AndroidView( + factory = { chart }, + modifier = modifier.fillMaxSize(), + update = { ganttChart -> + ganttChart.setData(data) + } + ) +} + diff --git a/screenshotsToCompare9/StartTest_smokeTestStart-46-TimeIntervalChartActivity-Timeinterval-1SampleClick.png b/screenshotsToCompare9/StartTest_smokeTestStart-44-TimeIntervalChartActivity-Timeinterval-1SampleClick.png similarity index 100% rename from screenshotsToCompare9/StartTest_smokeTestStart-46-TimeIntervalChartActivity-Timeinterval-1SampleClick.png rename to screenshotsToCompare9/StartTest_smokeTestStart-44-TimeIntervalChartActivity-Timeinterval-1SampleClick.png diff --git a/screenshotsToCompare9/StartTest_smokeTestStart-46-TimeIntervalChartActivity-Timeinterval-click.png b/screenshotsToCompare9/StartTest_smokeTestStart-44-TimeIntervalChartActivity-Timeinterval-click.png similarity index 100% rename from screenshotsToCompare9/StartTest_smokeTestStart-46-TimeIntervalChartActivity-Timeinterval-click.png rename to screenshotsToCompare9/StartTest_smokeTestStart-44-TimeIntervalChartActivity-Timeinterval-click.png diff --git a/screenshotsToCompare9/StartTest_smokeTestStart-46-TimeIntervalChartActivity-Timeinterval-click2020.png b/screenshotsToCompare9/StartTest_smokeTestStart-44-TimeIntervalChartActivity-Timeinterval-click2020.png similarity index 100% rename from screenshotsToCompare9/StartTest_smokeTestStart-46-TimeIntervalChartActivity-Timeinterval-click2020.png rename to screenshotsToCompare9/StartTest_smokeTestStart-44-TimeIntervalChartActivity-Timeinterval-click2020.png diff --git a/screenshotsToCompare9/StartTest_smokeTestStart-46-TimeIntervalChartActivity-Timeinterval-click7070.png b/screenshotsToCompare9/StartTest_smokeTestStart-44-TimeIntervalChartActivity-Timeinterval-click7070.png similarity index 100% rename from screenshotsToCompare9/StartTest_smokeTestStart-46-TimeIntervalChartActivity-Timeinterval-click7070.png rename to screenshotsToCompare9/StartTest_smokeTestStart-44-TimeIntervalChartActivity-Timeinterval-click7070.png diff --git a/screenshotsToCompare9/StartTest_smokeTestStart-45-TimeIntervalComposeActivity-Timeintervalcompose-1SampleClick.png b/screenshotsToCompare9/StartTest_smokeTestStart-45-TimeIntervalComposeActivity-Timeintervalcompose-1SampleClick.png new file mode 100644 index 000000000..b1d1ddacc Binary files /dev/null and b/screenshotsToCompare9/StartTest_smokeTestStart-45-TimeIntervalComposeActivity-Timeintervalcompose-1SampleClick.png differ diff --git a/screenshotsToCompare9/StartTest_smokeTestStart-44-GradientActivity-Gradient-1SampleClick.png b/screenshotsToCompare9/StartTest_smokeTestStart-47-GradientActivity-Gradient-1SampleClick.png similarity index 100% rename from screenshotsToCompare9/StartTest_smokeTestStart-44-GradientActivity-Gradient-1SampleClick.png rename to screenshotsToCompare9/StartTest_smokeTestStart-47-GradientActivity-Gradient-1SampleClick.png diff --git a/screenshotsToCompare9/StartTest_smokeTestStart-45-TimeLineActivity-Timeline-1SampleClick.png b/screenshotsToCompare9/StartTest_smokeTestStart-48-TimeLineActivity-Timeline-1SampleClick.png similarity index 100% rename from screenshotsToCompare9/StartTest_smokeTestStart-45-TimeLineActivity-Timeline-1SampleClick.png rename to screenshotsToCompare9/StartTest_smokeTestStart-48-TimeLineActivity-Timeline-1SampleClick.png