craft-native 0.0.55 → 0.0.74
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/dist/android/src/index.d.ts +73 -0
- package/dist/android/src/index.js +177 -37
- package/dist/android/templates/AndroidManifest.xml.template +10 -36
- package/dist/android/templates/CraftBridge.kt.template +192 -60
- package/dist/android/templates/CraftHealthConnect.kt.template +200 -0
- package/dist/android/templates/CraftHealthConnectStub.kt.template +28 -0
- package/dist/android/templates/LocationRecordingService.kt.template +160 -0
- package/dist/android/templates/MainActivity.kt.template +140 -19
- package/dist/android/templates/build.gradle.kts.app.template +23 -1
- package/dist/android/templates/build.gradle.kts.project.template +3 -2
- package/dist/android/templates/proguard-rules.pro.template +9 -0
- package/dist/api/android-advanced.d.ts +5 -6
- package/dist/api/app.d.ts +0 -1
- package/dist/api/clipboard.d.ts +0 -1
- package/dist/api/crypto.d.ts +1 -2
- package/dist/api/db.d.ts +1 -2
- package/dist/api/dialog.d.ts +0 -1
- package/dist/api/fs.d.ts +1 -2
- package/dist/api/gestures.d.ts +66 -0
- package/dist/api/http.d.ts +1 -2
- package/dist/api/index.d.ts +37 -34
- package/dist/api/ios-advanced.d.ts +6 -7
- package/dist/api/macos-advanced.d.ts +2 -3
- package/dist/api/media.d.ts +0 -1
- package/dist/api/mobile.d.ts +109 -1
- package/dist/api/process.d.ts +0 -1
- package/dist/api/sidebar.d.ts +3 -20
- package/dist/api/spaces.d.ts +83 -0
- package/dist/api/tray.d.ts +2 -3
- package/dist/api/window.d.ts +1 -2
- package/dist/api/windows-advanced.d.ts +6 -7
- package/dist/assets/index.d.ts +0 -1
- package/dist/binary-resolver.d.ts +23 -1
- package/dist/bridge/android.d.ts +0 -1
- package/dist/bridge/core.d.ts +0 -38
- package/dist/bridge/ids.d.ts +0 -1
- package/dist/bridge/ios.d.ts +0 -1
- package/dist/bridge/webkit-pending.d.ts +0 -1
- package/dist/build/bundle-optimizer.d.ts +0 -1
- package/dist/cli.js +666 -90
- package/dist/components/index.d.ts +6 -7
- package/dist/components/native.d.ts +0 -1
- package/dist/components/sidebar.d.ts +2 -81
- package/dist/dev/devtools.d.ts +0 -1
- package/dist/dev/error-overlay.d.ts +0 -1
- package/dist/dev/hot-reload.d.ts +0 -1
- package/dist/index.cjs +113 -3
- package/dist/index.d.cts +16 -16
- package/dist/index.d.ts +16 -16
- package/dist/index.js +112 -4
- package/dist/ios/src/index.d.ts +172 -0
- package/dist/ios/src/index.js +440 -42
- package/dist/ios/templates/CraftActivityAttributes.swift +14 -0
- package/dist/ios/templates/CraftApp.swift +1001 -73
- package/dist/ios/templates/CraftLiveActivityWidget.swift.template +62 -0
- package/dist/ios/templates/CraftWatchApp.swift.template +155 -0
- package/dist/ios/templates/Info.plist.template +10 -9
- package/dist/ios/templates/WatchApp.Info.plist.template +26 -0
- package/dist/ios/templates/WidgetExtension.Info.plist +31 -0
- package/dist/ios/templates/project.yml.template +12 -1
- package/dist/logging/index.d.ts +0 -1
- package/dist/mobile.d.ts +8 -0
- package/dist/mobile.js +755 -0
- package/dist/optimizations/index.d.ts +3 -4
- package/dist/package.d.ts +11 -1
- package/dist/performance/runtime.d.ts +0 -1
- package/dist/performance/startup.d.ts +0 -1
- package/dist/plugins/index.d.ts +0 -1
- package/dist/security/index.d.ts +0 -1
- package/dist/sidebar-types.d.ts +102 -0
- package/dist/signing/index.d.ts +0 -1
- package/dist/styles/headwind.d.ts +0 -1
- package/dist/styles/index.d.ts +9 -10
- package/dist/styles/sidebar-templates.d.ts +0 -1
- package/dist/styles/sidebars.d.ts +0 -1
- package/dist/types.d.ts +131 -1
- package/dist/updater/index.d.ts +0 -1
- package/dist/utils/audio.d.ts +0 -1
- package/dist/utils/index.d.ts +4 -5
- package/dist/utils/native.d.ts +1 -2
- package/dist/utils/react.d.ts +0 -1
- package/dist/utils/storage.d.ts +0 -1
- package/dist/utils/svelte.d.ts +0 -1
- package/dist/utils/timer.d.ts +0 -1
- package/dist/utils/vue.d.ts +0 -1
- package/package.json +32 -4
- package/dist/android/src/index.js.map +0 -10
- package/dist/api/android-advanced.d.ts.map +0 -1
- package/dist/api/app.d.ts.map +0 -1
- package/dist/api/clipboard.d.ts.map +0 -1
- package/dist/api/crypto.d.ts.map +0 -1
- package/dist/api/db.d.ts.map +0 -1
- package/dist/api/dialog.d.ts.map +0 -1
- package/dist/api/fs.d.ts.map +0 -1
- package/dist/api/http.d.ts.map +0 -1
- package/dist/api/index.d.ts.map +0 -1
- package/dist/api/ios-advanced.d.ts.map +0 -1
- package/dist/api/macos-advanced.d.ts.map +0 -1
- package/dist/api/media.d.ts.map +0 -1
- package/dist/api/mobile.d.ts.map +0 -1
- package/dist/api/process.d.ts.map +0 -1
- package/dist/api/sidebar.d.ts.map +0 -1
- package/dist/api/tray.d.ts.map +0 -1
- package/dist/api/window.d.ts.map +0 -1
- package/dist/api/windows-advanced.d.ts.map +0 -1
- package/dist/assets/index.d.ts.map +0 -1
- package/dist/binary-resolver.d.ts.map +0 -1
- package/dist/bridge/android.d.ts.map +0 -1
- package/dist/bridge/core.d.ts.map +0 -1
- package/dist/bridge/ids.d.ts.map +0 -1
- package/dist/bridge/ios.d.ts.map +0 -1
- package/dist/bridge/webkit-pending.d.ts.map +0 -1
- package/dist/build/bundle-optimizer.d.ts.map +0 -1
- package/dist/components/index.d.ts.map +0 -1
- package/dist/components/native.d.ts.map +0 -1
- package/dist/components/sidebar.d.ts.map +0 -1
- package/dist/dev/devtools.d.ts.map +0 -1
- package/dist/dev/error-overlay.d.ts.map +0 -1
- package/dist/dev/hot-reload.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -45
- package/dist/ios/src/index.js.map +0 -10
- package/dist/logging/index.d.ts.map +0 -1
- package/dist/optimizations/index.d.ts.map +0 -1
- package/dist/package.d.ts.map +0 -1
- package/dist/package.test.d.ts +0 -2
- package/dist/package.test.d.ts.map +0 -1
- package/dist/performance/runtime.d.ts.map +0 -1
- package/dist/performance/startup.d.ts.map +0 -1
- package/dist/plugins/index.d.ts.map +0 -1
- package/dist/security/index.d.ts.map +0 -1
- package/dist/signing/index.d.ts.map +0 -1
- package/dist/styles/headwind.d.ts.map +0 -1
- package/dist/styles/index.d.ts.map +0 -1
- package/dist/styles/sidebar-templates.d.ts.map +0 -1
- package/dist/styles/sidebars.d.ts.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/updater/index.d.ts.map +0 -1
- package/dist/utils/audio.d.ts.map +0 -1
- package/dist/utils/index.d.ts.map +0 -1
- package/dist/utils/native.d.ts.map +0 -1
- package/dist/utils/react.d.ts.map +0 -1
- package/dist/utils/storage.d.ts.map +0 -1
- package/dist/utils/svelte.d.ts.map +0 -1
- package/dist/utils/timer.d.ts.map +0 -1
- package/dist/utils/vue.d.ts.map +0 -1
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Craft Android Builder
|
|
3
|
+
*
|
|
4
|
+
* Generates native Android apps from web content using WebView.
|
|
5
|
+
*/
|
|
6
|
+
export interface CraftAndroidConfig {
|
|
7
|
+
appName: string;
|
|
8
|
+
packageName: string;
|
|
9
|
+
version?: string;
|
|
10
|
+
versionCode?: number;
|
|
11
|
+
darkMode?: boolean;
|
|
12
|
+
backgroundColor?: string;
|
|
13
|
+
enableSpeechRecognition?: boolean;
|
|
14
|
+
enableHaptics?: boolean;
|
|
15
|
+
enableShare?: boolean;
|
|
16
|
+
enableCamera?: boolean;
|
|
17
|
+
enableBiometric?: boolean;
|
|
18
|
+
enablePushNotifications?: boolean;
|
|
19
|
+
enableSecureStorage?: boolean;
|
|
20
|
+
enableGeolocation?: boolean;
|
|
21
|
+
enableBackgroundLocation?: boolean;
|
|
22
|
+
enableHealthConnect?: boolean;
|
|
23
|
+
enableKeepAwake?: boolean;
|
|
24
|
+
enableDeepLinks?: boolean;
|
|
25
|
+
urlSchemes?: string[];
|
|
26
|
+
trustedOrigins?: string[];
|
|
27
|
+
appIconPath?: string;
|
|
28
|
+
googleServicesFile?: string;
|
|
29
|
+
devServerURL?: string;
|
|
30
|
+
hasBundledFallback?: boolean;
|
|
31
|
+
minSdk?: number;
|
|
32
|
+
compileSdk?: number;
|
|
33
|
+
targetSdk?: number;
|
|
34
|
+
}
|
|
35
|
+
export interface InitOptions {
|
|
36
|
+
name: string;
|
|
37
|
+
packageName?: string;
|
|
38
|
+
output: string;
|
|
39
|
+
config?: Partial<CraftAndroidConfig>;
|
|
40
|
+
}
|
|
41
|
+
export interface BuildOptions {
|
|
42
|
+
htmlPath?: string;
|
|
43
|
+
devServer?: string;
|
|
44
|
+
output: string;
|
|
45
|
+
release?: boolean;
|
|
46
|
+
compile?: boolean;
|
|
47
|
+
}
|
|
48
|
+
export interface OpenOptions {
|
|
49
|
+
output: string;
|
|
50
|
+
}
|
|
51
|
+
export interface RunOptions {
|
|
52
|
+
device?: string;
|
|
53
|
+
output: string;
|
|
54
|
+
}
|
|
55
|
+
export declare function syncAndroidWebAssets(source: string, output: string): void;
|
|
56
|
+
export declare function renderAndroidPermissions(config: CraftAndroidConfig): string;
|
|
57
|
+
export declare function renderAndroidDeepLinks(config: CraftAndroidConfig): string;
|
|
58
|
+
/**
|
|
59
|
+
* Initialize a new Android project
|
|
60
|
+
*/
|
|
61
|
+
export declare function init(options: InitOptions): Promise<void>;
|
|
62
|
+
/**
|
|
63
|
+
* Build Android project
|
|
64
|
+
*/
|
|
65
|
+
export declare function build(options: BuildOptions): Promise<void>;
|
|
66
|
+
/**
|
|
67
|
+
* Open Android project in Android Studio
|
|
68
|
+
*/
|
|
69
|
+
export declare function open(options: OpenOptions): Promise<void>;
|
|
70
|
+
/**
|
|
71
|
+
* Run on Android device or emulator
|
|
72
|
+
*/
|
|
73
|
+
export declare function run(options: RunOptions): Promise<void>;
|
|
@@ -1,9 +1,98 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
// ../android/src/index.ts
|
|
3
|
-
import {
|
|
4
|
-
import { join,
|
|
3
|
+
import { cpSync, existsSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from "fs";
|
|
4
|
+
import { dirname, join, resolve } from "path";
|
|
5
5
|
var {$ } = globalThis.Bun;
|
|
6
6
|
var TEMPLATES_DIR = join(dirname(import.meta.dir), "templates");
|
|
7
|
+
var DEFAULT_CONFIG = {
|
|
8
|
+
version: "1.0.0",
|
|
9
|
+
versionCode: 1,
|
|
10
|
+
darkMode: true,
|
|
11
|
+
backgroundColor: "#0b1712",
|
|
12
|
+
enableSpeechRecognition: false,
|
|
13
|
+
enableHaptics: false,
|
|
14
|
+
enableShare: false,
|
|
15
|
+
enableCamera: false,
|
|
16
|
+
enableBiometric: false,
|
|
17
|
+
enablePushNotifications: false,
|
|
18
|
+
enableSecureStorage: false,
|
|
19
|
+
enableGeolocation: false,
|
|
20
|
+
enableBackgroundLocation: false,
|
|
21
|
+
enableHealthConnect: false,
|
|
22
|
+
enableKeepAwake: false,
|
|
23
|
+
enableDeepLinks: false,
|
|
24
|
+
urlSchemes: [],
|
|
25
|
+
trustedOrigins: [],
|
|
26
|
+
minSdk: 26,
|
|
27
|
+
compileSdk: 36,
|
|
28
|
+
targetSdk: 35
|
|
29
|
+
};
|
|
30
|
+
function syncAndroidWebAssets(source, output) {
|
|
31
|
+
const sourcePath = resolve(source);
|
|
32
|
+
if (!existsSync(sourcePath))
|
|
33
|
+
throw new Error(`Web asset path not found: ${source}`);
|
|
34
|
+
const assetsDir = join(output, "app/src/main/assets");
|
|
35
|
+
const configPath = join(assetsDir, "craft.config.json");
|
|
36
|
+
const config = existsSync(configPath) ? readFileSync(configPath) : undefined;
|
|
37
|
+
rmSync(assetsDir, { recursive: true, force: true });
|
|
38
|
+
mkdirSync(assetsDir, { recursive: true });
|
|
39
|
+
if (statSync(sourcePath).isDirectory())
|
|
40
|
+
cpSync(sourcePath, assetsDir, { recursive: true });
|
|
41
|
+
else
|
|
42
|
+
cpSync(sourcePath, join(assetsDir, "index.html"));
|
|
43
|
+
if (!existsSync(join(assetsDir, "index.html")))
|
|
44
|
+
throw new Error(`Web asset directory must contain index.html: ${source}`);
|
|
45
|
+
if (config)
|
|
46
|
+
writeFileSync(configPath, config);
|
|
47
|
+
}
|
|
48
|
+
function renderAndroidPermissions(config) {
|
|
49
|
+
const permissions = new Set(["android.permission.INTERNET", "android.permission.ACCESS_NETWORK_STATE"]);
|
|
50
|
+
if (config.enableSpeechRecognition)
|
|
51
|
+
permissions.add("android.permission.RECORD_AUDIO");
|
|
52
|
+
if (config.enableCamera)
|
|
53
|
+
permissions.add("android.permission.CAMERA");
|
|
54
|
+
if (config.enableHaptics)
|
|
55
|
+
permissions.add("android.permission.VIBRATE");
|
|
56
|
+
if (config.enableBiometric)
|
|
57
|
+
permissions.add("android.permission.USE_BIOMETRIC");
|
|
58
|
+
if (config.enableGeolocation || config.enableBackgroundLocation) {
|
|
59
|
+
permissions.add("android.permission.ACCESS_FINE_LOCATION");
|
|
60
|
+
permissions.add("android.permission.ACCESS_COARSE_LOCATION");
|
|
61
|
+
}
|
|
62
|
+
if (config.enableBackgroundLocation) {
|
|
63
|
+
permissions.add("android.permission.ACCESS_BACKGROUND_LOCATION");
|
|
64
|
+
permissions.add("android.permission.FOREGROUND_SERVICE");
|
|
65
|
+
permissions.add("android.permission.FOREGROUND_SERVICE_LOCATION");
|
|
66
|
+
}
|
|
67
|
+
if (config.enablePushNotifications)
|
|
68
|
+
permissions.add("android.permission.POST_NOTIFICATIONS");
|
|
69
|
+
if (config.enableKeepAwake)
|
|
70
|
+
permissions.add("android.permission.WAKE_LOCK");
|
|
71
|
+
if (config.enableHealthConnect) {
|
|
72
|
+
permissions.add("android.permission.health.READ_STEPS");
|
|
73
|
+
permissions.add("android.permission.health.READ_HEART_RATE");
|
|
74
|
+
permissions.add("android.permission.health.READ_ACTIVE_CALORIES_BURNED");
|
|
75
|
+
permissions.add("android.permission.health.READ_DISTANCE");
|
|
76
|
+
permissions.add("android.permission.health.READ_EXERCISE");
|
|
77
|
+
permissions.add("android.permission.health.WRITE_EXERCISE");
|
|
78
|
+
permissions.add("android.permission.health.WRITE_ACTIVE_CALORIES_BURNED");
|
|
79
|
+
permissions.add("android.permission.health.WRITE_DISTANCE");
|
|
80
|
+
}
|
|
81
|
+
return [...permissions].map((permission) => ` <uses-permission android:name="${permission}" />`).join(`
|
|
82
|
+
`);
|
|
83
|
+
}
|
|
84
|
+
function renderAndroidDeepLinks(config) {
|
|
85
|
+
if (!config.enableDeepLinks)
|
|
86
|
+
return "";
|
|
87
|
+
const schemes = [...new Set(config.urlSchemes?.map((value) => value.trim()).filter((value) => /^[a-z][a-z0-9+.-]*$/i.test(value)) ?? [])];
|
|
88
|
+
return schemes.map((scheme) => ` <intent-filter>
|
|
89
|
+
<action android:name="android.intent.action.VIEW" />
|
|
90
|
+
<category android:name="android.intent.category.DEFAULT" />
|
|
91
|
+
<category android:name="android.intent.category.BROWSABLE" />
|
|
92
|
+
<data android:scheme="${scheme}" />
|
|
93
|
+
</intent-filter>`).join(`
|
|
94
|
+
`);
|
|
95
|
+
}
|
|
7
96
|
async function init(options) {
|
|
8
97
|
const { name, packageName, output } = options;
|
|
9
98
|
console.log(`
|
|
@@ -27,36 +116,73 @@ async function init(options) {
|
|
|
27
116
|
}
|
|
28
117
|
}
|
|
29
118
|
const config = {
|
|
119
|
+
...DEFAULT_CONFIG,
|
|
30
120
|
appName: name,
|
|
31
121
|
packageName: finalPackageName,
|
|
32
|
-
|
|
33
|
-
versionCode: 1,
|
|
34
|
-
darkMode: true,
|
|
35
|
-
backgroundColor: "#1a1a2e",
|
|
36
|
-
enableSpeechRecognition: true,
|
|
37
|
-
enableHaptics: true,
|
|
38
|
-
enableShare: true,
|
|
39
|
-
enableCamera: true,
|
|
40
|
-
enableBiometric: true,
|
|
41
|
-
enablePushNotifications: false,
|
|
42
|
-
minSdk: 24,
|
|
43
|
-
targetSdk: 34
|
|
122
|
+
...options.config
|
|
44
123
|
};
|
|
124
|
+
if (config.enableBackgroundLocation)
|
|
125
|
+
config.enableGeolocation = true;
|
|
45
126
|
writeFileSync(join(output, "craft.config.json"), JSON.stringify(config, null, 2));
|
|
127
|
+
writeFileSync(join(output, "app/src/main/assets/craft.config.json"), JSON.stringify(config, null, 2));
|
|
128
|
+
const hasGoogleServices = Boolean(config.googleServicesFile);
|
|
129
|
+
if (config.googleServicesFile) {
|
|
130
|
+
if (!existsSync(config.googleServicesFile))
|
|
131
|
+
throw new Error(`Google services file not found: ${config.googleServicesFile}`);
|
|
132
|
+
cpSync(config.googleServicesFile, join(output, "app/google-services.json"));
|
|
133
|
+
}
|
|
46
134
|
const mainActivityTemplate = readFileSync(join(TEMPLATES_DIR, "MainActivity.kt.template"), "utf-8");
|
|
47
135
|
const mainActivity = mainActivityTemplate.replace(/\{\{PACKAGE_NAME\}\}/g, finalPackageName).replace(/\{\{APP_NAME\}\}/g, name);
|
|
48
136
|
writeFileSync(join(output, "app/src/main/java", packagePath, "MainActivity.kt"), mainActivity);
|
|
49
137
|
const craftBridgeTemplate = readFileSync(join(TEMPLATES_DIR, "CraftBridge.kt.template"), "utf-8");
|
|
50
|
-
const craftBridge = craftBridgeTemplate.replace(/\{\{PACKAGE_NAME\}\}/g, finalPackageName)
|
|
138
|
+
const craftBridge = craftBridgeTemplate.replace(/\{\{PACKAGE_NAME\}\}/g, finalPackageName).replace(/\{\{ENABLE_SPEECH\}\}/g, String(Boolean(config.enableSpeechRecognition))).replace(/\{\{ENABLE_HAPTICS\}\}/g, String(Boolean(config.enableHaptics))).replace(/\{\{ENABLE_SHARE\}\}/g, String(Boolean(config.enableShare))).replace(/\{\{ENABLE_CAMERA\}\}/g, String(Boolean(config.enableCamera))).replace(/\{\{ENABLE_BIOMETRIC\}\}/g, String(Boolean(config.enableBiometric))).replace(/\{\{ENABLE_PUSH\}\}/g, String(Boolean(config.enablePushNotifications))).replace(/\{\{ENABLE_SECURE_STORAGE\}\}/g, String(Boolean(config.enableSecureStorage))).replace(/\{\{ENABLE_GEOLOCATION\}\}/g, String(Boolean(config.enableGeolocation))).replace(/\{\{ENABLE_BACKGROUND_LOCATION\}\}/g, String(Boolean(config.enableBackgroundLocation))).replace(/\{\{ENABLE_KEEP_AWAKE\}\}/g, String(Boolean(config.enableKeepAwake))).replace(/\{\{ENABLE_DEEP_LINKS\}\}/g, String(Boolean(config.enableDeepLinks))).replace(/\{\{ENABLE_HEALTH_CONNECT\}\}/g, String(Boolean(config.enableHealthConnect))).replace(/\{\{FIREBASE_IMPORT\}\}/g, config.enablePushNotifications ? "import com.google.firebase.messaging.FirebaseMessaging" : "").replace(/\{\{REGISTER_PUSH_IMPLEMENTATION\}\}/g, config.enablePushNotifications ? `activity.runOnUiThread {
|
|
139
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
|
|
140
|
+
&& ContextCompat.checkSelfPermission(activity, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
|
|
141
|
+
ActivityCompat.requestPermissions(activity, arrayOf(Manifest.permission.POST_NOTIFICATIONS), 4204)
|
|
142
|
+
}
|
|
143
|
+
try {
|
|
144
|
+
FirebaseMessaging.getInstance().token.addOnCompleteListener { task ->
|
|
145
|
+
val token = if (task.isSuccessful) task.result else null
|
|
146
|
+
val callback = if (token.isNullOrBlank()) "window._craftPushReject" else "window._craftPushResolve"
|
|
147
|
+
val payload = JSONObject.quote(token ?: task.exception?.message ?: "Firebase Cloud Messaging is not configured")
|
|
148
|
+
webView.evaluateJavascript("$callback && $callback($payload)", null)
|
|
149
|
+
}
|
|
150
|
+
} catch (error: Exception) {
|
|
151
|
+
val payload = JSONObject.quote(error.message ?: "Firebase Cloud Messaging is not configured")
|
|
152
|
+
webView.evaluateJavascript("window._craftPushReject && window._craftPushReject($payload)", null)
|
|
153
|
+
}
|
|
154
|
+
}` : `activity.runOnUiThread {
|
|
155
|
+
webView.evaluateJavascript(
|
|
156
|
+
"window._craftPushReject && window._craftPushReject('Push notifications are disabled')",
|
|
157
|
+
null
|
|
158
|
+
)
|
|
159
|
+
}`);
|
|
51
160
|
writeFileSync(join(output, "app/src/main/java", packagePath, "CraftBridge.kt"), craftBridge);
|
|
161
|
+
const healthTemplate = readFileSync(join(TEMPLATES_DIR, config.enableHealthConnect ? "CraftHealthConnect.kt.template" : "CraftHealthConnectStub.kt.template"), "utf-8");
|
|
162
|
+
writeFileSync(join(output, "app/src/main/java", packagePath, "CraftHealthConnect.kt"), healthTemplate.replace(/\{\{PACKAGE_NAME\}\}/g, finalPackageName));
|
|
163
|
+
if (config.enableBackgroundLocation) {
|
|
164
|
+
const serviceTemplate = readFileSync(join(TEMPLATES_DIR, "LocationRecordingService.kt.template"), "utf-8");
|
|
165
|
+
writeFileSync(join(output, "app/src/main/java", packagePath, "LocationRecordingService.kt"), serviceTemplate.replace(/\{\{PACKAGE_NAME\}\}/g, finalPackageName));
|
|
166
|
+
}
|
|
52
167
|
const manifestTemplate = readFileSync(join(TEMPLATES_DIR, "AndroidManifest.xml.template"), "utf-8");
|
|
53
|
-
const manifest = manifestTemplate.replace(/\{\{PACKAGE_NAME\}\}/g, finalPackageName).replace(/\{\{APP_NAME\}\}/g, name)
|
|
168
|
+
const manifest = manifestTemplate.replace(/\{\{PACKAGE_NAME\}\}/g, finalPackageName).replace(/\{\{APP_NAME\}\}/g, name).replace(/\{\{PERMISSIONS\}\}/g, renderAndroidPermissions(config)).replace(/\{\{USES_CLEARTEXT\}\}/g, config.devServerURL?.startsWith("http://") ? "true" : "false").replace(/\{\{DEEP_LINK_INTENT_FILTERS\}\}/g, renderAndroidDeepLinks(config)).replace(/\{\{BACKGROUND_SERVICE\}\}/g, config.enableBackgroundLocation ? ' <service android:name=".LocationRecordingService" android:exported="false" android:foregroundServiceType="location" android:stopWithTask="false" />' : "").replace(/\{\{HEALTH_CONNECT_QUERIES\}\}/g, config.enableHealthConnect ? ` <queries>
|
|
169
|
+
<package android:name="com.google.android.apps.healthdata" />
|
|
170
|
+
</queries>` : "").replace(/\{\{HEALTH_CONNECT_RATIONALE\}\}/g, config.enableHealthConnect ? ` <intent-filter>
|
|
171
|
+
<action android:name="androidx.health.ACTION_SHOW_PERMISSIONS_RATIONALE" />
|
|
172
|
+
</intent-filter>
|
|
173
|
+
<intent-filter>
|
|
174
|
+
<action android:name="android.intent.action.VIEW_PERMISSION_USAGE" />
|
|
175
|
+
<category android:name="android.intent.category.HEALTH_PERMISSIONS" />
|
|
176
|
+
</intent-filter>` : "");
|
|
54
177
|
writeFileSync(join(output, "app/src/main/AndroidManifest.xml"), manifest);
|
|
55
178
|
const projectGradleTemplate = readFileSync(join(TEMPLATES_DIR, "build.gradle.kts.project.template"), "utf-8");
|
|
56
|
-
writeFileSync(join(output, "build.gradle.kts"), projectGradleTemplate);
|
|
179
|
+
writeFileSync(join(output, "build.gradle.kts"), projectGradleTemplate.replace(/\{\{GOOGLE_SERVICES_PLUGIN\}\}/g, hasGoogleServices ? ' id("com.google.gms.google-services") version "4.4.2" apply false' : ""));
|
|
57
180
|
const appGradleTemplate = readFileSync(join(TEMPLATES_DIR, "build.gradle.kts.app.template"), "utf-8");
|
|
58
|
-
const appGradle = appGradleTemplate.replace(/\{\{PACKAGE_NAME\}\}/g, finalPackageName).replace(/\{\{VERSION_NAME\}\}/g, config.version || "1.0.0").replace(/\{\{VERSION_CODE\}\}/g, String(config.versionCode || 1)).replace(/\{\{MIN_SDK\}\}/g, String(config.minSdk || 24)).replace(/\{\{TARGET_SDK\}\}/g, String(config.targetSdk ||
|
|
181
|
+
const appGradle = appGradleTemplate.replace(/\{\{PACKAGE_NAME\}\}/g, finalPackageName).replace(/\{\{VERSION_NAME\}\}/g, config.version || "1.0.0").replace(/\{\{VERSION_CODE\}\}/g, String(config.versionCode || 1)).replace(/\{\{MIN_SDK\}\}/g, String(config.minSdk || 24)).replace(/\{\{COMPILE_SDK\}\}/g, String(Math.max(config.compileSdk || 36, config.enableHealthConnect ? 36 : 1))).replace(/\{\{TARGET_SDK\}\}/g, String(config.targetSdk || 35)).replace(/\{\{GOOGLE_SERVICES_PLUGIN\}\}/g, hasGoogleServices ? ' id("com.google.gms.google-services")' : "").replace(/\{\{FIREBASE_MESSAGING_DEPENDENCY\}\}/g, config.enablePushNotifications ? ' implementation("com.google.firebase:firebase-messaging:24.1.0")' : "").replace(/\{\{HEALTH_CONNECT_DEPENDENCIES\}\}/g, config.enableHealthConnect ? ` implementation("androidx.health.connect:connect-client:1.1.0")
|
|
182
|
+
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1")` : "");
|
|
59
183
|
writeFileSync(join(output, "app/build.gradle.kts"), appGradle);
|
|
184
|
+
const proguardTemplate = readFileSync(join(TEMPLATES_DIR, "proguard-rules.pro.template"), "utf-8");
|
|
185
|
+
writeFileSync(join(output, "app/proguard-rules.pro"), proguardTemplate.replace(/\{\{PACKAGE_NAME\}\}/g, finalPackageName));
|
|
60
186
|
const settingsTemplate = readFileSync(join(TEMPLATES_DIR, "settings.gradle.kts.template"), "utf-8");
|
|
61
187
|
const settings = settingsTemplate.replace(/\{\{APP_NAME\}\}/g, name);
|
|
62
188
|
writeFileSync(join(output, "settings.gradle.kts"), settings);
|
|
@@ -70,7 +196,7 @@ android.nonTransitiveRClass=true
|
|
|
70
196
|
`);
|
|
71
197
|
const gradleWrapperProps = `distributionBase=GRADLE_USER_HOME
|
|
72
198
|
distributionPath=wrapper/dists
|
|
73
|
-
distributionUrl=https\\://services.gradle.org/distributions/gradle-8.
|
|
199
|
+
distributionUrl=https\\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
|
|
74
200
|
networkTimeout=10000
|
|
75
201
|
validateDistributionUrl=true
|
|
76
202
|
zipStoreBase=GRADLE_USER_HOME
|
|
@@ -92,6 +218,23 @@ zipStorePath=wrapper/dists
|
|
|
92
218
|
</resources>
|
|
93
219
|
`;
|
|
94
220
|
writeFileSync(join(output, "app/src/main/res/values/colors.xml"), colorsXml);
|
|
221
|
+
const appIconXml = `<?xml version="1.0" encoding="utf-8"?>
|
|
222
|
+
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
223
|
+
android:width="108dp"
|
|
224
|
+
android:height="108dp"
|
|
225
|
+
android:viewportWidth="108"
|
|
226
|
+
android:viewportHeight="108">
|
|
227
|
+
<path android:fillColor="${config.backgroundColor}" android:pathData="M0,0h108v108h-108z" />
|
|
228
|
+
<path android:fillColor="#10B981" android:pathData="M18,78L42,42l12,18l12,-12l24,30z" />
|
|
229
|
+
</vector>
|
|
230
|
+
`;
|
|
231
|
+
if (config.appIconPath) {
|
|
232
|
+
if (!existsSync(config.appIconPath))
|
|
233
|
+
throw new Error(`App icon not found: ${config.appIconPath}`);
|
|
234
|
+
cpSync(config.appIconPath, join(output, "app/src/main/res/drawable/craft_app_icon.png"));
|
|
235
|
+
} else {
|
|
236
|
+
writeFileSync(join(output, "app/src/main/res/drawable/craft_app_icon.xml"), appIconXml);
|
|
237
|
+
}
|
|
95
238
|
const themesXml = `<?xml version="1.0" encoding="utf-8"?>
|
|
96
239
|
<resources>
|
|
97
240
|
<style name="Theme.CraftApp" parent="Theme.Material3.DayNight.NoActionBar">
|
|
@@ -165,7 +308,7 @@ zipStorePath=wrapper/dists
|
|
|
165
308
|
console.log("");
|
|
166
309
|
}
|
|
167
310
|
async function build(options) {
|
|
168
|
-
const { htmlPath, devServer, output, release } = options;
|
|
311
|
+
const { htmlPath, devServer, output, release, compile = true } = options;
|
|
169
312
|
console.log(`
|
|
170
313
|
\uD83D\uDCE6 Building Craft Android project...`);
|
|
171
314
|
const configPath = join(output, "craft.config.json");
|
|
@@ -175,31 +318,27 @@ async function build(options) {
|
|
|
175
318
|
const config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
176
319
|
if (devServer) {
|
|
177
320
|
config.devServerURL = devServer;
|
|
321
|
+
config.trustedOrigins = [...new Set([...config.trustedOrigins ?? [], new URL(devServer).origin])];
|
|
178
322
|
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
323
|
+
writeFileSync(join(output, "app/src/main/assets/craft.config.json"), JSON.stringify(config, null, 2));
|
|
179
324
|
console.log(` Dev server: ${devServer}`);
|
|
180
325
|
}
|
|
181
326
|
if (htmlPath) {
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
const html = readFileSync(htmlPath, "utf-8");
|
|
188
|
-
writeFileSync(join(assetsDir, "index.html"), html);
|
|
189
|
-
console.log(` Copied: ${htmlPath} \u2192 assets/index.html`);
|
|
190
|
-
} else {
|
|
191
|
-
throw new Error(`HTML file not found or is a directory: ${htmlPath}`);
|
|
192
|
-
}
|
|
327
|
+
syncAndroidWebAssets(htmlPath, output);
|
|
328
|
+
config.hasBundledFallback = Boolean(devServer);
|
|
329
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
330
|
+
writeFileSync(join(output, "app/src/main/assets/craft.config.json"), JSON.stringify(config, null, 2));
|
|
331
|
+
console.log(` Synced: ${htmlPath} \u2192 assets/`);
|
|
193
332
|
}
|
|
333
|
+
if (!compile)
|
|
334
|
+
return;
|
|
194
335
|
const gradlewPath = join(output, "gradlew");
|
|
195
336
|
if (!existsSync(gradlewPath)) {
|
|
196
337
|
console.log(" Generating Gradle wrapper...");
|
|
197
338
|
try {
|
|
198
339
|
await $`cd ${output} && gradle wrapper`.quiet();
|
|
199
|
-
} catch {
|
|
200
|
-
|
|
201
|
-
console.log(" Download: https://developer.android.com/studio");
|
|
202
|
-
return;
|
|
340
|
+
} catch (error) {
|
|
341
|
+
throw new Error("Gradle is unavailable. Install Android Studio or Gradle before building Android.", { cause: error });
|
|
203
342
|
}
|
|
204
343
|
}
|
|
205
344
|
const buildType = release ? "assembleRelease" : "assembleDebug";
|
|
@@ -259,10 +398,11 @@ async function run(options) {
|
|
|
259
398
|
}
|
|
260
399
|
}
|
|
261
400
|
export {
|
|
401
|
+
syncAndroidWebAssets,
|
|
262
402
|
run,
|
|
403
|
+
renderAndroidPermissions,
|
|
404
|
+
renderAndroidDeepLinks,
|
|
263
405
|
open,
|
|
264
406
|
init,
|
|
265
407
|
build
|
|
266
408
|
};
|
|
267
|
-
|
|
268
|
-
//# debugId=5497ABB54764526564756E2164756E21
|
|
@@ -2,38 +2,8 @@
|
|
|
2
2
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
|
3
3
|
xmlns:tools="http://schemas.android.com/tools">
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
|
8
|
-
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
|
9
|
-
<uses-permission android:name="android.permission.CAMERA" />
|
|
10
|
-
<uses-permission android:name="android.permission.VIBRATE" />
|
|
11
|
-
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
|
|
12
|
-
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
|
|
13
|
-
android:maxSdkVersion="32" />
|
|
14
|
-
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
|
15
|
-
android:maxSdkVersion="28" />
|
|
16
|
-
|
|
17
|
-
<!-- Location permissions -->
|
|
18
|
-
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
|
19
|
-
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
|
20
|
-
|
|
21
|
-
<!-- Flashlight permission -->
|
|
22
|
-
<uses-permission android:name="android.permission.FLASHLIGHT" />
|
|
23
|
-
|
|
24
|
-
<!-- Contacts permissions -->
|
|
25
|
-
<uses-permission android:name="android.permission.READ_CONTACTS" />
|
|
26
|
-
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
|
|
27
|
-
|
|
28
|
-
<!-- Calendar permissions -->
|
|
29
|
-
<uses-permission android:name="android.permission.READ_CALENDAR" />
|
|
30
|
-
<uses-permission android:name="android.permission.WRITE_CALENDAR" />
|
|
31
|
-
|
|
32
|
-
<!-- Notifications permission (Android 13+) -->
|
|
33
|
-
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
|
34
|
-
|
|
35
|
-
<!-- Keep screen on -->
|
|
36
|
-
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
|
5
|
+
{{PERMISSIONS}}
|
|
6
|
+
{{HEALTH_CONNECT_QUERIES}}
|
|
37
7
|
|
|
38
8
|
<!-- Camera feature (optional) -->
|
|
39
9
|
<uses-feature android:name="android.hardware.camera" android:required="false" />
|
|
@@ -42,13 +12,13 @@
|
|
|
42
12
|
<uses-feature android:name="android.hardware.location.gps" android:required="false" />
|
|
43
13
|
|
|
44
14
|
<application
|
|
45
|
-
android:allowBackup="
|
|
46
|
-
android:icon="@
|
|
15
|
+
android:allowBackup="false"
|
|
16
|
+
android:icon="@drawable/craft_app_icon"
|
|
47
17
|
android:label="@string/app_name"
|
|
48
|
-
android:roundIcon="@
|
|
18
|
+
android:roundIcon="@drawable/craft_app_icon"
|
|
49
19
|
android:supportsRtl="true"
|
|
50
20
|
android:theme="@style/Theme.CraftApp"
|
|
51
|
-
android:usesCleartextTraffic="
|
|
21
|
+
android:usesCleartextTraffic="{{USES_CLEARTEXT}}"
|
|
52
22
|
android:hardwareAccelerated="true"
|
|
53
23
|
tools:targetApi="34">
|
|
54
24
|
|
|
@@ -62,8 +32,12 @@
|
|
|
62
32
|
<action android:name="android.intent.action.MAIN" />
|
|
63
33
|
<category android:name="android.intent.category.LAUNCHER" />
|
|
64
34
|
</intent-filter>
|
|
35
|
+
{{DEEP_LINK_INTENT_FILTERS}}
|
|
36
|
+
{{HEALTH_CONNECT_RATIONALE}}
|
|
65
37
|
</activity>
|
|
66
38
|
|
|
39
|
+
{{BACKGROUND_SERVICE}}
|
|
40
|
+
|
|
67
41
|
</application>
|
|
68
42
|
|
|
69
43
|
</manifest>
|