diff --git a/.github/workflows/blog-prose.yml b/.github/workflows/blog-prose.yml index 325981ea249..e4599f5f7fd 100644 --- a/.github/workflows/blog-prose.yml +++ b/.github/workflows/blog-prose.yml @@ -110,6 +110,26 @@ jobs: echo "status=$?" >> "$GITHUB_OUTPUT" set -e + - name: Set up Node 20 for Mermaid validation + if: steps.detect.outputs.has_posts == 'true' + uses: actions/setup-node@v6 + with: + node-version: '20' + + - name: Install Playwright Chromium + if: steps.detect.outputs.has_posts == 'true' + run: | + set -euo pipefail + cd scripts + npm init -y 2>/dev/null || true + npm install playwright + npx playwright install-deps chromium + npx playwright install chromium + + - name: Validate blog Mermaid diagrams + if: steps.detect.outputs.has_posts == 'true' + run: node scripts/website/validate_mermaid.mjs docs/website/content/blog + - name: Upload net-new findings report if: ${{ always() && steps.detect.outputs.has_posts == 'true' }} uses: actions/upload-artifact@v7 diff --git a/docs/website/assets/css/extended/cn1-blog-post.css b/docs/website/assets/css/extended/cn1-blog-post.css index 3522c795c66..3f049f7b93d 100644 --- a/docs/website/assets/css/extended/cn1-blog-post.css +++ b/docs/website/assets/css/extended/cn1-blog-post.css @@ -80,6 +80,46 @@ body.dark .cn1-blog-post .post-content table td { border-color: rgba(175, 193, 255, 0.25); } +.cn1-blog-post .blog-series-nav { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 12px; + margin: 30px 0 22px; +} + +.cn1-blog-post .blog-series-nav a { + display: block; + min-height: 54px; + padding: 12px 14px; + border: 1px solid rgba(28, 81, 255, 0.18); + border-radius: 8px; + background: rgba(28, 81, 255, 0.04); + color: var(--primary); + font-size: 14px; + font-weight: 600; + line-height: 1.35; +} + +.cn1-blog-post .blog-series-nav a:hover { + border-color: rgba(28, 81, 255, 0.36); + background: rgba(28, 81, 255, 0.08); + text-decoration: none; +} + +.cn1-blog-post .blog-series-nav__next { + text-align: right; +} + +body.dark .cn1-blog-post .blog-series-nav a { + border-color: rgba(175, 193, 255, 0.24); + background: rgba(175, 193, 255, 0.08); +} + +body.dark .cn1-blog-post .blog-series-nav a:hover { + border-color: rgba(175, 193, 255, 0.42); + background: rgba(175, 193, 255, 0.12); +} + @media (max-width: 900px) { .cn1-blog-post .post-content { font-size: 16px; @@ -96,4 +136,12 @@ body.dark .cn1-blog-post .post-content table td { .cn1-blog-post .post-content h4 { font-size: 19px; } + + .cn1-blog-post .blog-series-nav { + grid-template-columns: 1fr; + } + + .cn1-blog-post .blog-series-nav__next { + text-align: left; + } } diff --git a/docs/website/content/blog/carplay-android-auto-codename-one.md b/docs/website/content/blog/carplay-android-auto-codename-one.md new file mode 100644 index 00000000000..84d275be7c5 --- /dev/null +++ b/docs/website/content/blog/carplay-android-auto-codename-one.md @@ -0,0 +1,142 @@ +--- +title: "CarPlay And Android Auto From One Codename One API" +slug: carplay-android-auto-codename-one +url: /blog/carplay-android-auto-codename-one/ +date: '2026-07-04' +author: Shai Almog +description: Codename One now projects driver-safe app screens to Apple CarPlay and Google Android Auto through one portable template API, with simulator support and zero native wiring when unused. +feed_html: 'CarPlay And Android Auto Codename One now projects driver-safe app screens to Apple CarPlay and Google Android Auto through one portable template API.' +series: ["release-2026-07-03"] +--- + +![CarPlay And Android Auto From One Codename One API](/blog/carplay-android-auto-codename-one.jpg) + +Yesterday's release post was about the bigger business line we will not cross: no royalties on IAP, ads, commerce or app revenue. This post is about one of the most concrete platform additions in that release. [PR #5281](https://github.com/codenameone/CodenameOne/pull/5281) adds Apple CarPlay and Google Android Auto support under `com.codename1.car`. + +The first thing to know is what this is not. CarPlay and Android Auto are not second screens where your normal Codename One `Form` is drawn. They are driver-safe, template-based systems. Apple and Google decide which templates are legal in a car, how many rows can appear, which app categories are allowed, and which interactions are safe while driving. + +Codename One now gives you one portable way to describe those templates. + +![A CarPlay list template generated from the portable car model](/blog/carplay-android-auto-codename-one/carplay-list.png) + +![An Android Auto grid template generated from the same portable car model](/blog/carplay-android-auto-codename-one/android-auto-grid.png) + +## The Template Model + +A car app registers a `CarApplication`. When a head unit connects, the application returns a root `CarScreen`. Each screen returns one `CarTemplate`: list, grid, pane, message, navigation, or now-playing. + +```java +import com.codename1.car.*; + +public class MyApp { + public void init(Object context) { + Car.setApplication(new MyCarApplication()); + } +} + +class MyCarApplication extends CarApplication { + public CarScreen onCreateRootScreen(CarContext context) { + return new LibraryScreen(); + } +} + +class LibraryScreen extends CarScreen { + protected CarTemplate onCreateTemplate() { + return new CarListTemplate().setTitle("Library") + .addRow(new CarRow("Now Playing") + .setText("Daft Punk - Discovery") + .setOnAction(ctx -> play())) + .addRow(new CarRow("Albums") + .setBrowsable(true) + .setOnAction(ctx -> ctx.pushScreen(new AlbumsScreen()))) + .addRow(new CarRow("Playlists") + .setBrowsable(true) + .setOnAction(ctx -> ctx.pushScreen(new PlaylistsScreen()))); + } +} +``` + +That code maps to `CPListTemplate` on CarPlay and `ListTemplate` on Android Auto. Grid items map to `CPGridButton` and `GridItem`. The now-playing template routes to the system media surface. Navigation gets a template shell with controls and ETA strips; the moving map surface is scaffolded but still being completed. + +## Navigation And Lifecycle + +`CarContext` gives you a head-unit stack: + +```java +context.pushScreen(new DetailScreen()); +context.popScreen(); +screen.invalidate(); +context.showToast("Added to queue"); +``` + +`CarScreen` has lifecycle hooks for create, resume, pause, and destroy. `CarApplication` receives connect and disconnect callbacks, and you can listen globally: + +```java +Car.addConnectionListener(new CarConnectionListener() { + public void carConnected(CarContext ctx) { + startLocationStream(); + } + + public void carDisconnected() { + stopLocationStream(); + } +}); +``` + +That is a good place to start and stop work that only exists while the dashboard is connected. + +## Zero Cost When Unused + +The build system scans your bytecode. If it sees `com.codename1.car`, it injects the native wiring. If it does not, the app carries none of this. + +{{< mermaid >}} +flowchart TD + A["Your app bytecode"] --> B{"References com.codename1.car?"} + B -->|no| C["Normal iOS / Android build
no car dependencies"] + B -->|yes| D["Inject car support"] + D --> E["iOS: CarPlay scene
framework + entitlement"] + D --> F["Android: CarAppService
androidx.car.app dependency"] + E --> G["Native head unit templates"] + F --> G +{{< /mermaid >}} + +That is especially important for a framework with one codebase. A normal shopping app, game, or business tool should not gain a CarPlay dependency just because the framework knows how to build one. + +## Build Hints And Approval + +Car app categories matter. The build can inject wiring, but Apple and Google still decide whether an app is allowed into the car. + +```properties +ios.carplay.audio=true +ios.carplay.navigation=true +android.androidAuto.navigation=true +android.androidAuto.poi=true +``` + +Audio is the default CarPlay category when no iOS car category is specified. Messaging, navigation and point-of-interest categories need the matching hints. Restricted Android Auto categories and real CarPlay deployment still require platform approval and, for iOS, the relevant CarPlay entitlement on your Apple App ID. + +That is not a Codename One limitation. It is the safety model of the car platforms. + +## Simulator Head Units + +The simulator has a `Car` menu now. `Car > Connect CarPlay` and `Car > Connect Android Auto` open a simulated head-unit window that renders the same portable template tree. It is not a pixel-perfect emulator, but it gives you a fast loop for stack navigation, row caps and action callbacks. + +![The Codename One simulator rendering a CarPlay-style list head unit](/blog/carplay-android-auto-codename-one/car-sim-list.png) + +Selecting a browsable row pushes the next screen: + +![The Codename One simulator rendering a CarPlay-style grid head unit](/blog/carplay-android-auto-codename-one/car-sim-grid.png) + +That is the right level of simulation. You can build the car experience while still working in the normal desktop loop, then use Apple's CarPlay simulator or Android's Desktop Head Unit when you need to test the native projection layer. + +## Wrapping Up + +CarPlay and Android Auto support is a platform capability, not a revenue feature. There is no extra royalty because your podcast app, navigation app, or point-of-interest app reaches a dashboard. You still need Apple and Google approval for the categories they restrict, and you still need to design within the car template catalogue. Codename One now gives you the portable model and build wiring so that work lives in one codebase. + +--- + +## Discussion + +_Join the conversation via GitHub Discussions._ + +{{< giscus >}} diff --git a/docs/website/content/blog/commerce-secrets-without-iap-tax.md b/docs/website/content/blog/commerce-secrets-without-iap-tax.md new file mode 100644 index 00000000000..d19fce94d98 --- /dev/null +++ b/docs/website/content/blog/commerce-secrets-without-iap-tax.md @@ -0,0 +1,150 @@ +--- +title: "Commerce And Secrets Without An IAP Tax" +slug: commerce-secrets-without-iap-tax +url: /blog/commerce-secrets-without-iap-tax/ +date: '2026-07-06' +author: Shai Almog +description: Commerce validates receipts and normalizes entitlements, while Secrets keeps credentials out of the binary. Both are designed to fail soft, stay optional, and avoid turning IAP into a Codename One royalty. +feed_html: 'Commerce And Secrets Without An IAP Tax Commerce validates receipts and normalizes entitlements, while Secrets keeps credentials out of the binary without adding a Codename One royalty to IAP.' +series: ["release-2026-07-03"] +--- + +![Commerce And Secrets Without An IAP Tax](/blog/commerce-secrets-without-iap-tax.jpg) + +Commerce is the easiest feature in this release to misunderstand, so the first sentence has to be blunt: + +**Commerce does not replace IAP and never will.** + +Purchases still go through Apple, Google, or the payment processor you chose. Codename One does not process the payment, does not touch the money, and does not take a percentage. [PR #5300](https://github.com/codenameone/CodenameOne/pull/5300) adds infrastructure around the annoying backend work that comes after a purchase: validation, entitlement checks, subscription lifecycle, webhooks, and reporting. + +That backend work is real. Anyone who has shipped subscriptions knows the trap. Buying a SKU is not the same as knowing whether the user has the right to a feature right now. Renewals, grace periods, refunds, billing retry, product changes, trials, family sharing and store server notifications all show up later. The device has one view. The store has another. Your backend usually needs a third. + +Commerce is the optional service that turns that mess into an entitlement. + +![Commerce dashboard for receipt validation, entitlements and revenue metrics](/blog/commerce-secrets-without-iap-tax/commerce.png) + +{{< mermaid >}} +flowchart TD + A["App calls CommerceManager.subscribe()"] --> B["Purchase API"] + B --> C["Apple / Google store flow"] + C --> D["Store receipt"] + D --> E["Commerce refresh()"] + E --> F["Cloud receipt validation"] + F --> G["Entitlement cache"] + G --> H["isEntitled(pro)"] + C --> StoreOK["Purchase still completes even if cloud validation is unavailable"] +{{< /mermaid >}} + +## Entitlements Instead Of SKU Branches + +Your app should not need to know every SKU that grants `pro`. It should ask for `pro`. + +```java +CommerceManager cm = CommerceManager.getInstance(); +cm.setAppUserId(accountId); + +if (cm.isEntitled("pro")) { + unlockProFeatures(); +} +``` + +Purchases are still delegated to the existing `Purchase` API: + +```java +cm.subscribe("pro_monthly"); +// or +cm.purchase("remove_ads"); +``` + +After a purchase, or when the app starts, refresh off the EDT: + +```java +new Thread(() -> { + CommerceManager cm = CommerceManager.getInstance(); + cm.refresh(); + + CN.callSerially(() -> { + if (cm.isEntitled("pro")) { + unlockProFeatures(); + } + }); +}).start(); +``` + +`refresh()` validates the current receipts with the cloud when the build has a `build_key` and commerce is enabled. In a local build or simulator, it safely falls back to the normal `Purchase` path. + +## What Happens When Quota Runs Out + +This is the real question that matters most. If Commerce is tiered, what happens when a developer exceeds quota? + +Validation degrades. Purchases do not stop. + +`CommerceManager.isDegraded()` tells you the cloud did not return a server-validated answer. In that state, entitlement checks fall back to the platform's own receipt signal, treating the entitlement id as a subscription SKU when no cached cloud answer exists. That is less rich than server-side validation, but it is the right failure mode. A paying user should not be locked out because your account hit a validated-volume cap. + +```java +cm.refresh(); +if (cm.isDegraded()) { + Log.p("Commerce validation degraded; using store-direct fallback"); +} +``` + +Commerce is tiered because it is a backend service that can be abused: receipt validation, store API calls, lifecycle processing, webhook delivery and revenue analytics cost real infrastructure. The degradation rule is what keeps that business reality from becoming user pain. + +## What Commerce Adds + +The service can: + +- Validate receipts against Apple and Google. +- Normalize subscription state across stores. +- Track entitlements by your app user id. +- Forward lifecycle webhooks to your backend with HMAC signatures. +- Present revenue metrics such as MRR, ARR, ARPU, churn, trial conversion, cohort retention and realized LTV. + +The app-facing API remains small because the complicated part lives server-side: + +```java +CommerceManager cm = CommerceManager.getInstance(); +cm.setAppUserId(myAccountId); +cm.refresh(); +boolean active = cm.isEntitled("remove_ads"); +``` + +That split explains how Commerce complements IAP instead of replacing it. `Purchase` starts the transaction. Commerce answers the longer-term entitlement question. + +## Secrets + +The same PR adds `com.codename1.security.Secrets`, which solves a different but related problem: API keys do not belong in source code or in the app binary. + +![Secrets dashboard for managing app-readable secrets and server-side credentials](/blog/commerce-secrets-without-iap-tax/secrets.png) + +```java +// Run off the EDT; the first call may hit the network. +String mapsKey = Secrets.get("googlemaps.key"); +``` + +The value is fetched from the Codename One Cloud vault over TLS and cached in `SecureStorage`. `refresh(name)` forces a fresh fetch after you rotate the value server-side, and `clear(name)` drops the cached copy. + +```java +String key = Secrets.refresh("googlemaps.key"); +Secrets.clear("googlemaps.key"); +``` + +Only app-readable secrets are served to the device. Server-only credentials, such as App Store Connect keys or Google Play service account JSON used for commerce validation, stay in the vault and are never reachable from client code. + +That rule is non-negotiable: do not check API keys into source, do not paste them into snippets, and do not embed server credentials in the binary. If the app can read a secret, a determined attacker can eventually extract it. Secrets reduces exposure and makes rotation easier; it does not turn a client app into a secure server. + +## The Boundary + +Commerce and Secrets are both cloud features, but they sit on different sides of the volume line. Secrets usage stays low enough to enable it for everyone. Commerce has tiers because validation and analytics can create real backend load. + +The important boundary is not tiering. The boundary is lock-in. You can still use the raw `Purchase` API. You can still build your own receipt backend. You can still ship an app that sells subscriptions without giving Codename One a revenue share. + +Commerce is there to remove backend pain, not to insert a toll booth. + +--- + +## Discussion + +_Join the conversation via GitHub Discussions._ + +{{< giscus >}} diff --git a/docs/website/content/blog/motion-input-form-factors.md b/docs/website/content/blog/motion-input-form-factors.md new file mode 100644 index 00000000000..2fd86e0b517 --- /dev/null +++ b/docs/website/content/blog/motion-input-form-factors.md @@ -0,0 +1,166 @@ +--- +title: "Motion, Stylus, Trackpads And Foldables In The Core" +slug: motion-input-form-factors +url: /blog/motion-input-form-factors/ +date: '2026-07-05' +author: Shai Almog +description: "Two new core APIs expose the hardware modern apps actually see: motion sensors and gestures on one side, rich pointer detail, wheel events, stylus input, trackpads and foldable posture on the other." +feed_html: 'Motion, Stylus, Trackpads And Foldables Motion sensors, gestures, pointer detail, wheel events, stylus input, trackpads and foldable posture are now core Codename One APIs.' +series: ["release-2026-07-03"] +--- + +![Motion, Stylus, Trackpads And Foldables In The Core](/blog/motion-input-form-factors.jpg) + +For a long time, mobile meant a touch screen and a few platform-specific escape hatches. That is no longer enough. A Codename One app can run on phones, tablets, desktops, watches, TVs, browsers, foldables, touch laptops, external displays, cars, and devices with pens, mice, and trackpads. + +[PR #5310](https://github.com/codenameone/CodenameOne/pull/5310) and [PR #5309](https://github.com/codenameone/CodenameOne/pull/5309) make that hardware visible through core APIs instead of cn1libs and platform branches. + +## Motion Sensors And Gestures + +The new `com.codename1.sensors` package exposes accelerometer, gyroscope and magnetometer readings. It also derives gravity, linear acceleration and orientation in the core when the platform does not provide them directly. + +```java +MotionSensorManager sensors = MotionSensorManager.getInstance(); +MotionSensor accel = sensors.getSensor(MotionSensorManager.TYPE_ACCELEROMETER); + +if (accel != null) { + MotionSensorListener listener = new MotionSensorListener() { + public void motionReceived(MotionEvent evt) { + label.setText(evt.getX() + ", " + evt.getY() + ", " + evt.getZ()); + label.getParent().revalidate(); + } + }; + accel.addListener(listener); +} +``` + +The manager itself is always available. Individual sensors return `null` when the hardware is unsupported, so the app can keep one code path with a small capability check. + +Gestures are built on top of the sensor stream in the core: + +```java +MotionSensorManager.getInstance().addGestureListener( + GestureEvent.TYPE_SHAKE, + new GestureListener() { + public void gestureDetected(GestureEvent evt) { + Dialog.show("Shaken", "You shook the device", "OK", null); + } + }); +``` + +The recognized set is practical rather than exotic: shake, flip face down, flip face up, tilt left, tilt right, tilt forward, tilt backward, pick up and free fall. Because recognition lives in `GestureEngine`, every port that exposes an accelerometer gets the same behavior. + +Sampling is reference counted. The hardware powers on while listeners exist and powers down when they are removed. On iOS you still need `ios.NSMotionUsageDescription` with a user-facing reason for motion access. + +## Rich Pointer Detail + +Classic pointer callbacks tell you where a press happened. Modern input asks more questions: was it a mouse, a finger, a stylus, or an eraser? Which button? What pressure? Was the pen tilted? Was the pointer hovering? + +`PointerEvent` is the snapshot attached to pointer action events: + +```java +myComponent.addPointerPressedListener(e -> { + PointerEvent pe = e.getPointerEvent(); + if (pe.isSecondaryButton()) { + showContextMenu(pe.getX(), pe.getY()); + } + drawPreview(pe.getX(), pe.getY(), pe.getPressure()); +}); +``` + +You can also poll the current dispatch state through `CN`: + +```java +int button = CN.getPointerButton(); +float pressure = CN.getPointerPressure(); +boolean pen = CN.isStylusPointer(); +PointerEvent current = CN.getCurrentPointerEvent(); +``` + +On plain touch screens these values have safe defaults: primary button, pressure `1.0`, touch or unknown pointer type. Existing apps keep working. + +## Wheel, Trackpad And Context Menus + +`WheelEvent` is now the cross-platform scroll-gesture event. It handles mouse wheels, precision trackpads, horizontal scrolling, iOS/iPadOS pointer devices and the Apple Watch Digital Crown through the same path. + +```java +canvas.addMouseWheelListener(e -> { + WheelEvent w = (WheelEvent)e; + if (w.isControlDown()) { + zoom(w.getDeltaY()); + w.consume(); + } +}); +``` + +Consuming the event suppresses the default scroll. That is the difference between "wheel scrolls the container" and "control plus wheel zooms a design surface." + +For context menus, use the higher-level listener: + +```java +label.addContextMenuListener(e -> { + e.consume(); + showMyContextMenu(e.getX(), e.getY()); +}); +``` + +It fires for right click, stylus barrel button and long press, which is exactly the kind of cross-platform behavior app code should not have to rediscover. + +Trackpad magnify and rotate gestures route to component methods: + +```java +Container photo = new Container() { + protected boolean pinch(float scale) { + zoom(scale); + return true; + } + + protected boolean rotation(float radians) { + rotate(radians); + return true; + } +}; +``` + +## Stylus And Foldables + +Stylus support exposes pressure, tilt, contact size and eraser type. A drawing surface can listen only to stylus events: + +```java +drawingArea.addStylusListener(e -> { + PointerEvent pe = e.getPointerEvent(); + float width = 1f + pe.getPressure() * 9f; + if (pe.isEraser()) { + erase(pe.getX(), pe.getY()); + } else { + drawPoint(pe.getX(), pe.getY(), width); + } +}); +``` + +Foldables use `DevicePosture`: + +```java +DevicePosture posture = CN.getDevicePosture(); +if (posture.isFoldable() && posture.isTableTop()) { + layoutForTableTop(posture.getFoldBounds(null)); +} + +CN.addPostureListener(e -> relayoutForPosture(CN.getDevicePosture())); +``` + +On Android, foldable support is opt-in with `android.foldableSupport=true`, so apps that do not need the AndroidX window library do not carry it. The simulator can generate posture changes for testing. + +## Wrapping Up + +Input used to mean key codes, pointer coordinates, and maybe a scroll wheel. That model is too small now. A modern app has to reason about pointer type, pressure, hover, buttons, tilt, posture, trackpads, sensor sampling, and gestures that may or may not exist on the current device. + +The new APIs do not make that complexity disappear. They put it behind capability checks and default-safe events, so the same component can handle a finger on a phone, a stylus on a tablet, a mouse on desktop, and a foldable hinge without turning every screen into platform-specific code. + +--- + +## Discussion + +_Join the conversation via GitHub Discussions._ + +{{< giscus >}} diff --git a/docs/website/content/blog/one-codebase-more-surfaces.md b/docs/website/content/blog/one-codebase-more-surfaces.md new file mode 100644 index 00000000000..27deda510a0 --- /dev/null +++ b/docs/website/content/blog/one-codebase-more-surfaces.md @@ -0,0 +1,123 @@ +--- +title: "More Surfaces, Same Deal: Cars, Sensors, Commerce, Video And Builds" +slug: one-codebase-more-surfaces +url: /blog/one-codebase-more-surfaces/ +date: '2026-07-03' +author: Shai Almog +description: "This week's release adds car dashboards, motion sensors, desktop-class input, foldables, Commerce, Secrets, versioned builds, and real video generation while keeping last week's open-source deal intact." +feed_html: 'More Surfaces, Same Deal This release adds car dashboards, motion sensors, desktop-class input, foldables, Commerce, Secrets, versioned builds, and real video generation while keeping last week''s open-source deal intact.' +series: ["release-2026-07-03"] +--- + +![More Surfaces, Same Deal: Cars, Sensors, Commerce, Video And Builds](/blog/one-codebase-more-surfaces.jpg) + +Last week's release post was about funding open source without the bait and switch. This week's release tests that idea again, because two of the new features touch paid infrastructure directly: Commerce and versioned builds. + +That kind of expansion often makes the model slippery. A platform adds something developers need, puts it behind an account tier, and over time the open part gets worse or the paid part starts taxing success. We do not want to drift into that, so the useful question for this week is not just what shipped, but how the paid pieces behave. + +Commerce validates purchases and normalizes entitlements, but it does not replace IAP and it does not take a cut. If quota runs out, validation degrades; the Apple or Google purchase still goes through. Everyone gets the Secrets API because volume stays low, and keeping keys out of source code should not be a luxury feature. Versioned builds are back, including limited access lower down the account ladder, and master builds give the community a way to verify fixes without waiting for nightly artifacts. + +That is the continuation of last week's deal: charge for services that cost money to run, but keep them optional, predictable, and as broadly useful as we can make them. + +The rest of the release pushes the open source core outward: car projection, motion sensors, richer pointer input, foldable posture, frame-accurate video, sample-accurate PCM mixing, and timed subtitles. A phone screen is still the center of most apps, but it is no longer the whole app. A serious product may also need a CarPlay list, an Android Auto grid, a stylus drawing surface, trackpad zoom, motion gestures, a pinned production build, and a generated video clip for sharing or support. + +Here is what shipped. + +## CarPlay and Android Auto + +[PR #5281](https://github.com/codenameone/CodenameOne/pull/5281) adds a portable `com.codename1.car` API for Apple CarPlay and Google Android Auto. The important caveat is that car platforms are template-based. They do not allow an app to draw an arbitrary Codename One `Form` on the dashboard. You describe a driver-safe list, grid, message, pane, navigation, or now-playing template, and Codename One maps that to `CPTemplate` on CarPlay and `androidx.car.app` templates on Android Auto. + +![CarPlay list template rendered by the Codename One car API](/blog/carplay-android-auto-codename-one/carplay-list.png) + +![Android Auto grid template rendered by the Codename One car API](/blog/carplay-android-auto-codename-one/android-auto-grid.png) + +The API is zero cost when unused. Referencing `com.codename1.car` is what tells the build to inject CarPlay scenes, entitlements, Android Auto services, and the AndroidX dependency. Apps that never touch the package do not carry that code. Tomorrow's post walks through the template model, the simulator head unit, and the approvals you still need from Apple and Google. + +## Motion, Input, And Real Hardware + +[PR #5310](https://github.com/codenameone/CodenameOne/pull/5310) adds `com.codename1.sensors`, a cross-platform motion API with accelerometer, gyroscope, magnetometer, derived gravity, linear acceleration, orientation, and common gestures such as shake, flip, tilt, pick up, and free fall. This replaces the old external `sensors-codenameone` cn1lib with a core API and a core gesture engine. + +[PR #5309](https://github.com/codenameone/CodenameOne/pull/5309) fills in the other side of modern hardware: rich pointer events, mouse buttons, wheel and trackpad scrolling, stylus pressure and tilt, foldable posture, desktop windowing, and external display awareness. + +Together they make Codename One less phone-only. A canvas can tell the difference between a finger, a mouse, and a stylus. A foldable can split layout around a hinge. A trackpad pinch can zoom the same component as a mobile pinch. Sunday's post covers the full hardware story. + +## Commerce And Secrets + +[PR #5300](https://github.com/codenameone/CodenameOne/pull/5300) adds the Commerce SDK and the Secrets API. + +Commerce is optional infrastructure around IAP. It answers the backend question apps usually end up writing themselves: "does this user have this entitlement right now?" It can validate receipts, normalize subscription state across Apple and Google, send lifecycle webhooks to your backend, and show revenue metrics. It does not replace `Purchase`, it delegates to it. + +```java +CommerceManager cm = CommerceManager.getInstance(); +cm.setAppUserId(accountId); +cm.subscribe("pro_monthly"); + +new Thread(() -> { + cm.refresh(); + if (cm.isEntitled("pro")) { + unlockProFeatures(); + } +}).start(); +``` + +The Secrets API has deliberately lower volume and is enabled for everyone. It fetches app-readable secrets from the cloud vault at runtime and caches them in `SecureStorage`, so API keys do not need to live in code or in the binary. Server-only credentials, such as App Store or Google Play keys used by commerce validation, stay server-side and are not served to the app. + +Monday's post is mostly a failure-mode pass over Commerce: what it does, what it refuses to do, what happens when quota is exhausted, and why optional validation is not an IAP tax. + +![Commerce dashboard for receipt validation and entitlement tracking](/blog/commerce-secrets-without-iap-tax/commerce.png) + +![Secrets dashboard for cloud-managed app secrets](/blog/commerce-secrets-without-iap-tax/secrets.png) + +## Versioned Builds Are Back + +Versioned builds are back, and the model is better suited to Maven than the old Ant-era point release scheme. You can pin a cloud build to a released Codename One version, or build against `master` when you need to verify an unreleased fix. + +```properties +build.cn1Version=master +``` + +Community developers recently asked for nightly or daily builds. Building against master solves the same verification problem without waiting for an artificial daily package. You are asking the build server to use the current development head. + +Subscription tiers limit versioned build access because old versions create support churn, not because fetching a few artifacts drives the business model. The further back a build goes, the harder it is to diagnose a regression against the current code. The new model still opens versioned builds much further down the account ladder than before, including limited access for basic/free usage, while giving paying teams the longer support windows they actually need. Tuesday's post covers the tradeoff. + +## Video, Audio, And Subtitles + +[PR #5315](https://github.com/codenameone/CodenameOne/pull/5315) adds `VideoIO`: cross-platform video encode and frame-accurate decode using native codecs. It can encode app-rendered frames plus audio into MP4/WebM/MOV/MKV style containers, enumerate codecs, decode exact frames, resample variable-frame-rate clips, and expose the audio track as PCM. + +[PR #5317](https://github.com/codenameone/CodenameOne/pull/5317) adds a sample-accurate `AudioMixer` for combining PCM tracks on one clock. [PR #5319](https://github.com/codenameone/CodenameOne/pull/5319) adds timed Whisper transcription, so generated videos can get SRT or VTT captions instead of plain text transcripts. + +![Frame-accurate VideoIO decode output from the Codename One test app](/blog/videoio-audio-mixer-whisper/videoio-decoded-frames.png) + +Wednesday's post shows how these pieces fit: render frames, mix audio, encode a video, decode frames back out, and attach timed captions. + +## Smaller But Important + +There are four smaller changes worth calling out: + +- **JavaSE upstream JCEF and bundled ffmpeg media.** [PR #5294](https://github.com/codenameone/CodenameOne/pull/5294) moves the simulator away from our home-built Chromium 84 CEF fork and onto upstream prebuilt JCEF. We used to build our own CEF fork mostly to keep H.264 media working. That build was long, fragile and kept us behind. The new approach uses upstream JCEF for the browser and bundled ffmpeg for the Codename One `Media` API. +- **Skin Designer now builds with our JavaScript port.** [PR #5303](https://github.com/codenameone/CodenameOne/pull/5303) moves Skin Designer to the local ParparVM JavaScript target. Initializr and Playground already moved; the console is now the last TeaVM internal tool. This migration did expose a regression, but the JavaScript port is still moving toward production. +- **MorphTransition grew up.** [PR #5314](https://github.com/codenameone/CodenameOne/pull/5314) adds opacity, rotation, scale, deterministic scrubbing, and arbitrary rendered elements. This matters for UI polish and also for frame export, because scrubbing lets the same transition be rendered into generated video. +- **Timed Whisper transcription.** [PR #5319](https://github.com/codenameone/CodenameOne/pull/5319) also deserves a second mention because timed segments turn transcription into subtitles. The cn1lib can render SRT and VTT payloads from segment timestamps. + +## Upcoming Posts + +- **Saturday.** {{< post-link path="/blog/carplay-android-auto-codename-one" text="CarPlay and Android Auto from one template API" >}}. PR [#5281](https://github.com/codenameone/CodenameOne/pull/5281). +- **Sunday.** {{< post-link path="/blog/motion-input-form-factors" text="Motion sensors, pointer detail, stylus, trackpads and foldables" >}}. PRs [#5310](https://github.com/codenameone/CodenameOne/pull/5310) and [#5309](https://github.com/codenameone/CodenameOne/pull/5309). +- **Monday.** {{< post-link path="/blog/commerce-secrets-without-iap-tax" text="Commerce and Secrets without an IAP tax" >}}. PR [#5300](https://github.com/codenameone/CodenameOne/pull/5300). +- **Tuesday.** {{< post-link path="/blog/versioned-builds-master" text="Versioned builds and building against master" >}}. +- **Wednesday.** {{< post-link path="/blog/videoio-audio-mixer-whisper" text="VideoIO, the PCM mixer and timed Whisper captions" >}}. PRs [#5315](https://github.com/codenameone/CodenameOne/pull/5315), [#5317](https://github.com/codenameone/CodenameOne/pull/5317), and [#5319](https://github.com/codenameone/CodenameOne/pull/5319). +- **Thursday.** {{< post-link path="/blog/game-builder-3d-dungeon" text="Game Builder Tutorial 3: a first-person 3D dungeon" >}}. The final part of the Game Builder tutorial series. + +## Wrapping Up + +The release is large because the app surface is larger now. It reaches cars, foldables, desktops, sensors, stylus devices, media pipelines, cloud validation, and older build timelines. + +The business side needs the same discipline as the API side. Cloud validation should help, not hold purchases hostage. Build tiers should buy capacity and support windows, not a license to keep your own revenue. Optional should mean optional, especially when the project stays open source. + +--- + +## Discussion + +_Join the conversation via GitHub Discussions._ + +{{< giscus >}} diff --git a/docs/website/content/blog/versioned-builds-master.md b/docs/website/content/blog/versioned-builds-master.md new file mode 100644 index 00000000000..50a34ab2740 --- /dev/null +++ b/docs/website/content/blog/versioned-builds-master.md @@ -0,0 +1,105 @@ +--- +title: "Versioned Builds Are Back, With Master Builds For Fast Verification" +slug: versioned-builds-master +url: /blog/versioned-builds-master/ +date: '2026-07-07' +author: Shai Almog +description: "Versioned builds return with a Maven-era model: pin a cloud build to a released Codename One version, or build against master to verify the current development head without waiting for a nightly package." +feed_html: 'Versioned Builds Are Back Versioned builds return with a Maven-era model: pin a cloud build to a release, or build against master to verify the current development head.' +series: ["release-2026-07-03"] +--- + +![Versioned Builds Are Back, With Master Builds For Fast Verification](/blog/versioned-builds-master.jpg) + +Versioned builds are back. + +The old versioned-build story came from the Ant era. Codename One had point releases every few months, and versioning meant "build against that point release." It made sense at the time, but the platform and the build infrastructure moved on. Maven Central, faster releases, more build targets, and a much larger code surface made the old model hard to maintain. + +The new model keeps the part developers actually need: a way to stabilize and diagnose builds without being forced onto the current server-side framework every time. + +## Pin A Released Version + +By default, a cloud build uses the current Codename One release. With a versioned build, you can pin to a specific published version: + +```properties +build.cn1Version=7.0.182 +``` + +The build server fetches that version's framework artifacts and builds against them. If you also want the simulator and local compile classpath to match, set the matching `cn1.version` in your Maven project. + +This is useful in three situations: + +- You are stabilizing a production release and want fewer moving parts. +- A build started failing and you need to know whether the regression is in your app or in the framework/build server. +- You are supporting an older customer app and need to reproduce the environment it last built with. + +## Build Against Master + +The more interesting part is `master`: + +```properties +build.cn1Version=master +``` + +This builds against the current development head of Codename One. Community developers recently asked for nightly or daily builds, but pushing nightly artifacts through Maven Central and support channels is messier than it sounds. A fixed nightly is also stale the moment the next fix lands. + +Building against `master` is a faster developer loop for most verification cases. If a fix landed this morning and you need to verify it on a device, you do not wait for tomorrow's nightly. You ask the cloud build to use the current development head. + +{{< mermaid >}} +flowchart TD + A["A fix lands on master"] --> B["Framework artifacts published"] + B --> C["Your build sets build.cn1Version=master"] + C --> D["Cloud build uses current development head"] + D --> E["Verify on real target"] + E --> F["Switch back to a release for production"] +{{< /mermaid >}} + +Use this for verification, not for shipping production builds. `master` is active development. It gives you speed, not a stability promise. + +## Why It Is Tiered + +The small overhead of fetching versioned artifacts is noticeable, but that is not the real reason this is limited by account level. + +The hard cost is support. + +If a free user reports a build issue from six months ago, and another user reports one from yesterday, and an enterprise customer needs a diagnosis against a two-month-old version, support becomes a bisection problem across a moving build system. The further back the community goes, the harder every issue is to understand, reproduce, and fix. + +That does not mean versioned builds should only exist at the top. The new approach opens them much further down than before, including limited access for basic/free usage. The longer windows still belong to higher tiers because those teams are the ones that need older reproducible builds and the support time that comes with them. + +The principle is simple: the tier buys a support window and capacity, not a right to keep your app revenue. + +## A Practical Workflow + +When a build starts failing after an update: + +```properties +# First, verify the last known good release. +build.cn1Version=7.0.182 +``` + +If that works, the regression is likely in the framework or build server. If it still fails, the issue is probably in your app, dependencies, native configuration, or a platform toolchain change that affects the old version too. + +When a fix lands: + +```properties +# Then verify the unreleased fix. +build.cn1Version=master +``` + +Once the fix is released, pin to the release you plan to ship with or remove the hint to follow the current default again. + +## What This Does Not Solve + +Versioned builds are not a time machine for every external dependency. Apple, Google, OS SDKs, certificates, signing rules, app store requirements, Maven repositories and native tools all move. A six-month-old framework can still be affected by a current Xcode or Android requirement. + +It also does not make `master` a nightly release channel. It is a verification channel. + +That distinction keeps the feature useful without turning support into archaeology for every build ever made. + +--- + +## Discussion + +_Join the conversation via GitHub Discussions._ + +{{< giscus >}} diff --git a/docs/website/content/blog/videoio-audio-mixer-whisper.md b/docs/website/content/blog/videoio-audio-mixer-whisper.md new file mode 100644 index 00000000000..0f53614b50a --- /dev/null +++ b/docs/website/content/blog/videoio-audio-mixer-whisper.md @@ -0,0 +1,194 @@ +--- +title: "VideoIO, PCM Mixing And Timed Whisper Captions" +slug: videoio-audio-mixer-whisper +url: /blog/videoio-audio-mixer-whisper/ +date: '2026-07-08' +author: Shai Almog +description: "Codename One can now generate and inspect real video: encode app-rendered frames with audio, decode exact frames back out, mix PCM on a sample clock, and turn Whisper segment timestamps into subtitles." +feed_html: 'VideoIO, PCM Mixing And Timed Whisper Captions Codename One can now generate and inspect real video: encode app-rendered frames, decode exact frames, mix PCM, and turn Whisper timestamps into subtitles.' +series: ["release-2026-07-03"] +--- + +![VideoIO, PCM Mixing And Timed Whisper Captions](/blog/videoio-audio-mixer-whisper.jpg) + +This release turns media from "play a file" into "build a file." + +[PR #5315](https://github.com/codenameone/CodenameOne/pull/5315) adds `VideoIO`, a cross-platform video subsystem that can encode app-rendered frames and audio into a standard video file, then decode an existing clip back into exact RGBA frames and PCM audio. [PR #5317](https://github.com/codenameone/CodenameOne/pull/5317) adds `AudioMixer`, a sample-accurate PCM timeline. [PR #5319](https://github.com/codenameone/CodenameOne/pull/5319) adds timed Whisper transcription segments with SRT and VTT output. + +Together, these APIs let Codename One generate proper videos across supported app platforms. + +![Frame-accurate VideoIO decode output from the Codename One test app](/blog/videoio-audio-mixer-whisper/videoio-decoded-frames.png) + +## Encoding App-Rendered Frames + +`VideoIO` is gated like other optional platform APIs: + +```java +if (!VideoIO.isSupported()) { + return; +} +``` + +Then you build a writer. Frames are ordinary `Image` instances, so the pixels can come from your UI, a game renderer, an animation scrubber, a generated chart, or a custom drawing routine. + +```java +String out = FileSystemStorage.getInstance().getAppHomePath() + "/demo.mp4"; + +VideoWriter writer = new VideoWriterBuilder() + .path(out) + .container(VideoIO.CONTAINER_MP4) + .width(720) + .height(1280) + .frameRate(30) + .videoCodec(VideoIO.CODEC_H264) + .videoBitRate(4000000) + .hasAudio(true) + .audioCodec(VideoIO.CODEC_AAC) + .sampleRate(44100) + .audioChannels(2) + .build(); + +try { + for (int i = 0; i < 90; i++) { + Image frame = renderFrame(i); + writer.writeFrame(frame, i * 1000L / 30L); + } + + short[] pcm = renderAudioTrack(); + writer.writeAudio(pcm, 44100, 2, 0); +} finally { + writer.close(); +} +``` + +The exact codecs available are platform-dependent. `VideoIO.getAvailableEncoders()` and `getAvailableDecoders()` expose what the device actually supports, including hardware acceleration flags. + +## Frame-Accurate Decode + +The old `Media` API is a player. It can seek, but playback seek is usually key-frame based. That is not enough when you are generating thumbnails, verifying a rendered animation, extracting frames for processing, or round-tripping a generated video in a test. + +`VideoReader.frameAt(ms)` returns the exact frame at a timestamp: + +```java +VideoReader reader = VideoIO.getVideoIO().openReader(out); +try { + VideoFrame oneSecond = reader.frameAt(1000); + Image image = oneSecond.getImage(); +} finally { + reader.close(); +} +``` + +You can also resample a whole clip to a constant frame rate: + +```java +reader.readFrames(10, frame -> { + process(frame.getImage(), frame.getTimestampMillis()); + return true; +}); +``` + +And when the backend supports it, `readAudio()` returns the audio track as an `AudioBuffer`: + +```java +AudioBuffer audio = reader.readAudio(); +``` + +JavaScript currently supports frame decode and full encode, but decoded audio extraction is not wired there yet. TV, Watch and Car targets do not expose `VideoIO`. + +## PCM Mixing On One Clock + +`AudioMixer` is intentionally small. It combines interleaved float PCM tracks in the normalized `[-1, 1]` range on a single sample clock. + +```java +AudioBuffer music = new AudioBuffer(musicPcm.length); +music.copyFrom(44100, 2, musicPcm); + +AudioBuffer voice = new AudioBuffer(voicePcm.length); +voice.copyFrom(44100, 2, voicePcm); + +AudioMixer mixer = new AudioMixer(44100, 2); +mixer.addTrack(music, 0, 0.65f); +mixer.addTrack(voice, 1200, 1.0f); + +AudioBuffer mixed = mixer.mix(); +``` + +If you already know exact sample-frame offsets, use `addTrackAtFrame(...)` instead of millisecond offsets. The output clips to `[-1, 1]` and can be written through `WAVWriter` or passed into a `VideoWriter`. + +```java +float[] pcm = new float[mixed.getSize()]; +mixed.copyTo(pcm); + +WAVWriter wav = new WAVWriter(new File("mix.wav"), + mixed.getSampleRate(), + mixed.getNumChannels(), + 16); +try { + wav.write(pcm, 0, mixed.getSize()); +} finally { + wav.close(); +} +``` + +## Subtitles From Timed Whisper + +The Whisper cn1lib already returned text. The new transcription API returns timed segments: + +```java +String modelPath = FileSystemStorage.getInstance().getAppHomePath() + + "/ggml-base.en.bin"; +Transcriber t = WhisperRecognizer.transcriber(modelPath); +TranscriptionResult result = t.transcribe( + new TranscriptionRequest(audioPath).setLanguageTag("en-US")).get(); + +String srt = result.toSrt(); +String vtt = result.toVtt(); +``` + +That `get()` call is blocking, so run it off the EDT or use the returned `AsyncResource` callback. Those segment timestamps are the missing link between "we transcribed the video" and "the video has subtitles." A generated training clip, game replay, tutorial or support recording can now get caption files automatically. + +The Whisper PR also fixed native packaging gaps: Android now ships a JNI AAR with native slices, iOS exposes timed segments, JavaSE keeps a fallback segment, Linux and Windows native bridge modules are packaged, and JavaScript is explicitly unsupported. + +## Where MorphTransition Fits + +[PR #5314](https://github.com/codenameone/CodenameOne/pull/5314) is listed as a smaller enhancement in the release post, but it connects directly to media generation. `MorphTransition` is now scrubbable: + +```java +MorphTransition transition = MorphTransition.create(300) + .snapshotMode(true) + .opacity("card", 0, 255) + .scale("card", 0.8f, 1.0f) + .rotation("card", -0.2f, 0); + +transition.setProgress(0.5); +``` + +Scrubbing means a transition can be rendered deterministically at progress `0.0`, `0.1`, `0.2`, and onward. That is exactly what a video export loop needs. + +## Platform Backends + +The backends use native media stacks rather than one portable lowest-common-denominator codec: + +- JavaSE and the simulator use bundled ffmpeg/ffprobe. +- iOS and macOS use AVFoundation. +- Android uses MediaCodec, MediaMuxer, MediaExtractor and MediaMetadataRetriever. +- Windows uses Media Foundation. +- Linux uses GStreamer. +- JavaScript uses HTML5 video/canvas decode and WebCodecs encode. + +This gives each platform the codec set and acceleration it already has. App code still starts from the same `VideoIO` facade. + +## Wrapping Up + +Video export used to be something a Codename One app handed off to a backend or a native library. Now the app can render frames, mix PCM, encode a video, decode it back for validation, and generate timed captions. That opens up tutorials, game clips, social exports, support recordings, animated explainers and media QA workflows from the same Java codebase. + +No special revenue model is attached to that. The app generates the media. The app owns the output. + +--- + +## Discussion + +_Join the conversation via GitHub Discussions._ + +{{< giscus >}} diff --git a/docs/website/layouts/_default/single.html b/docs/website/layouts/_default/single.html index 0c1bb2d92e9..c2b807c5bdd 100644 --- a/docs/website/layouts/_default/single.html +++ b/docs/website/layouts/_default/single.html @@ -165,6 +165,7 @@

{{ .Title }}

{{- end }}