create-mithril-lynx 2.4.0 → 2.5.0
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 +12 -9
- package/package.json +1 -1
- package/src/index.js +37 -1
- package/templates/_shared/ts/src/rspeedy-env.d.ts +9 -0
- package/templates/android/app-scripts/android-prepare.mjs +45 -0
- package/templates/android/host/app/src/main/AndroidManifest.xml +1 -0
- package/templates/android/host/app/src/main/java/package-path/MainActivity.kt +27 -0
- package/templates/android/host/app/src/main/java/package-path/MithrilLynxNavigationModule.kt +28 -0
- package/templates/android/host/app/src/main/res/xml/config.xml +8 -0
- package/templates/android/host/app/src/main/res/xml/file_paths.xml +10 -0
- package/templates/android/host/app/src/main/res/xml/network_security_config.xml +9 -0
- package/templates/basic-activity/ts/src/app-bar.ts +3 -4
- package/templates/basic-activity/ts/src/background.ts +8 -0
- /package/templates/android/host/app/src/main/res/values/{themes.xml → styles.xml} +0 -0
package/README.md
CHANGED
|
@@ -124,8 +124,8 @@ The sibling `<name>-android/` project is a complete, no-Android-Studio Gradle CL
|
|
|
124
124
|
|
|
125
125
|
- the Gradle wrapper (`gradlew`, `gradlew.bat`, `gradle-wrapper.jar`), so nothing needs to be installed besides a JDK and the Android SDK;
|
|
126
126
|
- `local.properties` with `sdk.dir` auto-detected from `ANDROID_HOME`/`ANDROID_SDK_ROOT` (with a written warning if it can't be found);
|
|
127
|
-
- the Kotlin host: the `Application` (registers `AssetFontFaceLoader`), `MainActivity` (async `AssetTemplateProvider`, splash screen, `NoopGenericResourceFetcher` for the fast `data:` font path — [lynx#9431](https://github.com/lynx-family/lynx/issues/9431));
|
|
128
|
-
-
|
|
127
|
+
- the Kotlin host: the `Application` (registers `AssetFontFaceLoader`), `MainActivity` (async `AssetTemplateProvider`, splash screen, `NoopGenericResourceFetcher` for the fast `data:` font path — [lynx#9431](https://github.com/lynx-family/lynx/issues/9431)), and a predefined native bridge for `mithril-lynx/route`'s Android system Back support;
|
|
128
|
+
- a project-local `android/` folder with editable `AndroidManifest.xml`, `strings.xml`, `styles.xml`, `config.xml`, `network_security_config.xml`, and `file_paths.xml`, plus `bun run android:prepare` to copy them into the Gradle host;
|
|
129
129
|
- an opt-in release signing setup driven by a gitignored `keystore.properties`.
|
|
130
130
|
|
|
131
131
|
And in the JS project, a `scripts/android.mjs` bridge plus npm scripts:
|
|
@@ -135,10 +135,11 @@ npm run android # build + sync + installDebug + launch, printing Tota
|
|
|
135
135
|
npm run android:apk # build + sync + assembleDebug
|
|
136
136
|
npm run android:release # build + sync + assembleRelease (signed if keystore.properties exists)
|
|
137
137
|
npm run android:sync # build + sync bundle into app/src/main/assets/, no Gradle
|
|
138
|
+
bun run android:prepare # manually sync android/*.xml -> sibling Android host
|
|
138
139
|
npm run android:keystore # generate release.keystore + keystore.properties (KEYSTORE_PASSWORD=...)
|
|
139
140
|
```
|
|
140
141
|
|
|
141
|
-
|
|
142
|
+
The packaging path still wraps whatever bundle `rspeedy build` produces. Its one framework-aware convenience is the predefined, otherwise dormant system Back bridge used by `mithril-lynx/route`; the Basic Activity template connects it automatically.
|
|
142
143
|
|
|
143
144
|
## Templates
|
|
144
145
|
|
|
@@ -146,7 +147,7 @@ This part of the tool is framework-agnostic — it just wraps whatever bundle `r
|
|
|
146
147
|
|---|---|
|
|
147
148
|
| **Hello World** (recommended) | The Mithril.js analog of Lynx's official React hello-world ([`lynx-examples/examples/hello-world`](https://github.com/lynx-family/lynx-examples/tree/main/examples/hello-world)) — same layout, same "tap the logo" interaction, running through Mithril hyperscript instead of JSX. |
|
|
148
149
|
| **Blank** | A single centered line of text. Nothing else — the starting point when you don't want any of Hello World's styling/assets in your way. |
|
|
149
|
-
| **Basic Activity** | Two screens (Main → Details) wired with [`mithril-lynx/route`](https://github.com/carlos-sweb/mithril-lynx/blob/main/ROUTE.md) — an in-memory router (`m.route`, reimplemented for an environment with no URL bar). Includes a reusable app-bar
|
|
150
|
+
| **Basic Activity** | Two screens (Main → Details) wired with [`mithril-lynx/route`](https://github.com/carlos-sweb/mithril-lynx/blob/main/ROUTE.md) — an in-memory router (`m.route`, reimplemented for an environment with no URL bar). Includes a reusable app-bar back affordance and, with `--android`, predefined system Back/predictive Back integration through `route.listenBackButton()`. |
|
|
150
151
|
|
|
151
152
|
## Package structure
|
|
152
153
|
|
|
@@ -172,15 +173,17 @@ create-mithril-lynx/
|
|
|
172
173
|
ts/src/{app-bar.ts, background.ts, screens/{home,detail}.ts}
|
|
173
174
|
android/ only used with --android
|
|
174
175
|
host/ the Gradle project -> <name>-android/
|
|
175
|
-
(includes AssetFontFaceLoader
|
|
176
|
-
NoopGenericResourceFetcher for lynx#9431
|
|
177
|
-
|
|
178
|
-
|
|
176
|
+
(includes AssetFontFaceLoader,
|
|
177
|
+
NoopGenericResourceFetcher for lynx#9431, and the
|
|
178
|
+
MithrilLynxNavigationModule system-Back bridge)
|
|
179
|
+
app-scripts/{android,android-prepare}.mjs
|
|
180
|
+
bridges in <name>/scripts/ (syncs bundle/fonts;
|
|
181
|
+
prepares <name>/android/*.xml into the host)
|
|
179
182
|
```
|
|
180
183
|
|
|
181
184
|
`src/index.js` copies, in order, `templates/_shared/common` → `templates/_shared/ts` → `templates/<template>/common` → `templates/<template>/ts` into the target directory — later copies overwrite same-named files from earlier ones, which is exactly how **Basic Activity**'s own routing-based `src/background.ts` replaces `_shared/ts`'s generic single-view one (Hello World and Blank don't ship their own, so they keep the shared file). It then renames `gitignore` to `.gitignore` (npm doesn't publish dotfiles reliably otherwise) and replaces `{{PROJECT_NAME}}`/`{{MITHRIL_LYNX_VERSION}}` placeholders in `package.json` and `README.md`.
|
|
182
185
|
|
|
183
|
-
With `--android` it then copies `templates/android/host` into a sibling `<name>-android/` (renaming `package-path` to the real package directory and `App.kt` to the Application class) and substitutes `{{PACKAGE_NAME}}`, `{{APP_CLASS}}`, `{{APP_NAME}}`, `{{ANDROID_DIR}}` and `{{SDK_DIR}}` across the tree. Binary files (the Gradle wrapper jar, a `--with-font` `.ttf`) are never text-substituted. With `--with-font`, each font is copied into the JS project (`src/assets/fonts/`) *and* seeded into the Android host's `assets/fonts/`, wired up with a `FONTS` loop + `lynx.addFont()` in `src/background.ts` plus a generated `src/fonts.css` (`text { font-family }` for the first font, one class per font when there are several) imported from `src/style.css`. `scripts/android.mjs` keeps `assets/fonts/` in sync on every `npm run android` / `android:sync`.
|
|
186
|
+
With `--android` it then copies `templates/android/host` into a sibling `<name>-android/` (renaming `package-path` to the real package directory and `App.kt` to the Application class) and substitutes `{{PACKAGE_NAME}}`, `{{APP_CLASS}}`, `{{APP_NAME}}`, `{{ANDROID_DIR}}` and `{{SDK_DIR}}` across the tree. It also seeds the generated project's `android/` folder with `AndroidManifest.xml`, `strings.xml`, `styles.xml`, `config.xml`, `network_security_config.xml`, and `file_paths.xml`. `bun run build` (and the Android build commands that invoke it) automatically synchronizes those local copies to the Gradle host first; use `bun run android:prepare` (or `npm run android:prepare`) when you only want that synchronization. Binary files (the Gradle wrapper jar, a `--with-font` `.ttf`) are never text-substituted. With `--with-font`, each font is copied into the JS project (`src/assets/fonts/`) *and* seeded into the Android host's `assets/fonts/`, wired up with a `FONTS` loop + `lynx.addFont()` in `src/background.ts` plus a generated `src/fonts.css` (`text { font-family }` for the first font, one class per font when there are several) imported from `src/style.css`. `scripts/android.mjs` keeps `assets/fonts/` in sync on every `npm run android` / `android:sync`.
|
|
184
187
|
|
|
185
188
|
**Not carried over from v1 (yet)**: the JavaScript variant. The old tool generated either TypeScript or JavaScript for every template; this rewrite ships TypeScript only for now — doubling every template for a parallel JS copy wasn't part of this pass.
|
|
186
189
|
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -137,6 +137,26 @@ function appClassNameFor(rawName) {
|
|
|
137
137
|
return name === "MainActivity" ? `${name}App` : name;
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
+
/** Android files users edit from the JS project, before syncing them into the
|
|
141
|
+
* sibling Gradle host with `bun run android:prepare`. */
|
|
142
|
+
const ANDROID_DEVELOPMENT_FILES = [
|
|
143
|
+
["AndroidManifest.xml", ["AndroidManifest.xml"]],
|
|
144
|
+
["strings.xml", ["res", "values", "strings.xml"]],
|
|
145
|
+
["styles.xml", ["res", "values", "styles.xml"]],
|
|
146
|
+
["config.xml", ["res", "xml", "config.xml"]],
|
|
147
|
+
["network_security_config.xml", ["res", "xml", "network_security_config.xml"]],
|
|
148
|
+
["file_paths.xml", ["res", "xml", "file_paths.xml"]],
|
|
149
|
+
];
|
|
150
|
+
|
|
151
|
+
function seedAndroidDevelopmentFiles(targetDir, androidDir) {
|
|
152
|
+
const sourceRoot = path.join(androidDir, "app", "src", "main");
|
|
153
|
+
const destinationRoot = path.join(targetDir, "android");
|
|
154
|
+
fs.mkdirSync(destinationRoot, { recursive: true });
|
|
155
|
+
for (const [name, sourceParts] of ANDROID_DEVELOPMENT_FILES) {
|
|
156
|
+
fs.copyFileSync(path.join(sourceRoot, ...sourceParts), path.join(destinationRoot, name));
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
140
160
|
function fontFamilyFor(filePath) {
|
|
141
161
|
return path
|
|
142
162
|
.basename(filePath, path.extname(filePath))
|
|
@@ -641,10 +661,16 @@ function scaffoldAndroid({ targetDir, rawName, options }) {
|
|
|
641
661
|
}
|
|
642
662
|
}
|
|
643
663
|
|
|
644
|
-
// 5.
|
|
664
|
+
// 5. Give app developers editable Android XML in their project, instead of
|
|
665
|
+
// making them traverse into the sibling Gradle host. android:prepare copies
|
|
666
|
+
// these exact files back into their Android resource locations.
|
|
667
|
+
seedAndroidDevelopmentFiles(targetDir, androidDir);
|
|
668
|
+
|
|
669
|
+
// 6. The scripts that join the two halves, inside the JS project.
|
|
645
670
|
const scriptsDir = path.join(targetDir, "scripts");
|
|
646
671
|
fs.mkdirSync(scriptsDir, { recursive: true });
|
|
647
672
|
fs.copyFileSync(path.join(ANDROID_TEMPLATE_ROOT, "app-scripts", "android.mjs"), path.join(scriptsDir, "android.mjs"));
|
|
673
|
+
fs.copyFileSync(path.join(ANDROID_TEMPLATE_ROOT, "app-scripts", "android-prepare.mjs"), path.join(scriptsDir, "android-prepare.mjs"));
|
|
648
674
|
|
|
649
675
|
const androidRelDir = path.relative(targetDir, androidDir).split(path.sep).join("/");
|
|
650
676
|
replaceInFile(path.join(scriptsDir, "android.mjs"), [
|
|
@@ -654,6 +680,7 @@ function scaffoldAndroid({ targetDir, rawName, options }) {
|
|
|
654
680
|
// A name with quotes would break keytool's -dname=CN=...
|
|
655
681
|
["{{APP_NAME}}", appName.replace(/["\\]/g, "")],
|
|
656
682
|
]);
|
|
683
|
+
replaceInFile(path.join(scriptsDir, "android-prepare.mjs"), [["{{ANDROID_REL_DIR}}", androidRelDir]]);
|
|
657
684
|
|
|
658
685
|
return { androidDir, androidDirName, androidRelDir, androidId, appName, appClass, fonts, sdkDir };
|
|
659
686
|
}
|
|
@@ -662,12 +689,18 @@ function patchJsProject({ targetDir, android }) {
|
|
|
662
689
|
// package.json: the Android scripts.
|
|
663
690
|
const pkgPath = path.join(targetDir, "package.json");
|
|
664
691
|
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
692
|
+
const buildCommand = pkg.scripts.build;
|
|
665
693
|
pkg.scripts = {
|
|
666
694
|
...pkg.scripts,
|
|
695
|
+
// Keep project-owned Android XML authoritative for every build path:
|
|
696
|
+
// `bun run build`, `npm run build`, and android.mjs's package-manager
|
|
697
|
+
// invocation all execute this command before rspeedy starts.
|
|
698
|
+
build: "node scripts/android-prepare.mjs && " + buildCommand,
|
|
667
699
|
android: "node scripts/android.mjs",
|
|
668
700
|
"android:apk": "node scripts/android.mjs --apk",
|
|
669
701
|
"android:release": "node scripts/android.mjs --release",
|
|
670
702
|
"android:sync": "node scripts/android.mjs --sync-only",
|
|
703
|
+
"android:prepare": "node scripts/android-prepare.mjs",
|
|
671
704
|
"android:keystore": "node scripts/android.mjs --keystore",
|
|
672
705
|
};
|
|
673
706
|
fs.writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`);
|
|
@@ -701,6 +734,7 @@ function patchJsProject({ targetDir, android }) {
|
|
|
701
734
|
"npm run android # build the bundle, copy it to assets, install and launch on the device",
|
|
702
735
|
"npm run android:apk # build the debug APK only",
|
|
703
736
|
"npm run android:sync # bundle -> assets only, no Gradle",
|
|
737
|
+
"bun run android:prepare # android/*.xml -> sibling Android host",
|
|
704
738
|
"",
|
|
705
739
|
"# Signed release APK (generate the keystore once):",
|
|
706
740
|
"KEYSTORE_PASSWORD='...' npm run android:keystore",
|
|
@@ -709,6 +743,8 @@ function patchJsProject({ targetDir, android }) {
|
|
|
709
743
|
"",
|
|
710
744
|
`- Application ID: \`${android.androidId}\``,
|
|
711
745
|
`- Application class: \`${android.appClass}\` · Activity: \`MainActivity\``,
|
|
746
|
+
"- Android XML: edit `android/*.xml`; every `bun run build` synchronizes those files to the sibling host before bundling. Run `bun run android:prepare` when you only need the synchronization.",
|
|
747
|
+
"- Android system Back bridge: the host pre-registers `MithrilLynxNavigationModule`; connect a `mithril-lynx/route` app with `route.listenBackButton({ onCanGoBackChange: (value) => NativeModules.MithrilLynxNavigationModule?.setCanGoBack(value) })`. The Basic Activity template is already connected.",
|
|
712
748
|
...android.fonts.map(
|
|
713
749
|
(f) =>
|
|
714
750
|
`- Font \`${f.family}\` (\`src/assets/fonts/${f.file}\`): \`lynx.addFont()\` — DEV inlines a \`data:\` URI for Lynx Go; PROD uses \`asset:///fonts/${f.file}\` via \`AssetFontFaceLoader\` ([lynx#9431](https://github.com/lynx-family/lynx/issues/9431)).`,
|
|
@@ -1,3 +1,12 @@
|
|
|
1
1
|
/// <reference types="@lynx-js/rspeedy/client" />
|
|
2
2
|
/// <reference types="@lynx-js/types" />
|
|
3
3
|
/// <reference types="@lynx-js/type-element-api" />
|
|
4
|
+
|
|
5
|
+
declare module "@lynx-js/types" {
|
|
6
|
+
interface NativeModules {
|
|
7
|
+
/** Android host bridge used by mithril-lynx/route.listenBackButton(). */
|
|
8
|
+
MithrilLynxNavigationModule?: {
|
|
9
|
+
setCanGoBack(canGoBack: boolean): void;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Copies the Android resources kept beside the JS source into the sibling
|
|
4
|
+
* Gradle host. Edit <project>/android/*.xml, then run:
|
|
5
|
+
*
|
|
6
|
+
* bun run android:prepare
|
|
7
|
+
*/
|
|
8
|
+
import fs from "node:fs";
|
|
9
|
+
import path from "node:path";
|
|
10
|
+
import { fileURLToPath } from "node:url";
|
|
11
|
+
|
|
12
|
+
const projectRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
13
|
+
const sourceDir = path.join(projectRoot, "android");
|
|
14
|
+
const androidDir = path.resolve(projectRoot, "{{ANDROID_REL_DIR}}");
|
|
15
|
+
|
|
16
|
+
const files = [
|
|
17
|
+
["AndroidManifest.xml", ["app", "src", "main", "AndroidManifest.xml"]],
|
|
18
|
+
["strings.xml", ["app", "src", "main", "res", "values", "strings.xml"]],
|
|
19
|
+
["styles.xml", ["app", "src", "main", "res", "values", "styles.xml"]],
|
|
20
|
+
["config.xml", ["app", "src", "main", "res", "xml", "config.xml"]],
|
|
21
|
+
["network_security_config.xml", ["app", "src", "main", "res", "xml", "network_security_config.xml"]],
|
|
22
|
+
["file_paths.xml", ["app", "src", "main", "res", "xml", "file_paths.xml"]],
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
function fail(message) {
|
|
26
|
+
console.error(`\n ✖ ${message}\n`);
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (!fs.existsSync(path.join(androidDir, "settings.gradle.kts"))) {
|
|
31
|
+
fail(`Can't find the Android host at ${androidDir}.`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
for (const [name, destination] of files) {
|
|
35
|
+
const source = path.join(sourceDir, name);
|
|
36
|
+
if (!fs.existsSync(source)) {
|
|
37
|
+
fail(`Missing ${path.relative(projectRoot, source)}. Restore it before preparing the Android host.`);
|
|
38
|
+
}
|
|
39
|
+
const target = path.join(androidDir, ...destination);
|
|
40
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
41
|
+
fs.copyFileSync(source, target);
|
|
42
|
+
console.log(` → ${path.relative(projectRoot, target)}`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
console.log("\n ✔ Android resources prepared.\n");
|
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
package {{PACKAGE_NAME}}
|
|
2
2
|
|
|
3
3
|
import android.os.Bundle
|
|
4
|
+
import androidx.activity.OnBackPressedCallback
|
|
4
5
|
import androidx.appcompat.app.AppCompatActivity
|
|
5
6
|
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
|
7
|
+
import com.lynx.react.bridge.JavaOnlyArray
|
|
6
8
|
import com.lynx.tasm.LynxBooleanOption
|
|
7
9
|
import com.lynx.tasm.LynxViewBuilder
|
|
8
10
|
import com.lynx.tasm.ThreadStrategyForRendering
|
|
9
11
|
import com.lynx.xelement.XElementBehaviors
|
|
10
12
|
|
|
11
13
|
class MainActivity : AppCompatActivity() {
|
|
14
|
+
private var backCallback: OnBackPressedCallback? = null
|
|
15
|
+
|
|
12
16
|
override fun onCreate(savedInstanceState: Bundle?) {
|
|
13
17
|
// Must run BEFORE super.onCreate() — a Splash Screen API requirement.
|
|
14
18
|
installSplashScreen()
|
|
@@ -20,6 +24,9 @@ class MainActivity : AppCompatActivity() {
|
|
|
20
24
|
// keyboard. Remove this line (and the xelement dependencies) if you
|
|
21
25
|
// don't use them.
|
|
22
26
|
builder.addBehaviors(XElementBehaviors().create())
|
|
27
|
+
// Lets mithril-lynx/route tell Android when its in-memory history can
|
|
28
|
+
// handle Back. See MithrilLynxNavigationModule and the callback below.
|
|
29
|
+
builder.registerModule("MithrilLynxNavigationModule", MithrilLynxNavigationModule::class.java)
|
|
23
30
|
// lynx-family/lynx's own explorer/android registers a
|
|
24
31
|
// GenericResourceFetcher unconditionally (LynxViewShellActivity —
|
|
25
32
|
// "used inside LynxEngine for resource loading capabilities of
|
|
@@ -37,9 +44,29 @@ class MainActivity : AppCompatActivity() {
|
|
|
37
44
|
val lynxView = builder.build(this)
|
|
38
45
|
setContentView(lynxView)
|
|
39
46
|
|
|
47
|
+
// Disabled until route.listenBackButton() reports that JS has an
|
|
48
|
+
// in-app history entry. When disabled, Android's normal Back behavior
|
|
49
|
+
// remains in charge and closes the activity from the first screen.
|
|
50
|
+
val callback = object : OnBackPressedCallback(false) {
|
|
51
|
+
override fun handleOnBackPressed() {
|
|
52
|
+
lynxView.sendGlobalEvent("mithrilLynx:back", JavaOnlyArray())
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
onBackPressedDispatcher.addCallback(this, callback)
|
|
56
|
+
backCallback = callback
|
|
57
|
+
|
|
40
58
|
// The bundle lives in app/src/main/assets/main-thread.bundle, copied
|
|
41
59
|
// there by `npm run android` (scripts/android.mjs) from the JS
|
|
42
60
|
// project's dist/. The name has to match exactly.
|
|
43
61
|
lynxView.renderTemplateUrl("main-thread.bundle", "")
|
|
44
62
|
}
|
|
63
|
+
|
|
64
|
+
fun setCanGoBack(canGoBack: Boolean) {
|
|
65
|
+
backCallback?.isEnabled = canGoBack
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
override fun onDestroy() {
|
|
69
|
+
backCallback = null
|
|
70
|
+
super.onDestroy()
|
|
71
|
+
}
|
|
45
72
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
package {{PACKAGE_NAME}}
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.os.Handler
|
|
5
|
+
import android.os.Looper
|
|
6
|
+
import com.lynx.jsbridge.LynxMethod
|
|
7
|
+
import com.lynx.jsbridge.LynxModule
|
|
8
|
+
import com.lynx.tasm.behavior.LynxContext
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* JS -> Android half of mithril-lynx/route's system Back integration.
|
|
12
|
+
*
|
|
13
|
+
* MainActivity supplies the Android -> JS half with sendGlobalEvent(). Keeping
|
|
14
|
+
* the callback disabled when the route history is empty preserves Android's
|
|
15
|
+
* default behavior on the app's first screen, including predictive Back.
|
|
16
|
+
*/
|
|
17
|
+
class MithrilLynxNavigationModule(context: Context) : LynxModule(context) {
|
|
18
|
+
private val activity = (context as? LynxContext)?.activity as? MainActivity
|
|
19
|
+
|
|
20
|
+
@LynxMethod
|
|
21
|
+
fun setCanGoBack(canGoBack: Boolean) {
|
|
22
|
+
// Lynx invokes native modules from its JS thread; Android's callback
|
|
23
|
+
// belongs to the main thread.
|
|
24
|
+
Handler(Looper.getMainLooper()).post {
|
|
25
|
+
activity?.setCanGoBack(canGoBack)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<!--
|
|
3
|
+
Project-owned Android configuration entry point. Android does not consume a
|
|
4
|
+
generic config.xml by itself; use it for XML metadata that a manifest entry
|
|
5
|
+
or a native integration needs, keeping that configuration beside the other
|
|
6
|
+
editable Android files in <project>/android/.
|
|
7
|
+
-->
|
|
8
|
+
<config />
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<!--
|
|
3
|
+
Ready for an androidx.core.content.FileProvider declaration in
|
|
4
|
+
AndroidManifest.xml when the app needs to share a file with another app.
|
|
5
|
+
-->
|
|
6
|
+
<paths>
|
|
7
|
+
<files-path name="files" path="." />
|
|
8
|
+
<cache-path name="cache" path="." />
|
|
9
|
+
<external-files-path name="external_files" path="." />
|
|
10
|
+
</paths>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<!--
|
|
3
|
+
The generated manifest references this resource. It deliberately matches the
|
|
4
|
+
previous host default (cleartext allowed) so existing local HTTP workflows do
|
|
5
|
+
not change. Restrict this before a production release when possible.
|
|
6
|
+
-->
|
|
7
|
+
<network-security-config>
|
|
8
|
+
<base-config cleartextTrafficPermitted="true" />
|
|
9
|
+
</network-security-config>
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import m from "mithril-runtime";
|
|
2
2
|
|
|
3
3
|
// A reusable app bar: a title, plus a back affordance when `onBack` is
|
|
4
|
-
// given. No m.route.Link involved for
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
// route.back() explicitly.
|
|
4
|
+
// given. No m.route.Link involved for this on-screen affordance: it calls
|
|
5
|
+
// route.back() explicitly. The generated Android host's system Back bridge
|
|
6
|
+
// reaches the same history separately through route.listenBackButton().
|
|
8
7
|
export function AppBar(title: string, onBack?: () => void) {
|
|
9
8
|
return m("view", { class: "AppBar" }, [
|
|
10
9
|
onBack != null ? m("text", { class: "AppBar-back", ontap: onBack }, "←") : null,
|
|
@@ -21,6 +21,14 @@ route("/", {
|
|
|
21
21
|
"/detail": DetailHost,
|
|
22
22
|
});
|
|
23
23
|
|
|
24
|
+
// The host generated by --android forwards the system Back button through a
|
|
25
|
+
// Lynx global event. The module is optional so this template still runs in
|
|
26
|
+
// LynxExplorer, or when generated without its native Android host.
|
|
27
|
+
route.listenBackButton({
|
|
28
|
+
onCanGoBackChange: (canGoBack) =>
|
|
29
|
+
NativeModules.MithrilLynxNavigationModule?.setCanGoBack(canGoBack),
|
|
30
|
+
});
|
|
31
|
+
|
|
24
32
|
declare const module: {
|
|
25
33
|
hot?: {
|
|
26
34
|
accept(path: string, callback: () => void): void;
|
|
File without changes
|