Skip to content

Commit 46f1075

Browse files
RD-1142: Add MTLine Supporting Enums (#52)
1 parent 408bf5e commit 46f1075

File tree

13 files changed

+691
-1
lines changed

13 files changed

+691
-1
lines changed

.github/copilot-instructions.md

Lines changed: 288 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,288 @@
1+
<!-- AGENT_DIRECTIVES v1 -->
2+
3+
Priority: Follow these directives unless they conflict with system/developer instructions or safety rules.
4+
5+
## System Context
6+
7+
You are an AI agent specialized in Kotlin (Android/Compose) and JavaScript development. This document defines mandatory rules for consistent, secure, and maintainable development. Follow the sections below precisely:
8+
9+
- Project Overview
10+
- Pre-Implementation Checklist
11+
- Bridge Rules
12+
- Code Style Guidelines
13+
- ktlint Compliance
14+
- Development Best Practices
15+
- Kotlin Coroutines
16+
- Project Structure
17+
- Style Lifecycle
18+
- Error Handling
19+
- Privacy
20+
- Tests
21+
- Pull Request Template
22+
23+
## Project Overview
24+
25+
This Android library wraps the MapTiler JS SDK via a typed Kotlin↔JS bridge located in `MapTilerSDK/src/main/java/com/maptiler/maptilersdk/bridge`. It renders a web map inside an Android `WebView` hosted by a Compose `MTMapView` and managed by `MTMapViewController`. The HTML container (`src/main/assets/MapTilerMap.html`) loads `maptiler-sdk.umd.min.js`. Kotlin APIs translate to JS via `MTCommand`, executed by `MTBridge` through `WebViewExecutor`. The JS SDK API reference is embedded under `assets` (open `maptiler-sdk.umd.min.js.map` for sources mapping) and the JS docs live in the JS repo; search in this repo and JS bundle for the needed API. Always consult the docs/bundle before wrapping any function.
26+
27+
### Main Components
28+
29+
- UI (Compose): `MTMapView` is a Compose `@Composable` that attaches a configured `WebView`.
30+
- UI (XML): `MTMapViewClassic` is a `FrameLayout`-based view that inflates a `WebView` from `res/layout/mtmapview_layout.xml`. Initialize it by calling `initialize(referenceStyle, options, controller, styleVariant)` after inflation.
31+
- Controller: `MTMapViewController` binds coroutines, holds `MTStyle` and `MTMapOptions`, and exposes navigation/style APIs and events.
32+
- Bridge: Public Kotlin APIs construct `MTCommand`s serialized to JS, executed by `WebViewExecutor`; results decode via `MTBridgeReturnType`.
33+
- Lifecycle: `EventProcessor` listens to JS events, updates state, and notifies `MTMapViewDelegate` (`ON_READY`, `ON_IDLE`, etc.).
34+
- Safety: Mutate map after `ON_READY`. Style changes reapply queued layers; `MTStyle` manages layer queueing.
35+
- Responsibilities: `MTBridge` executes commands; `MTCommand` defines JS; `WebViewExecutor` calls `evaluateJavascript` on `WebView` on the main thread.
36+
37+
## Pre-Implementation Checklist
38+
Before writing ANY new code, you MUST:
39+
- Search for existing related types: `rg -n "MT[TypeName]|[RelatedConcept]"` in this repo.
40+
- Read similar implementations completely (commands, workers, options, style, types).
41+
- Identify established patterns and shared types to reuse (e.g., options models, helpers, workers).
42+
- Confirm no existing types can be reused before creating new ones.
43+
- Follow ktlint rules enforced by the Gradle plugin.
44+
- Run `./gradlew ktlintCheck` and fix all violations locally. Optionally run `./gradlew :MapTilerSDK:ktlintCheck` for module scope.
45+
- If you add public API, add KDoc for it and ensure Dokka can generate docs (`./gradlew :MapTilerSDK:dokkaHtml`).
46+
- For UI changes, verify both Compose (`MTMapView`) and XML (`MTMapViewClassic`) usage paths compile and behave consistently.
47+
48+
## Bridge Rules
49+
50+
MUST follow this end-to-end flow when wrapping a JS API into Kotlin:
51+
52+
1) Discover and design
53+
- Read the JS API in MapTiler SDK for JS docs/bundle; determine parameters, defaults, and return type.
54+
- Define an internal Kotlin `data class` implementing `MTCommand` with strongly typed parameters. Use `@Serializable` surrogates for JSON payloads when needed.
55+
56+
2) Encode parameters
57+
- Use `kotlinx.serialization` via `JsonConfig.json.encodeToString(...)` to build a compact JSON payload; avoid manual string concatenation where possible.
58+
- Validate/clamp numeric ranges in Kotlin prior to execution (zoom, pitch, bearing, durations).
59+
60+
3) Implement `toJS()`
61+
- Build a `JSString` that calls the JS API. Prefer passing a single options JSON object.
62+
- If easing functions or callbacks are needed, express as a JS expression string compatible with the bundle.
63+
64+
4) Choose execution and return type
65+
- For commands with no meaningful return: set `isPrimitiveReturnType = false` and just return the invocation string.
66+
- For numeric/boolean/string results: set `isPrimitiveReturnType = true`; the result decodes into `MTBridgeReturnType` variants: `DoubleValue`, `BoolValue`, `StringValue`, `StringDoubleDict`, `Null`.
67+
- If a new return shape is required, extend `MTBridgeReturnType` in a focused change with tests.
68+
69+
5) Public API surface
70+
- Add a thin convenience method on `MTMapViewController` (or appropriate worker/service) that:
71+
- Ensures the map/style are ready (`ON_READY`).
72+
- Validates inputs and applies sensible defaults.
73+
- Launches on the appropriate coroutine scope and calls the bridge.
74+
- Uses `suspend` functions for getters and `Result`/exceptions for failures where appropriate.
75+
76+
6) Threading and lifecycle
77+
- MUST execute JS on the main thread. `WebViewExecutor` already enforces `Dispatchers.Main`; do not bypass it.
78+
- Avoid firing commands before the `WebView`/bridge is available; prefer queuing via style/lifecycle or guard on readiness.
79+
80+
7) Testing
81+
- Unit test: parameter encoding and range clamping.
82+
- Contract test: `toJS()` string for simple cases (e.g., duration only).
83+
84+
Example skeleton:
85+
86+
```kotlin
87+
@Serializable
88+
private data class RotateToOptions(
89+
val bearing: Double,
90+
val duration: Double? = null,
91+
)
92+
93+
internal data class RotateTo(
94+
private val bearing: Double,
95+
private val durationMs: Double? = null,
96+
) : MTCommand {
97+
override val isPrimitiveReturnType: Boolean = false
98+
99+
override fun toJS(): JSString {
100+
val opts = RotateToOptions(bearing = bearing, duration = durationMs)
101+
val json = JsonConfig.json.encodeToString(opts)
102+
return "${MTBridge.MAP_OBJECT}.rotateTo($json);"
103+
}
104+
}
105+
106+
// Controller convenience API
107+
fun MTMapViewController.setBearing(
108+
bearing: Double,
109+
durationMs: Double? = null,
110+
) {
111+
val clamped = ((bearing % 360 + 360) % 360)
112+
coroutineScope?.launch { bridge?.execute(RotateTo(clamped, durationMs)) }
113+
}
114+
```
115+
116+
XML usage example (Classic view):
117+
118+
```xml
119+
<!-- app/src/main/res/layout/activity_main.xml -->
120+
<com.maptiler.maptilersdk.map.MTMapViewClassic
121+
android:id="@+id/mtClassic"
122+
android:layout_width="match_parent"
123+
android:layout_height="match_parent" />
124+
```
125+
126+
```kotlin
127+
// In Activity/Fragment after setContentView / onViewCreated
128+
val controller = MTMapViewController(requireContext())
129+
val classic = view.findViewById<MTMapViewClassic>(R.id.mtClassic)
130+
classic.initialize(
131+
referenceStyle = MTMapReferenceStyle.SomeStyle,
132+
options = MTMapOptions(/* ... */),
133+
controller = controller,
134+
styleVariant = null,
135+
)
136+
137+
override fun onDestroy() {
138+
super.onDestroy()
139+
controller.destroy()
140+
}
141+
```
142+
143+
### Add a new command (checklist)
144+
- Read target JS API and confirm params/return.
145+
- Create command data class + `@Serializable` surrogate if needed.
146+
- Implement `toJS()` with a single options object.
147+
- Set `isPrimitiveReturnType` correctly and handle `MTBridgeReturnType` if you need the value.
148+
- Add `MTMapViewController` (or worker) convenience API; validate readiness and inputs.
149+
- Tests: encoding, clamping, and `toJS()` contract.
150+
151+
## Code Style Guidelines (MANDATORY)
152+
153+
- Public entities use the `MT` prefix (e.g., `MTMapView`, `MTStyle`). Domain types like `LngLat` are established exceptions.
154+
- Types use PascalCase; variables/functions use camelCase. Constants use UPPER_SNAKE_CASE where applicable.
155+
- Prefer `val` over `var`; minimize mutable state; use `data class` for value objects.
156+
- 4-space indentation; default parameters at the end of parameter lists.
157+
- End files with exactly one trailing newline.
158+
- Line length: target 120 characters max (code and comments). Wrap KDoc accordingly.
159+
- Nullability: Prefer non-null types; use `?` and safe calls sparingly, with clear invariants.
160+
- Visibility: Keep implementation details `internal`/`private`. Avoid unnecessary public APIs.
161+
- Packages: `com.maptiler.maptilersdk.[area]` and mirror directory structure.
162+
163+
## ktlint Compliance (MANDATORY)
164+
- ALWAYS follow the rules enforced by the `org.jlleitschuh.gradle.ktlint` plugin.
165+
- Key rules: spacing, imports order, trailing newline, indentation, annotation/kdoc formatting, no wildcard imports.
166+
- Pre-commit: a Git hook runs `./gradlew ktlintCheck`. Ensure zero violations before committing.
167+
- CI/Local requirement: run `./gradlew ktlintCheck` and ensure zero warnings/errors. PRs must be lint-clean.
168+
- Formatting: the project disables `ktlintFormat` by default; fix violations manually or enable formatting locally if needed.
169+
170+
## Development Best Practices
171+
172+
- API surface: use `public` only for intended external API; default to `internal` within the SDK module.
173+
- Encapsulation: keep workers/services internal to their domains (navigation, zoomable, style, gestures).
174+
- Errors: represent SDK failures with typed `MTError` and propagate meaningful messages.
175+
- Documentation: document every public declaration with concise KDoc (`/** ... */`).
176+
- Tests: prioritize bridge commands, options encoding, and `toJS()` contracts.
177+
- Logging: use `MTLogger` with appropriate `MTLogType`; never log secrets.
178+
179+
## Kotlin Coroutines
180+
181+
- Main thread safety:
182+
- Interacting with `WebView` must occur on `Dispatchers.Main` (handled in `WebViewExecutor`).
183+
- Compose UI code runs on the main thread; do not perform blocking work in Composables.
184+
- Scope:
185+
- Use the controller’s bound `CoroutineScope` for map operations.
186+
- Prefer `suspend` APIs for bridge reads (e.g., getters like `getZoom()`).
187+
- Sendability/Immutability:
188+
- Use immutable `data class` models; avoid shared mutable state across coroutines.
189+
- Avoid `@OptIn(DelicateCoroutinesApi::class)`; keep structured concurrency.
190+
191+
## Project Structure
192+
193+
### Top-Level
194+
195+
- `README.md`: Usage, Compose snippet, sources/layers, annotations, installation.
196+
- `CHANGELOG.md`, `CONTRIBUTING.md`, `LICENSE.txt`: Project meta.
197+
- Gradle: `build.gradle.kts`, `settings.gradle.kts`, `gradle.properties`, wrapper.
198+
- Lint/Docs: ktlint plugin configured in Gradle; Dokka for KDoc.
199+
- `.github/`, `scripts/`: CI, hooks, and scripts scaffolding.
200+
201+
### Library: `MapTilerSDK`
202+
203+
- `map/`: Core UI and map API.
204+
- `MTMapView` (Compose) attaches a `WebView`; exposes map/style APIs via controller and lifecycle events.
205+
- `MTMapViewClassic` (XML) is a `FrameLayout` that inflates a `WebView` from `res/layout/mtmapview_layout.xml` and must be initialized via `initialize(...)`.
206+
- `MTMapViewController`: camera, events, gesture service, workers binding.
207+
- `MTMapOptions` + `options/`: camera, padding, animation, gestures config.
208+
- `style/`: `MTStyle`, reference styles/variants, glyphs/terrain/tile scheme, style errors.
209+
- `gestures/`: gesture types and services (pan, pinch/rotate/zoom, double tap).
210+
- `types/`: shared types (e.g., `LngLat`, `MTPoint`, colors, source data).
211+
- `bridge/`: Kotlin ↔ JS bridge via `WebView`.
212+
- `MTCommand`, `MTBridge`, `WebViewExecutor`, `MTBridgeReturnType`, `MTError`.
213+
- `MTJavaScriptInterface` for events and error propagation from JS.
214+
- `commands/`: Strongly-typed wrappers that turn Kotlin calls into JS invocations.
215+
- `navigation/`: flyTo, easeTo, jumpTo, pan/zoom/bearing/pitch/roll, bounds, padding.
216+
- `style/`: add/remove sources and layers, set style, language, light, glyphs, projection, terrain.
217+
- `annotations/`: add/remove markers and text popups, set coordinates, batch ops.
218+
- `gestures/`: enable/disable gesture types.
219+
- `annotations/`: Public annotation APIs (`MTMarker`, `MTTextPopup`, base `MTAnnotation`).
220+
- `events/`: Event pipeline that feeds `MTMapViewDelegate` and content delegates.
221+
- `helpers/`: serialization config, color/coordinates converters, image helpers.
222+
- `logging/`: `MTLogger`, types, and adapters.
223+
- `assets/`: Embedded JS/CSS/HTML for the web map container.
224+
225+
### Tests
226+
227+
- `MapTilerSDK/src/test/java`: Unit tests using JUnit/MockK.
228+
- Helpers: coordinate and color conversions, camera helpers.
229+
- Suites: navigation and bridge tests (toJS contracts, encoding, return type handling).
230+
231+
### Kotlin API Surface (Prefer Stronger Kotlin Types)
232+
- Prefer expressive Kotlin-first APIs that hide JS-specific details while encoding the correct JS schema under the hood.
233+
- For values that are strings in JS but have richer domain types (e.g., colors), expose ergonomic initializers and helpers.
234+
- Validate inputs and clamp numeric ranges in Kotlin before bridging to JS.
235+
- Default parameter values should reflect sensible SDK defaults.
236+
237+
#### Developer-Facing Simplicity (MANDATORY)
238+
- Prefer Kotlin-first types over raw strings in public APIs (e.g., enums, value classes, typed models).
239+
- Numbers/angles: provide `.constant(Double)` and zoom-stop helpers where domain types exist.
240+
- Mixed unions: accept both collections and strings where JS unions require it; encode internally.
241+
- Add minimal tests to verify encoding and `toJS()` contracts for these conveniences.
242+
243+
## Style Lifecycle
244+
245+
MUST wait for `ON_READY` before mutating style or layers. Changing the reference style resets layers; re-add required sources/layers after style changes. Prefer batch commands where available. When enabling terrain or projection changes, verify map idleness before subsequent camera moves.
246+
247+
## Error Handling
248+
249+
- Retry once on transient bridge failures; log verbosely under `MTLogLevel.Debug`.
250+
- Return clear, user-facing messages with the failed command and suggested fix.
251+
- Treat unsupported return types as warnings; choose a safer path or request input.
252+
253+
## Privacy
254+
255+
- Never log API keys. Redact sensitive values from structured logs.
256+
- Avoid sending exact user coordinates unless necessary; round or fuzz where acceptable.
257+
258+
## Tests
259+
260+
- Before you make the Pull Request ALWAYS run unit tests to validate the code and fix potential issues.
261+
- Commands:
262+
- `./gradlew ktlintCheck`
263+
- `./gradlew :MapTilerSDK:test`
264+
- Optional: `./gradlew :MapTilerSDK:dokkaHtml` to validate docs generation
265+
- Add or update unit tests as required (encoding, clamping, `toJS()` contract), but leave full execution to the user/CI.
266+
- Prefer small, focused tests near the code you change; avoid introducing unrelated tests.
267+
268+
## Glossary
269+
270+
- `JSString`: A Kotlin `String` containing JS source to evaluate in the `WebView`.
271+
- `MTCommand`: A Kotlin type describing a JS-callable command (`toJS()` returns `JSString`).
272+
- Bridge executor: The component that feeds `JSString` into `evaluateJavascript` on the Android `WebView`.
273+
- `MTBridgeReturnType`: Decoders for typed return values from JS.
274+
275+
Cross-reference implementation against original prompt requirements before making pull request, and make sure to follow the Pull Request Template below:
276+
277+
## Pull Request Template
278+
279+
[Link to related issue]
280+
281+
## Objective
282+
What is the goal?
283+
284+
## Description
285+
What changed, how and why?
286+
287+
## Acceptance
288+
How were changes tested?

0 commit comments

Comments
 (0)