Skip to content

Kotlin multiplatform - #833

Draft
hannesa2 wants to merge 22 commits into
masterfrom
KotlinMultiplatform
Draft

hannesa2 wants to merge 22 commits into
masterfrom
KotlinMultiplatform

Conversation

@hannesa2

Copy link
Copy Markdown
Collaborator

No description provided.

@hannesa2
hannesa2 marked this pull request as draft September 26, 2026 17:55
hannesa2 and others added 22 commits September 27, 2026 13:46
…tform-independent classes

* Created a new chartLibCore Kotlin Multiplatform module (com.android.kotlin.multiplatform.library + org.jetbrains.kotlin.multiplatform) targeting androidTarget, jvm("desktop"), and iosX64/iosArm64/iosSimulatorArm64.

* Extracted the 5 genuinely platform-independent classes into its commonMain (same package names, so no call-site changes needed elsewhere): ObjectPool, FSize, PointD, ColorTemplate, highlight.Range, highlight.RangeDouble.

* Fixed the only two real platform couplings found: replaced android.graphics.Color.rgb(...) in ColorTemplate with a common argb() helper, and dropped the JVM-only @synchronized annotations in ObjectPool (common code can't assume JVM's synchronized — the class is not thread-safety-critical against instrumentation tests as run today, but flagging this trade-off).

* Moved ObjectPoolTest into commonTest, converted its JUnit assertions to kotlin.test.

* Registered chartLibCore in settings.gradle.kts and made chartLib depend on it (api(project(":chartLibCore"))).

* Verified: chartLibCore builds/tests pass on Android, desktop-JVM, and all 3 iOS targets; chartLib:test, chartLibCompose:assembleDebug, and the full ./gradlew test all still pass unchanged.
* Designed and added ChartIcon — an expect abstract class in commonMain, actual typealiasd to android.graphics.drawable.Drawable on Android (zero behavior change there) and an empty placeholder on iOS/desktop for now.

* Moved all 19 files of the entry hierarchy (BaseEntry, EntryFloat, EntryDouble, deprecated Entry, and all Bar/Pie/Radar/Bubble/Candle Float/Double/deprecated variants) into chartLibCore commonMain, same package names.

* Removed Drawable→ChartIcon, dropped @SuppressLint, replaced Build.VERSION toString branching with ::class.simpleName, replaced Timber logging calls, and replaced the Utils.FLOAT_EPSILON/DOUBLE_EPSILON dependency with local common constants.

* Flagged breaking change: EntryFloat/Entry no longer implement Parcelable/Serializable (no multiplatform equivalent, no internal usage found — documented as reversible via expect/actual if a consumer needs it back).

* Verified: chartLibCore builds on Android/iOS×3/desktop; chartLib:compileDebugKotlin needed zero source changes; full ./gradlew test, chartLibCompose:assembleDebug, app:assembleDebug all pass.

