@craft-native/android 0.0.204 → 0.0.206
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -8
- package/dist/index.js +91 -1
- package/dist/index.js.map +3 -3
- package/package.json +1 -1
- package/templates/CraftNativeFlatList.kt.template +117 -0
- package/templates/CraftNativeRefreshableFlatList.kt.template +7 -0
- package/templates/CraftNativeShell.kt.template +42 -10
- package/templates/MainActivityNative.kt.template +628 -99
package/README.md
CHANGED
|
@@ -106,16 +106,21 @@ Native mode maps STX `View`, `SafeAreaView`, `Text`, `Button`, `Link`, `Icon`,
|
|
|
106
106
|
`Pressable`, `TouchableOpacity`, `TouchableHighlight`, `Checkbox`, `TextInput`, `Picker`, `DatePicker`, `Modal`, `Switch`, `Slider`, `ActivityIndicator`, `Image`, `ImageBackground`, `Video`, `Svg`, `G`, `SvgText`, `ScrollView`,
|
|
107
107
|
`Map`, `SectionList`, and `FlatList` to Android views. `Map` uses the Google
|
|
108
108
|
Maps SDK for trail polylines and marker taps; its overlays carry flat
|
|
109
|
-
latitude/longitude `geometry` arrays
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
109
|
+
latitude/longitude `geometry` arrays, `strokeWidth` is expressed in logical
|
|
110
|
+
points, and the camera supports controlled `region`, one-time `initialRegion`,
|
|
111
|
+
and `fitToBounds`. Set `mapsApiKey` in the Android config to emit the SDK manifest key;
|
|
112
|
+
Coordinates must be JSON numbers (numeric strings are not coerced), are
|
|
113
|
+
range-checked at the native boundary, and invalid points or pole-crossing
|
|
114
|
+
regions are ignored. `fitToBounds` waits for the map viewport to
|
|
115
|
+
be measured before caching a successful camera fit, so an early map callback
|
|
116
|
+
does not lose the initial route framing.
|
|
113
117
|
without it, the native map remains an explicit opt-in integration that needs
|
|
114
118
|
the app's own Maps credentials. `SectionList` uses the same
|
|
115
119
|
native scroll host for ordinary `.stx` slot children; grouped section data
|
|
116
120
|
should use `FlatList` until the section-data protocol is available. The
|
|
117
121
|
generated host routes both list names through `CraftNativeScrollView` for this
|
|
118
|
-
slot-based contract. `
|
|
122
|
+
slot-based contract. `ScrollView` also supports `stickyHeaderIndices` and
|
|
123
|
+
`scrollTarget` for vertical native content. `Picker` renders `<select>`/`<option>` values
|
|
119
124
|
with `onValueChange` or `onChange`. `DatePicker` uses the platform `DatePicker`
|
|
120
125
|
and `TimePicker` controls for date, time, datetime, and countdown modes and
|
|
121
126
|
emits local `value`, ISO, and millisecond `timestamp` fields. `Modal` is parent-scoped and supports
|
|
@@ -125,7 +130,10 @@ and `thumbColor`, while `Slider` supports track/thumb tint props and `step`
|
|
|
125
130
|
snapping without collapsing stepped ranges to a fixed pixel-like precision.
|
|
126
131
|
`ActivityIndicator` keeps a visible stopped indicator genuinely
|
|
127
132
|
non-animated when `animating={false}` and `hidesWhenStopped={false}`. `Image`
|
|
128
|
-
reports load lifecycle events and structured failures.
|
|
133
|
+
reports load lifecycle events and structured failures. Native image sources
|
|
134
|
+
accept HTTPS everywhere and loopback HTTP for local development (`localhost`,
|
|
135
|
+
`127.0.0.1`, `10.0.2.2`, `::1`, and `*.localhost`); public HTTP and unsupported
|
|
136
|
+
schemes are rejected. `Video` uses the native
|
|
129
137
|
`VideoView` for URI playback with autoplay, pause, looping, mute, controls, and
|
|
130
138
|
resize-mode props.
|
|
131
139
|
`ImageBackground` loads an image behind ordinary native children while
|
|
@@ -146,8 +154,9 @@ become checked on their first user toggle.
|
|
|
146
154
|
horizontal and inverted layouts, list chrome, and end-reached pagination.
|
|
147
155
|
Flex items support `flex`, `flexGrow`, `flexShrink`, and numeric or percentage
|
|
148
156
|
`flexBasis` for main-axis space distribution while preserving gaps and margins.
|
|
149
|
-
Shared transforms, z-order, view shadows, text shadows,
|
|
150
|
-
are applied with the same style keys as the iOS
|
|
157
|
+
Shared transforms, z-order, view shadows, text shadows, per-edge border
|
|
158
|
+
styles, and linear gradients are applied with the same style keys as the iOS
|
|
159
|
+
renderer. Android `elevation` remains an additional native shadow input.
|
|
151
160
|
`gap`, `rowGap`, and `columnGap` retain fractional density-independent values
|
|
152
161
|
through line distribution and round only the final native frames.
|
|
153
162
|
Numeric lengths and percentage widths/heights resolve against the native parent
|
|
@@ -234,11 +243,18 @@ With `nativeScreens` (paths to the screens that draw them), `tabs` and
|
|
|
234
243
|
`nativeApp` in craft.config.json, the same keys as the iOS builder, the
|
|
235
244
|
native renderer is a whole app rather than one bundle route:
|
|
236
245
|
|
|
246
|
+
`craft android init` and `craft android build` validate every tab and the
|
|
247
|
+
configured start path against `nativeScreens`; invalid native route tables are
|
|
248
|
+
rejected before a project is generated.
|
|
249
|
+
|
|
237
250
|
- a tab bar and a navigation bar (`craft.navigation.setOptions`: title, large
|
|
238
251
|
title, right buttons with badges, `tabBarHidden`), a stack per tab, Android's
|
|
239
252
|
Back key popping it; completed left/right swipes switch between tab roots
|
|
240
253
|
interactively, yield to a horizontal scroll view, and can be disabled per
|
|
241
254
|
screen with `craft.navigation.setOptions({ tabSwipeEnabled: false })`;
|
|
255
|
+
each screen may also set `backgroundColor`; omitting it or passing `null`
|
|
256
|
+
follows the app's light/dark background resource, and the navigation bar and
|
|
257
|
+
large title use the same resolved color;
|
|
242
258
|
- `craft.navigation.open(path)` matched in the iOS shell's order (fixed segments
|
|
243
259
|
before parameters), a tab's path selecting its tab, a path no screen draws
|
|
244
260
|
opening the site in a Custom Tab; deep links and tapped notifications route
|
package/dist/index.js
CHANGED
|
@@ -380,6 +380,94 @@ function validateAndroidConfig(config) {
|
|
|
380
380
|
throw new Error("Android targetSdk must not exceed compileSdk");
|
|
381
381
|
}
|
|
382
382
|
}
|
|
383
|
+
function normalizeNativeRoutePath(raw) {
|
|
384
|
+
if (typeof raw !== "string")
|
|
385
|
+
return null;
|
|
386
|
+
let path = raw;
|
|
387
|
+
const cut = path.search(/[?#]/);
|
|
388
|
+
if (cut >= 0)
|
|
389
|
+
path = path.slice(0, cut);
|
|
390
|
+
if (!path.startsWith("/"))
|
|
391
|
+
return null;
|
|
392
|
+
path = path.replace(/\/{2,}/g, "/");
|
|
393
|
+
if (path.length > 1 && path.endsWith("/"))
|
|
394
|
+
path = path.slice(0, -1);
|
|
395
|
+
return path;
|
|
396
|
+
}
|
|
397
|
+
function nativeRouteSegments(path) {
|
|
398
|
+
return path === "/" ? [] : path.slice(1).split("/");
|
|
399
|
+
}
|
|
400
|
+
function matchesNativeRoute(nativeScreens, raw) {
|
|
401
|
+
const path = normalizeNativeRoutePath(raw);
|
|
402
|
+
if (path === null)
|
|
403
|
+
return false;
|
|
404
|
+
const parts = nativeRouteSegments(path);
|
|
405
|
+
return Object.entries(nativeScreens ?? {}).some(([pattern, screen]) => {
|
|
406
|
+
if (!screen)
|
|
407
|
+
return false;
|
|
408
|
+
const normalized = normalizeNativeRoutePath(pattern);
|
|
409
|
+
if (normalized === null)
|
|
410
|
+
return false;
|
|
411
|
+
const route = nativeRouteSegments(normalized);
|
|
412
|
+
for (let index = 0;index < route.length; index++) {
|
|
413
|
+
const segment = route[index];
|
|
414
|
+
if (segment === "*")
|
|
415
|
+
return index === route.length - 1;
|
|
416
|
+
if (index >= parts.length)
|
|
417
|
+
return false;
|
|
418
|
+
if (segment.startsWith(":")) {
|
|
419
|
+
if (!parts[index])
|
|
420
|
+
return false;
|
|
421
|
+
} else if (segment !== parts[index])
|
|
422
|
+
return false;
|
|
423
|
+
}
|
|
424
|
+
return route.length === parts.length;
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
function nativeStartPath(config) {
|
|
428
|
+
if (!config.devServerURL)
|
|
429
|
+
return "/";
|
|
430
|
+
try {
|
|
431
|
+
const url = new URL(config.devServerURL);
|
|
432
|
+
return `${url.pathname || "/"}${url.search}`;
|
|
433
|
+
} catch {
|
|
434
|
+
return "/";
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
function validateNativeRouteConfig(config) {
|
|
438
|
+
const screens = Object.entries(config.nativeScreens ?? {});
|
|
439
|
+
for (const [pattern, screen] of screens) {
|
|
440
|
+
if (normalizeNativeRoutePath(pattern) !== pattern || /[?#]/.test(pattern))
|
|
441
|
+
throw new Error(`nativeScreens path "${pattern}" must be an absolute path with no query, hash or trailing slash.`);
|
|
442
|
+
if (nativeRouteSegments(normalizeNativeRoutePath(pattern) ?? "/").slice(0, -1).includes("*"))
|
|
443
|
+
throw new Error(`nativeScreens path "${pattern}" may only end in *.`);
|
|
444
|
+
if (typeof screen !== "string" || !/^[A-Za-z_$][\w$-]*$/.test(screen))
|
|
445
|
+
throw new Error(`nativeScreens screen "${String(screen)}" for "${pattern}" must be a screen name from the native bundle.`);
|
|
446
|
+
}
|
|
447
|
+
const ids = new Set;
|
|
448
|
+
for (const tab of config.tabs ?? []) {
|
|
449
|
+
if (!tab || typeof tab.id !== "string" || !tab.id || typeof tab.title !== "string" || !tab.title)
|
|
450
|
+
throw new Error("Each tab needs an id (its root path) and a title.");
|
|
451
|
+
if (ids.has(tab.id))
|
|
452
|
+
throw new Error(`Tab "${tab.id}" is listed twice.`);
|
|
453
|
+
ids.add(tab.id);
|
|
454
|
+
}
|
|
455
|
+
if (config.renderer === "native" && screens.length > 0) {
|
|
456
|
+
for (const id of ids) {
|
|
457
|
+
if (!matchesNativeRoute(config.nativeScreens, id))
|
|
458
|
+
throw new Error(`Tab "${id}" is not a nativeScreens path; with renderer: "native" every tab must be a native screen.`);
|
|
459
|
+
}
|
|
460
|
+
const start = nativeStartPath(config);
|
|
461
|
+
if (!matchesNativeRoute(config.nativeScreens, start))
|
|
462
|
+
throw new Error(`The start path "${start}" (from devServerURL) is not a nativeScreens path; with renderer: "native" the app must start on a native screen.`);
|
|
463
|
+
}
|
|
464
|
+
if (config.nativeApp !== undefined) {
|
|
465
|
+
if (typeof config.nativeApp !== "string" || !/^[A-Za-z_$][\w$-]*$/.test(config.nativeApp))
|
|
466
|
+
throw new Error(`nativeApp "${String(config.nativeApp)}" must be a route name from the native bundle.`);
|
|
467
|
+
if (config.renderer !== "native" && screens.length === 0)
|
|
468
|
+
throw new Error('nativeApp runs in the native bundle; it needs nativeScreens or renderer: "native".');
|
|
469
|
+
}
|
|
470
|
+
}
|
|
383
471
|
function writeAndroidConfig(output, config) {
|
|
384
472
|
writeFileSync(join2(output, "craft.config.json"), JSON.stringify(config, null, 2));
|
|
385
473
|
const runtimeConfig = { ...config };
|
|
@@ -601,6 +689,7 @@ async function init(options) {
|
|
|
601
689
|
}
|
|
602
690
|
normalizeAndroidNetworkConfig(config);
|
|
603
691
|
validateAndroidConfig(config);
|
|
692
|
+
validateNativeRouteConfig(config);
|
|
604
693
|
if (config.enablePushNotifications && !config.googleServicesFile) {
|
|
605
694
|
throw new Error("Android push notifications require a googleServicesFile");
|
|
606
695
|
}
|
|
@@ -861,6 +950,7 @@ async function build(options) {
|
|
|
861
950
|
}
|
|
862
951
|
const config = JSON.parse(readFileSync2(configPath, "utf-8"));
|
|
863
952
|
validateAndroidConfig(config);
|
|
953
|
+
validateNativeRouteConfig(config);
|
|
864
954
|
if (config.renderer === "native") {
|
|
865
955
|
if (htmlPath || devServer)
|
|
866
956
|
throw new Error("A native Android screen cannot use --html-path or --dev-server.");
|
|
@@ -990,4 +1080,4 @@ export {
|
|
|
990
1080
|
ANDROID_MIN_SDK
|
|
991
1081
|
};
|
|
992
1082
|
|
|
993
|
-
//# debugId=
|
|
1083
|
+
//# debugId=D95CCF804E0EBBCF64756E2164756E21
|