* Updated KotlinMultiplatform.md with the detailed change log and remaining scope.
Landed (in chartLibCore, reusable common types): AxisDependency, LegendForm enums (with nested-typealias back-compat on YAxis/Legend), PaintStyle enum (applied to CandleDataSet/ICandleDataSet + renderer/demo conversion), ChartTypeface expect/actual (applied to IDataSet/BaseDataSet/ChartData), DashEffect (designed, not yet wired), consolidated chartDensity/convertDpToPixel, and PointF (Parcelable dropped, moved to commonMain
* Landed: highlight/Highlight.kt (dropped Serializable, switched to the top-level common AxisDependency) and highlight/IHighlighter.kt moved to chartLibCore — small but real, verified-green.

* Blocked: All 10 formatter/ files and the remaining 7 highlight/ files (ChartHighlighter, BarHighlighter, HorizontalBarHighlighter, CombinedHighlighter, PieHighlighter, RadarHighlighter, PieRadarHighlighter) transitively depend on ViewPortHandler (Step A.6), AxisBase (Step A.5), IDataSet/ILineDataSet/LineDataProvider (blocked per last slice's finding), or concrete PieChart/RadarChart/PieRadarChartBase view classes.
Moves the viewport geometry engine to commonMain, built on the new
common Matrix/RectF value types:

- ViewPortHandler moved to chartLibCore as-is, using common Matrix/
  RectF; its View? parameter on refresh()/centerViewPort() became a
  platform-agnostic invalidate callback lambda, and its one Timber
  log call became a plain println().
- Transformer split into a common TransformerCore base (chartLibCore)
  holding all matrix/rect math, and a slim Android Transformer
  subclass (chartLib) that keeps only the dataset-family
  (generateTransformedValues*) and Path-based (pathValueToPixel(s))
  methods that can't move yet.
- Added Android-side Matrix/RectF conversion helpers (MatrixAndroid.kt,
  RectFAndroid.kt) and updated ~20 call sites (Chart/BarChart/
  HorizontalBarChart/BarLineChartBase, BarLineChartTouchListener,
  jobs, axis/bar renderers) to convert at the Canvas-drawing boundary.

This unblocks the rest of Step A.3 (IDataSet/BaseDataSet/DataSet/
ChartData family, since IValueFormatter/IFillFormatter no longer need
an Android-only ViewPortHandler/LineDataProvider), most of Step A.4
(formatter, highlight), and Step A.5 groundwork.

Verified: chartLibCore builds/tests green on Android host, desktop-
JVM, and iOS simulator; chartLib:compileDebugKotlin, full
./gradlew test, chartLibCompose:assembleDebug, and app:assembleDebug
all pass.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… to chartLibCore

Unblocked by Step A.6 (ViewPortHandler migration), this completes the
DataSet-family migration to the multiplatform chartLibCore module:

- Move interfaces/datasets/IDataSet.kt, data/BaseDataSet.kt, data/DataSet.kt
  (incl. nested Rounding enum), and data/ChartData.kt to chartLibCore
  commonMain, preserving package/class names so no downstream imports change.
- Wire up the previously-unused common DashEffect type: IDataSet/BaseDataSet/
  Legend/LegendEntry now use DashEffect instead of android.graphics.
  DashPathEffect for formLineDashEffect, with a new Android-only
  DashEffectAndroid.kt converter at the one real Canvas boundary in
  LegendRenderer. Demo app call sites updated accordingly.
- IDataSet now imports the top-level common AxisDependency/LegendForm enums
  directly instead of importing YAxis/Legend for their nested type aliases.
- BaseDataSet: drop @ColorInt/@transient annotations, replace
  android.graphics.Color helpers with new platform-independent
  ColorTemplate.argb/alpha/red/green/blue helpers, extract the Context-based
  setColors(IntArray, Context) overload into a chartLib-only extension
  function (BaseDataSetAndroid.kt), and replace Utils.defaultValueFormatter
  with a local companion default.
- DataSet/ChartData: drop @SuppressLint, java.io.Serializable, and Timber
  logging (replaced with println), matching the precedent set by
  EntryFloat/Highlight/ViewPortHandler.
- Move the now-unblocked formatter cluster (IValueFormatter,
  DefaultValueFormatter, StackedValueFormatter, ColorFormatter) to
  chartLibCore. DefaultValueFormatter/StackedValueFormatter no longer use
  java.text.DecimalFormat (no KMP equivalent); rewritten against a new shared
  utils/DecimalFormatting.kt helper with equivalent rounding/grouping
  behavior, covered by 5 new unit tests.

Verified: chartLibCore build/allTests/testAndroidHostTest (Android host,
desktop, iOS simulator), chartLib:compileDebugKotlin, full ./gradlew test,
chartLibCompose:assembleDebug, app:assembleDebug all green.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…Axis/LimitLine/LimitRange to chartLibCore

Moves the axis-component family and its coupled axis-value formatter
cluster into the platform-agnostic chartLibCore module, following the
same extension-function extraction pattern established in Step A.3/A.6
for Paint/Context-dependent methods.

- ComponentBase: Typeface? -> common ChartTypeface?, Color.BLACK ->
  ColorTemplate.BLACK, dropped @ColorInt.
- AxisBase: Color.GRAY -> ColorTemplate.GRAY (new constant),
  axisLineDashPathEffect/gridDashPathEffect -> common DashEffect?,
  Timber.e -> println, dropped @ColorInt. The Paint-dependent
  getLongestLabel(p: Paint?) was extracted into a new Android-only
  extension function (chartLib/components/AxisBaseAndroid.kt); the
  no-arg longestLabel property (pure string comparison) stayed on the
  common class.
- YAxis: Color.GRAY -> ColorTemplate.GRAY, dropped @ColorInt. The two
  Paint-dependent methods getRequiredWidthSpace/getRequiredHeightSpace
  were extracted into chartLib/components/YAxisAndroid.kt. XAxis needed
  no changes.
- LimitLine/LimitRange: Color.rgb(...) -> ColorTemplate.argb(...),
  Paint.Style? -> common PaintStyle?, DashPathEffect? -> common
  DashEffect?, dropped @ColorInt.
- Circular-dependency slice: AxisBase, IAxisValueFormatter and
  DefaultAxisValueFormatter had to move together atomically since
  AxisBase.valueFormatter references DefaultAxisValueFormatter
  directly, and both formatter types take an AxisBase? parameter.
- DefaultAxisValueFormatter/PercentFormatter: replaced
  java.text.DecimalFormat with the shared
  chartLibCore/utils/DecimalFormatting.kt formatGroupedDecimal helper.
  PercentFormatter's unused DecimalFormat-based constructor overload
  (zero callers) was replaced with PercentFormatter(decimalDigits: Int)
  - a disclosed breaking change.
- LargeValueFormatter: manual Kotlin rewrite of the
  DecimalFormat("###E00") engineering-notation algorithm (exponent
  forced to a multiple of 3, mantissa rounded to 3 significant digits,
  trailing zeros stripped, carry-over handled). Verified against the
  existing ~25-assertion LargeValueFormatterTest (moved to
  chartLibCore/commonTest and ported to kotlin.test) - caught and fixed
  a floor-division bug during verification.
- Updated renderer call sites (XAxisRenderer, YAxisRenderer,
  XAxisRendererHorizontalBarChart, YAxisRendererHorizontalBarChart,
  YAxisRendererRadarChart, BarLineChartBase, HorizontalBarChart) for
  the DashEffect/PaintStyle conversions and the new extension-function
  imports.

Legend/LegendEntry/Description/IMarker/MarkerImage/MarkerView remain in
chartLib for a follow-up slice.

Verified: chartLibCore:allTests, chartLib:compileDebugKotlin, full
./gradlew test, chartLibCompose:assembleDebug, app:assembleDebug all
green.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…artLibCore

Completes Step A.5 by moving the remaining portable components/ classes
into chartLibCore commonMain.

- LegendEntry: trivial move, dropped @ColorInt.
- Legend: the bulk of the class (alignment/orientation enums, spacing
  properties, entries/extraEntries, setCustom/setExtra/resetCustom)
  needed no changes. The three Paint-dependent methods
  (getMaximumEntryWidth, getMaximumEntryHeight, and the ~110-line
  calculateDimensions layout algorithm) were extracted into a new
  Android-only extension file, chartLib/components/LegendAndroid.kt -
  the largest extension-function extraction in this migration so far,
  demonstrating the pattern scales to large multi-branch logic as long
  as every touched member is public (required switching two internal
  offset reads from the protected mXOffset/mYOffset fields to the
  existing public xOffset/yOffset accessors on ComponentBase).
- Description: textAlign (android.graphics.Paint.Align?) replaced with
  a new common chartLibCore/utils/TextAlign.kt enum plus a new
  chartLib/utils/TextAlignAndroid.kt converter, following the same
  common-enum-plus-Android-converter pattern already used for
  PaintStyle/DashEffect. Updated the one render call site in Chart.kt.
- LegendRenderer.kt: added the import for the new
  calculateDimensions extension function at its call site.
- IMarker/MarkerImage/MarkerView confirmed permanently Android-only
  (Canvas/Context/Drawable/RelativeLayout coupling with no separable
  portable core) and left in chartLib.

Step A.5 is now fully complete: all of components/ that has a
genuinely portable core lives in chartLibCore.

Verified: chartLibCore:allTests, chartLib:compileDebugKotlin, full
./gradlew test, chartLibCompose:assembleDebug, app:assembleDebug all
green.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…odule

Scaffolds a new chartLibComposeMultiplatform module targeting Android,
desktop (jvm), iosArm64 and iosSimulatorArm64, applying the Compose
Multiplatform (org.jetbrains.compose 1.12.1) and Compose compiler
(org.jetbrains.kotlin.plugin.compose 2.4.10) plugins on top of the same
com.android.kotlin.multiplatform.library + org.jetbrains.kotlin.multiplatform
setup used by chartLibCore. commonMain depends on chartLibCore (api) plus
compose.runtime/compose.foundation/compose.ui.

Two toolchain constraints discovered and documented in
KotlinMultiplatform.md:
- Compose Multiplatform 1.12.1's Android artifacts require compileSdk 37,
  so this module (only) is bumped to compileSdk 37 while the rest of the
  repo intentionally stays on 36.
- Compose Multiplatform 1.12.x no longer publishes artifacts for the
  iosX64 (Intel simulator) target, so unlike chartLibCore this module only
  configures iosArm64()/iosSimulatorArm64().

Only a documented placeholder constant exists so far; no renderer logic
has been ported yet. Verified chartLibComposeMultiplatform:build succeeds
across all targets, and the full existing verification chain
(chartLibCore:allTests, chartLib:compileDebugKotlin, ./gradlew test,
chartLibCompose:assembleDebug, app:assembleDebug) remains green.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…nderer slice

Ports the axis grid line drawing (XAxisRenderer.renderGridLines/
drawGridLine and YAxisRenderer.renderGridLines/linePath/
transformedPositions) to Compose's DrawScope, as the first real renderer
slice out of the ~32-file Step A.7 chunk.

This slice was chosen because it needs no concrete chart-type DataSet
(LineDataSet/BarDataSet/etc. haven't moved to chartLibCore yet) - only the
axis/viewport/transform types already migrated in Step A.5/A.6
(XAxis/YAxis, ViewPortHandler, TransformerCore), making it fully
self-contained.

Adds chartLibComposeMultiplatform's
renderer/AxisGridRenderer.kt with DrawScope.drawXAxisGridLines(...) and
DrawScope.drawYAxisGridLines(...), operating purely on chartLibCore types
rather than chartLib's Renderer/DataRenderer/AxisRenderer Android
base-class hierarchy - confirming Compose renderers will be fresh
idiomatic functions on the portable model, not a line-for-line port of
the View-based OOP renderer classes.

The axis-value-to-pixel math is factored out into plain
xAxisGridPixelPositions/yAxisGridPixelPositions functions, separate from
the DrawScope drawLine/clipRect calls, so it can be unit-tested without a
Compose UI test harness. Added AxisGridRendererTest covering both axes,
which now passes on testAndroidHostTest, desktopTest, and
iosSimulatorArm64Test.

Removes the earlier scaffold placeholder now that real renderer code
exists. Full existing verification chain (chartLibCore:allTests,
chartLib:compileDebugKotlin, ./gradlew test, chartLibCompose:assembleDebug,
app:assembleDebug) remains green.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…hartLibCore

Prerequisite work surfaced by the Step A.7 grid-line proof-of-concept:
most of the ~30 remaining renderer files need concrete chart-type DataSet
classes (LineDataSet/BarDataSet/etc.), which hadn't moved to chartLibCore
yet even though the abstract DataSet/BaseDataSet/IDataSet base already had
(Step A.3 completion).

Moves every concrete DataSet class with no remaining Android-only
blocker to chartLibCore commonMain: BarLineScatterCandleBubbleDataSet,
LineScatterCandleRadarDataSet, LineRadarDataSet (abstract parents), plus
the now fully-portable leaf classes BubbleDataSet, CandleDataSet,
PieDataSet, RadarDataSet, and their interfaces.

Trivial Android cleanup: @ColorInt annotations dropped, Color.rgb(...)/
Color.WHITE replaced with the existing common ColorTemplate.argb(r,g,b)
helper. ILineScatterCandleRadarDataSet.dashPathEffectHighlight switches
DashPathEffect -> the common DashEffect type (same pattern as Step A.5),
updating LineScatterCandleRadarRenderer's one call site.

fillDrawable (Drawable has no portable equivalent) is removed from
common code entirely and replaced with an Android-only extension
property in the new chartLib/data/LineRadarDataSetAndroid.kt, backed by
a WeakHashMap keyed by dataset identity - keeps
`dataSet.fillDrawable = ...` call-site syntax unchanged for both
LineDataSet (stays Android-only) and RadarDataSet (now common), at the
cost of an explicit import at each call site (LineChartRenderer,
RadarChartRenderer, three app example activities).

Two small disclosed behavior changes from removing the fillDrawable
stored property: setting fillColor no longer implicitly clears a
previously set fillDrawable, and .copy() no longer propagates
fillDrawable to the copy.

BarDataSet/LineDataSet (need the heavily Canvas/Paint/Drawable-coupled
Fill class, Context-based drawable loading, and IFillFormatter's
dependency on the permanently-Android-only LineDataProvider) and
ScatterDataSet (needs the Canvas/Paint-coupled IShapeRenderer family)
remain Android-only leaves extending the now-common abstract parents,
same pattern as IMarker/highlighters in Steps A.4/A.5.

Verified: chartLibCore:allTests, chartLib:compileDebugKotlin, full
./gradlew test, chartLibCompose:assembleDebug, app:assembleDebug, and
chartLibComposeMultiplatform:build (all targets) all pass.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- chartLibCore/utils/Fill.kt: portable data-only state (type, color, alpha,
  gradientColors/gradientPositions, finalColor). Dropped @ColorInt.
- chartLib/utils/FillAndroid.kt (new): Android-only Fill.fillRect()/
  Fill.fillPath() extension functions (ported verbatim) plus a
  WeakHashMap-backed Fill.drawable extension property for the Type.DRAWABLE
  payload, same side-channel pattern as ILineRadarDataSet.fillDrawable.
- BarChartRenderer.kt / HorizontalBarChartRenderer.kt: added
  `import info.appdev.charting.utils.fillRect` (no call-site changes needed).
- Moved data/BarDataSet.kt and interfaces/datasets/IBarDataSet.kt to
  chartLibCore; replaced Color.rgb(...)/Color.BLACK with
  ColorTemplate.argb(...)/ColorTemplate.BLACK, dropped @ColorInt.
  BarDataSet is now a fully portable leaf class.
- LineDataSet/ScatterDataSet remain Android-only (still blocked on
  IFillFormatter/LineDataProvider and IShapeRenderer respectively).
- No disclosed behavior changes.
- Updated KotlinMultiplatform.md with the changelog entry.

Verified: chartLibCore:allTests, chartLib:compileDebugKotlin,
chartLibCompose:assembleDebug, app:assembleDebug, full ./gradlew test, and
chartLibComposeMultiplatform:build (Android/desktop/iosArm64/
iosSimulatorArm64) all pass.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…eDataSet

- LineDataSet's dashPathEffect/enableDashedLine now use the common DashEffect
  type instead of android.graphics.DashPathEffect (reusing Step A.5's
  DashEffect/toAndroidDashPathEffect() pattern). Updated
  LineChartRenderer.kt's pathEffect assignment accordingly.
- fillFormatter removed from ILineDataSet/LineDataSet entirely (IFillFormatter
  depends on the permanently-Android-only LineDataProvider). New
  chartLib/data/LineDataSetAndroid.kt defines
  `var ILineDataSet<*>.fillFormatter: IFillFormatter?` as a
  WeakHashMap-backed extension property, same side-channel pattern as
  ILineRadarDataSet.fillDrawable. Added
  `import info.appdev.charting.data.fillFormatter` at 6 call sites
  (LineChartRenderer.kt + 5 app example activities).
- setCircleColors(colors, context) (ContextCompat-based) moved to the same
  LineDataSetAndroid.kt as a generic extension function.
- Moved data/LineDataSet.kt and interfaces/datasets/ILineDataSet.kt to
  chartLibCore; dropped @ColorInt/@SuppressLint("RawTypeDataSet"), replaced
  Color.WHITE/Color.rgb(...) with ColorTemplate equivalents, replaced
  Timber.e(...) warnings with println(...) (matching the existing
  chartLibCore warning-logging pattern). LineDataSet is now a fully portable
  leaf class.
- Disclosed behavior change: .copy() no longer propagates a custom
  fillFormatter to the copy (documented in LineDataSet's class doc, same
  category as the earlier fillDrawable copy-non-propagation change).
- ScatterDataSet/IScatterDataSet remain the last Android-only DataSet leaf,
  blocked on the IShapeRenderer family + extracting ScatterShape out of the
  Android-only ScatterChart.
- Updated KotlinMultiplatform.md with the changelog entry.

Verified: chartLibCore:allTests, chartLib:compileDebugKotlin,
chartLibCompose:assembleDebug, app:assembleDebug, full ./gradlew test, and
chartLibComposeMultiplatform:build (Android/desktop/iosArm64/
iosSimulatorArm64) all pass.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…rDataSet

Completes the concrete DataSet family migration (Bar/Line/Scatter/Candle/
Bubble/Radar/Pie) that started as a Step A.7 prerequisite.

- Extracted ScatterShape out of the Android-only ScatterChart class into a
  new top-level, portable chartLibCore/charts/ScatterShape.kt (same
  package, so ScatterChart.kt just drops the nested enum). Source-breaking:
  ScatterChart.ScatterShape.X -> ScatterShape.X (5 call sites updated).
- shapeRenderer removed from IScatterDataSet/ScatterDataSet entirely
  (IShapeRenderer is Canvas/Paint-coupled, no portable equivalent). New
  chartLib/data/ScatterDataSetAndroid.kt defines
  `var IScatterDataSet.shapeRenderer: IShapeRenderer?` as a
  WeakHashMap-backed extension property, same side-channel pattern as
  fillDrawable/fillFormatter. Uses containsKey (not getOrPut) so an
  explicit null stays null instead of resetting to the default
  SquareShapeRenderer().
- setScatterShape(shape) moved to the same file as an extension function;
  getRendererForShape(shape) moved from ScatterDataSet's companion object to
  a top-level function (source-breaking, unused elsewhere in this repo).
- Moved data/ScatterDataSet.kt and interfaces/datasets/IScatterDataSet.kt to
  chartLibCore. ScatterDataSet is now a fully portable leaf class.
- Disclosed behavior change: .copy() no longer propagates a custom
  shapeRenderer to the copy (documented in ScatterDataSet's class doc, same
  category as prior fillDrawable/fillFormatter changes).
- Updated KotlinMultiplatform.md with the changelog entry.

Verified: chartLibCore:allTests, chartLib:compileDebugKotlin,
chartLibCompose:assembleDebug, app:assembleDebug, full ./gradlew test, and
chartLibComposeMultiplatform:build (Android/desktop/iosArm64/
iosSimulatorArm64) all pass.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Resume the Compose Multiplatform renderer port now that the concrete
DataSet family is fully portable, adding the first chart *data*
renderer (previously only axis grid lines existed) using the same
"pure pixel-math function + thin DrawScope extension" pattern.

- chartLibComposeMultiplatform/.../renderer/BarChartRenderer.kt (new):
  barPixelRects(dataSet, barWidth, transformer) computes one
  [left, top, right, bottom] value-space rect per non-stacked entry
  (mirrors the non-stacked branch of chartLib's buffer.BarBuffer.feed)
  and maps it to pixel space via TransformerCore.pointValuesToPixel;
  DrawScope.drawBarChartDataSet(...) draws each rect with drawRect,
  cycling dataset colors via getColorByIndex(index).
- Deliberately out of scope for this slice: stacked bars, animation
  phases, inverted axis, bar borders/shadows, rounded bars, and the
  BarData/ChartData container classes (still Android-only) -- this
  renderer operates directly on a single IBarDataSet plus a caller
  supplied barWidth, matching how buffer.BarBuffer itself is
  parameterized, so it doesn't require BarData to be ported first.
- New BarChartRendererTest.kt: asserts a positive-value entry's rect
  maps to the expected content-rect pixels, and that a negative-value
  entry's rect keeps pixel-space top < bottom (Y-axis inversion
  handled correctly regardless of value sign).

Verified: chartLibComposeMultiplatform:build (all targets, including
iosSimulatorArm64Test), chartLibCore:allTests,
chartLib:compileDebugKotlin, chartLibCompose:assembleDebug,
app:assembleDebug, full ./gradlew test all pass.

Updated KotlinMultiplatform.md with the changelog entry.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add pan/pinch-zoom gesture support for Compose Multiplatform charts,
replacing chartLib's MotionEvent-based ChartTouchListener/
BarLineChartTouchListener for this proof-of-concept scope. Needed
almost no new common code: ViewPortHandler's zoom/translate/refresh/
limitTransAndScale matrix math (bounds-clamped pan/zoom) already
lived in chartLibCore from earlier steps.

- chartLibComposeMultiplatform/.../gesture/GestureHandling.kt (new):
  chartGestureMatrix(viewPortHandler, pan, zoom, centroid, ...)
  copies the current matrixTouch, applies postTranslate(pan) then
  postScale(zoom, zoom, centroid) -- mirrors
  BarLineChartTouchListener.performDrag/performZoom combined into one
  step, since Compose reports incremental per-frame deltas rather
  than cumulative-since-gesture-start deltas like MotionEvent.
  Modifier.chartTransformGestures(viewPortHandler, ..., onGesture)
  wires detectTransformGestures to it and calls
  ViewPortHandler.refresh(matrix, onGesture, true), which also
  applies the existing limitTransAndScale bounds clamping.
- Deliberately out of scope for this slice: rotation gestures,
  independent X/Y-only zoom modes, highlight-on-drag, double-tap
  zoom, fling/deceleration, and OnChartGestureListener callbacks.
- New GestureHandlingTest.kt: verifies chartGestureMatrix's pure
  matrix math directly (pan-only, zoom-about-centroid-only, and
  disabled-axis cases) without needing a Compose UI test harness or
  going through ViewPortHandler's stateful clamping.

Verified: chartLibComposeMultiplatform:build (all targets, including
iosSimulatorArm64Test), chartLibCore:allTests,
chartLib:compileDebugKotlin, chartLibCompose:assembleDebug,
app:assembleDebug, full ./gradlew test all pass.

Updated KotlinMultiplatform.md with the changelog entry, marking
Step A.8 complete (as a proof-of-concept).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add a working, runnable Compose Multiplatform demo that ties together
every proof-of-concept from Steps A.7/A.8: axis grid lines, a bar
chart data renderer, and pan/pinch-zoom gestures, all built exclusively
on chartLibComposeMultiplatform (never the Android-only chartLib/
chartLibCompose), and shown identically on Android, Desktop and iOS.

Two Gradle modules instead of one, because AGP 9's new KMP DSL forbids
combining com.android.application with org.jetbrains.kotlin.
multiplatform's androidTarget() in the same project:

- demoKmp (new, com.android.kotlin.multiplatform.library, same shape
  as chartLibComposeMultiplatform): commonMain/DemoKmpApp.kt holds the
  shared @composable (a 5-entry BarDataSet rendered inside a Canvas
  via the Step A.7 renderers, with Step A.8's
  Modifier.chartTransformGestures attached); desktopMain/Main.kt (also
  runnable via :demoKmp:run); iosMain/MainViewController.kt
  (ComposeUIViewController-based entry point for a future Xcode
  wrapper).
- demoKmpAndroid (new, plain com.android.application, not
  multiplatform): MainActivity/AndroidManifest.xml/applicationId,
  depending on project(":demoKmp") for the shared composable.

Both modules registered in settings.gradle.kts. demoKmp's compileSdk
bumped to 37 (Compose Multiplatform 1.12.1's Android artifacts require
it, matching chartLibComposeMultiplatform); demoKmpAndroid needed the
same bump plus its own distinct namespace to avoid an AGP
manifest-merger clash with demoKmp.

Verified: demoKmpAndroid:assembleDebug, demoKmp:compileKotlinDesktop
plus a manual demoKmp:run smoke-test, demoKmp:
compileKotlinIosSimulatorArm64, and the full demoKmp:build (all
targets, including linking debug/release iosArm64/
iosSimulatorArm64 frameworks) all pass. Full existing chain
(chartLibComposeMultiplatform:build, chartLibCore:allTests,
chartLib:compileDebugKotlin, chartLibCompose:assembleDebug,
app:assembleDebug, ./gradlew test) also still green.

Not yet done: an actual Xcode wrapper project embedding
MainViewController(), a wasmJsMain target, and richer demo content
(multiple chart types/screens).

Updated KotlinMultiplatform.md with the changelog entry, marking
Step A.9 complete.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add a new KotlinMultiplatform job to .github/workflows/pullrequest.yml
on macos-latest, since iOS simulator targets require Xcode and the
existing "Check" job (ubuntu-latest) silently skips them via the
kotlin.native.ignoreDisabledTargets=true gradle.properties flag added
during the earlier CI fix. That means chartLibCore/
chartLibComposeMultiplatform's iOS test coverage, added throughout
Steps A.3-A.9, had never actually executed in CI until now.

New job steps:
- Install JDK 17 + Android SDK (chartLibCore/
  chartLibComposeMultiplatform/demoKmp all still have an android
  target too).
- ./gradlew :chartLibCore:allTests :chartLibComposeMultiplatform:allTests
  -- now genuinely runs the Android host, Desktop, and iOS simulator
  test suites.
- ./gradlew :demoKmp:build :demoKmpAndroid:assembleDebug -- builds
  every demoKmp target (including linking the iOS frameworks) plus
  the separate Android app module.
- A desktop smoke-test step: launches :demoKmp:run in the background,
  waits 25s, and asserts the process is still running (i.e. the
  Compose Desktop window launched without an early crash) before
  killing it -- a lightweight substitute for a full screenshot job,
  left as explicitly optional in the plan.
- Archives chartLibCore/chartLibComposeMultiplatform test reports as
  a build artifact for failure diagnosis.

Verified locally (this sandbox has Xcode installed):
./gradlew :chartLibCore:allTests :chartLibComposeMultiplatform:allTests
passes; the demoKmp:run-then-check-still-alive shell logic was
manually confirmed to correctly detect a successfully launched
long-lived process. The workflow YAML was validated with
python3 -c "import yaml; yaml.safe_load(...)" for syntax correctness
(GitHub's own runners couldn't be exercised directly from this
environment).

Not yet done: a wasmJs target (none of the KMP modules target it yet,
so no CI job is needed until one exists) and a real screenshot-diff
job for demoKmp.

Updated KotlinMultiplatform.md with the changelog entry, marking
Step A.10 complete.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…platform

Add com.vanniktech.maven.publish to chartLibCore and
chartLibComposeMultiplatform, mirroring the existing chartLib/chartLibCompose
mavenPublishing { coordinates(...); pom {}; repositories { GitHubPackages } }
pattern. No configure(KotlinMultiplatform(...)) call is needed: the plugin
auto-detects com.android.kotlin.multiplatform.library projects and configures
per-target publications (android/desktop/iosArm64/iosSimulatorArm64[/iosX64])
plus a root kotlinMultiplatform metadata publication automatically.

Fixed two Gradle/plugin interaction bugs surfaced while wiring this up:

- Root gradle.properties' POM_ARTIFACT_ID=chart raced with each module's own
  coordinates(artifactId = ...) call for KMP projects, because the plugin's
  automatic pomFromGradleProperties() and our explicit coordinates() call
  both rename every per-target publication's artifactId in afterEvaluate, and
  the KMP rename logic requires the *current* artifactId to still start with
  "$projectName-". Fixed with per-module gradle.properties files
  (chartLibCore/gradle.properties, chartLibComposeMultiplatform/gradle.properties)
  overriding POM_ARTIFACT_ID so both renames agree.
- Applying id("com.vanniktech.maven.publish") version "0.37.0" independently
  in 4 sibling subprojects made Gradle load the plugin under different
  classloaders for some project pairs, breaking its shared Maven Central
  BuildService. Fixed by declaring the versioned plugin once (apply false) in
  the root build.gradle.kts's new plugins {} block and referencing it without
  a version in all four subprojects.

demoKmp/demoKmpAndroid remain unpublished (demo apps, not libraries).

Verified via ./gradlew :chartLib:publishToMavenLocal :chartLibCompose:publishToMavenLocal
:chartLibCore:publishToMavenLocal :chartLibComposeMultiplatform:publishToMavenLocal
-PRELEASE_SIGNING_ENABLED=false (all four succeed with correct per-target
artifact IDs), plus the full existing verification chain (chartLibCore:allTests,
chartLibComposeMultiplatform:allTests, chartLib:compileDebugKotlin,
chartLibCompose:assembleDebug, app:assembleDebug, demoKmp:build,
demoKmpAndroid:assembleDebug, full ./gradlew test).

Updates KotlinMultiplatform.md's Step A.11 changelog entry and detailed
reference section, marking it [x] — this was the final remaining step from
the original KMP plan.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant