@tamer4lynx/cli 0.0.1
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/LICENSE +21 -0
- package/README.md +307 -0
- package/dist/android/autolink.js +272 -0
- package/dist/android/build.js +36 -0
- package/dist/android/bundle.js +99 -0
- package/dist/android/coreElements.js +129 -0
- package/dist/android/create.js +423 -0
- package/dist/android/getGradle.js +92 -0
- package/dist/android/postinstall-and.js +7 -0
- package/dist/android/postinstall.js +7 -0
- package/dist/android/syncDevClient.js +70 -0
- package/dist/common/buildDevApp.js +43 -0
- package/dist/common/codegen.js +69 -0
- package/dist/common/config.js +113 -0
- package/dist/common/create.js +170 -0
- package/dist/common/devServer.js +231 -0
- package/dist/common/hostConfig.js +256 -0
- package/dist/common/init.js +65 -0
- package/dist/common/postinstall.js +39 -0
- package/dist/common/start.js +5 -0
- package/dist/explorer/devLauncher.js +47 -0
- package/dist/explorer/patches.js +400 -0
- package/dist/explorer/ref.js +9 -0
- package/dist/index.js +6381 -0
- package/dist/ios/autolink.js +246 -0
- package/dist/ios/build.js +31 -0
- package/dist/ios/bundle.js +73 -0
- package/dist/ios/create.js +597 -0
- package/dist/ios/getPod.js +53 -0
- package/dist/ios/postinstall.js +7 -0
- package/package.json +92 -0
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
import { discoverNativeExtensions } from "../common/config";
|
|
2
|
+
import { fetchExplorerFile } from "./ref";
|
|
3
|
+
const EXPLORER_APP = "android/lynx_explorer/src/main/java/com/lynx/explorer/ExplorerApplication.java";
|
|
4
|
+
const EXPLORER_PROVIDER = "android/lynx_explorer/src/main/java/com/lynx/explorer/provider/DemoTemplateProvider.java";
|
|
5
|
+
export async function fetchAndPatchApplication(vars) {
|
|
6
|
+
const raw = await fetchExplorerFile(EXPLORER_APP);
|
|
7
|
+
let out = raw
|
|
8
|
+
.replace(/package com\.lynx\.explorer;/, `package ${vars.packageName};`)
|
|
9
|
+
.replace(/public class ExplorerApplication/, "public class App")
|
|
10
|
+
.replace(/LynxEnv\.inst\(\)\.init\(this, null, new DemoTemplateProvider\(\), null\);/, `LynxEnv.inst().init(this, null, new TemplateProvider(this), null);`)
|
|
11
|
+
.replace(/import com\.lynx\.explorer\.provider\.DemoTemplateProvider;/, "")
|
|
12
|
+
.replace(/import com\.lynx\.explorer\.modules\.LynxModuleAdapter;/, "")
|
|
13
|
+
.replace(/import com\.lynx\.explorer\.shell\.LynxRecorderDefaultActionCallback;/, "")
|
|
14
|
+
.replace(/import com\.lynx\.devtool\.recorder\.LynxRecorderPageManager;/, "")
|
|
15
|
+
.replace(/import com\.lynx\.service\.devtool\.LynxDevToolService;/, "")
|
|
16
|
+
.replace(/import com\.lynx\.tasm\.service\.ILynxHttpService;/, "")
|
|
17
|
+
.replace(/import com\.lynx\.tasm\.service\.ILynxImageService;/, "");
|
|
18
|
+
out = out.replace(/@Override\s+public void onCreate\(\)\s*\{[\s\S]*?initLynxRecorder\(\);\s*\}/, `@Override
|
|
19
|
+
public void onCreate() {
|
|
20
|
+
super.onCreate();
|
|
21
|
+
initLynxService();
|
|
22
|
+
initFresco();
|
|
23
|
+
initLynxEnv();
|
|
24
|
+
}`);
|
|
25
|
+
out = out.replace(/private void initLynxRecorder\(\)\s*{\s*LynxRecorderPageManager\.getInstance\(\)\.registerCallback\(new LynxRecorderDefaultActionCallback\(\)\);\s*}\s*/, "");
|
|
26
|
+
out = out.replace(/private void installLynxJSModule\(\)\s*{\s*LynxModuleAdapter\.getInstance\(\)\.Init\(this\);\s*}\s*/, "");
|
|
27
|
+
out = out.replace(/\n\s*\/\/ merge it into InitProcessor later\.\s*\n/, "\n");
|
|
28
|
+
out = out.replace(/LynxServiceCenter\.inst\(\)\.registerService\(LynxDevToolService\.getINSTANCE\(\)\);\s*\n\s*\/\/ enable all sessions debug[\s\S]*?LynxDevToolService\.getINSTANCE\(\)\.setLoadV8Bridge\(true\);\s*/, "");
|
|
29
|
+
out = out.replace(/import com\.lynx\.tasm\.service\.LynxServiceCenter;/, `import com.lynx.tasm.service.LynxServiceCenter;
|
|
30
|
+
import ${vars.packageName}.generated.GeneratedLynxExtensions;
|
|
31
|
+
`);
|
|
32
|
+
out = out.replace(/private void initLynxEnv\(\)\s*\{\s*LynxEnv\.inst\(\)\.init\(this, null, new TemplateProvider\(this\), null\);\s*}/, `private void initLynxEnv() {
|
|
33
|
+
GeneratedLynxExtensions.INSTANCE.register(this);
|
|
34
|
+
LynxEnv.inst().init(this, null, new TemplateProvider(this), null);
|
|
35
|
+
}`);
|
|
36
|
+
return out.replace(/\n{3,}/g, "\n\n");
|
|
37
|
+
}
|
|
38
|
+
function getLoadTemplateBody(vars) {
|
|
39
|
+
if (vars.devMode !== "embedded") {
|
|
40
|
+
return ` @Override
|
|
41
|
+
public void loadTemplate(String url, final Callback callback) {
|
|
42
|
+
new Thread(() -> {
|
|
43
|
+
try {
|
|
44
|
+
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
|
|
45
|
+
try (java.io.InputStream is = context.getAssets().open(url)) {
|
|
46
|
+
byte[] buf = new byte[1024];
|
|
47
|
+
int n;
|
|
48
|
+
while ((n = is.read(buf)) != -1) {
|
|
49
|
+
baos.write(buf, 0, n);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
callback.onSuccess(baos.toByteArray());
|
|
53
|
+
} catch (java.io.IOException e) {
|
|
54
|
+
callback.onFailed(e.getMessage());
|
|
55
|
+
}
|
|
56
|
+
}).start();
|
|
57
|
+
}`;
|
|
58
|
+
}
|
|
59
|
+
return ` private static final String DEV_CLIENT_BUNDLE = "dev-client.lynx.bundle";
|
|
60
|
+
|
|
61
|
+
@Override
|
|
62
|
+
public void loadTemplate(String url, final Callback callback) {
|
|
63
|
+
new Thread(() -> {
|
|
64
|
+
if (url != null && (url.equals(DEV_CLIENT_BUNDLE) || url.endsWith("/" + DEV_CLIENT_BUNDLE) || url.contains(DEV_CLIENT_BUNDLE))) {
|
|
65
|
+
try {
|
|
66
|
+
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
|
|
67
|
+
try (java.io.InputStream is = context.getAssets().open(DEV_CLIENT_BUNDLE)) {
|
|
68
|
+
byte[] buf = new byte[1024];
|
|
69
|
+
int n;
|
|
70
|
+
while ((n = is.read(buf)) != -1) baos.write(buf, 0, n);
|
|
71
|
+
}
|
|
72
|
+
callback.onSuccess(baos.toByteArray());
|
|
73
|
+
} catch (java.io.IOException e) {
|
|
74
|
+
callback.onFailed(e.getMessage());
|
|
75
|
+
}
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (BuildConfig.DEBUG) {
|
|
79
|
+
String devUrl = DevServerPrefs.INSTANCE.getUrl(context);
|
|
80
|
+
if (devUrl != null && !devUrl.isEmpty()) {
|
|
81
|
+
try {
|
|
82
|
+
java.net.URL u = new java.net.URL(devUrl);
|
|
83
|
+
String base = u.getProtocol() + "://" + u.getHost() + (u.getPort() > 0 ? ":" + u.getPort() : ":3000") + (u.getPath() != null && !u.getPath().isEmpty() ? u.getPath() : "");
|
|
84
|
+
String fetchUrl = base.endsWith("/") ? base + url : base + "/" + url;
|
|
85
|
+
okhttp3.OkHttpClient client = new okhttp3.OkHttpClient.Builder()
|
|
86
|
+
.connectTimeout(10, java.util.concurrent.TimeUnit.SECONDS)
|
|
87
|
+
.readTimeout(15, java.util.concurrent.TimeUnit.SECONDS)
|
|
88
|
+
.build();
|
|
89
|
+
okhttp3.Request request = new okhttp3.Request.Builder().url(fetchUrl).build();
|
|
90
|
+
try (okhttp3.Response response = client.newCall(request).execute()) {
|
|
91
|
+
if (response.isSuccessful() && response.body() != null) {
|
|
92
|
+
callback.onSuccess(response.body().bytes());
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
callback.onFailed("HTTP " + response.code() + " for " + fetchUrl);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
} catch (Exception e) {
|
|
99
|
+
callback.onFailed("Fetch failed: " + (e.getMessage() != null ? e.getMessage() : e.getClass().getSimpleName()));
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
try {
|
|
105
|
+
java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
|
|
106
|
+
try (java.io.InputStream is = context.getAssets().open(url)) {
|
|
107
|
+
byte[] buf = new byte[1024];
|
|
108
|
+
int n;
|
|
109
|
+
while ((n = is.read(buf)) != -1) {
|
|
110
|
+
baos.write(buf, 0, n);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
callback.onSuccess(baos.toByteArray());
|
|
114
|
+
} catch (java.io.IOException e) {
|
|
115
|
+
callback.onFailed(e.getMessage());
|
|
116
|
+
}
|
|
117
|
+
}).start();
|
|
118
|
+
}`;
|
|
119
|
+
}
|
|
120
|
+
export async function fetchAndPatchTemplateProvider(vars) {
|
|
121
|
+
const raw = await fetchExplorerFile(EXPLORER_PROVIDER);
|
|
122
|
+
const loadBody = getLoadTemplateBody(vars);
|
|
123
|
+
const out = raw
|
|
124
|
+
.replace(/package com\.lynx\.explorer\.provider;/, `package ${vars.packageName};`)
|
|
125
|
+
.replace(/public class DemoTemplateProvider/, "public class TemplateProvider")
|
|
126
|
+
.replace(/extends AbsTemplateProvider \{/, `extends AbsTemplateProvider {
|
|
127
|
+
private final android.content.Context context;
|
|
128
|
+
|
|
129
|
+
public TemplateProvider(android.content.Context context) {
|
|
130
|
+
this.context = context.getApplicationContext();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
`)
|
|
134
|
+
.replace(/@Override\s+public void loadTemplate\(String url, final Callback callback\)\s*\{[\s\S]*?\}\s*\)\s*;\s*\n\s*\}/, loadBody)
|
|
135
|
+
.replace(/import okhttp3\.ResponseBody;[\s\n]*/, "")
|
|
136
|
+
.replace(/import retrofit2\.Call;[\s\n]*/, "")
|
|
137
|
+
.replace(/import retrofit2\.Response;[\s\n]*/, "")
|
|
138
|
+
.replace(/import retrofit2\.Retrofit;[\s\n]*/, "")
|
|
139
|
+
.replace(/import java\.io\.IOException;[\s\n]*/, "");
|
|
140
|
+
const withBuildConfig = vars.devMode === "embedded"
|
|
141
|
+
? out.replace(/(package [^;]+;)/, `$1\nimport ${vars.packageName}.BuildConfig;\nimport ${vars.packageName}.DevServerPrefs;`)
|
|
142
|
+
: out;
|
|
143
|
+
return withBuildConfig.replace(/\n{3,}/g, "\n\n");
|
|
144
|
+
}
|
|
145
|
+
export function getDevClientManager(vars) {
|
|
146
|
+
if (vars.devMode !== "embedded")
|
|
147
|
+
return null;
|
|
148
|
+
return `package ${vars.packageName}
|
|
149
|
+
|
|
150
|
+
import android.content.Context
|
|
151
|
+
import android.net.Uri
|
|
152
|
+
import android.os.Handler
|
|
153
|
+
import android.os.Looper
|
|
154
|
+
import okhttp3.OkHttpClient
|
|
155
|
+
import okhttp3.Request
|
|
156
|
+
import okhttp3.Response
|
|
157
|
+
import okhttp3.WebSocket
|
|
158
|
+
import okhttp3.WebSocketListener
|
|
159
|
+
|
|
160
|
+
class DevClientManager(private val context: Context, private val onReload: Runnable) {
|
|
161
|
+
private var webSocket: WebSocket? = null
|
|
162
|
+
private val handler = Handler(Looper.getMainLooper())
|
|
163
|
+
private val client = OkHttpClient.Builder()
|
|
164
|
+
.connectTimeout(5, java.util.concurrent.TimeUnit.SECONDS)
|
|
165
|
+
.readTimeout(0, java.util.concurrent.TimeUnit.SECONDS)
|
|
166
|
+
.build()
|
|
167
|
+
|
|
168
|
+
fun connect() {
|
|
169
|
+
val devUrl = DevServerPrefs.getUrl(context) ?: return
|
|
170
|
+
val uri = Uri.parse(devUrl)
|
|
171
|
+
val scheme = if (uri.scheme == "https") "wss" else "ws"
|
|
172
|
+
val host = uri.host ?: return
|
|
173
|
+
val port = if (uri.port > 0) ":\${uri.port}" else ""
|
|
174
|
+
val path = (uri.path ?: "").let { p -> (if (p.endsWith("/")) p else p + "/") + "__hmr" }
|
|
175
|
+
val wsUrl = "$scheme://$host$port$path"
|
|
176
|
+
val request = Request.Builder()
|
|
177
|
+
.url(wsUrl)
|
|
178
|
+
.build()
|
|
179
|
+
webSocket = client.newWebSocket(request, object : WebSocketListener() {
|
|
180
|
+
override fun onMessage(webSocket: WebSocket, text: String) {
|
|
181
|
+
try {
|
|
182
|
+
if (text.contains("\\\"type\\\":\\\"reload\\\"")) {
|
|
183
|
+
handler.post(onReload)
|
|
184
|
+
}
|
|
185
|
+
} catch (_: Exception) { }
|
|
186
|
+
}
|
|
187
|
+
override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) { }
|
|
188
|
+
override fun onClosed(webSocket: WebSocket, code: Int, reason: String) { }
|
|
189
|
+
})
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
fun disconnect() {
|
|
193
|
+
webSocket?.close(1000, null)
|
|
194
|
+
webSocket = null
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
`;
|
|
198
|
+
}
|
|
199
|
+
export function getProjectActivity(vars) {
|
|
200
|
+
const hasDevClient = vars.devMode === "embedded";
|
|
201
|
+
const extensions = vars.projectRoot ? discoverNativeExtensions(vars.projectRoot) : [];
|
|
202
|
+
const hasTamerInsets = extensions.some((e) => e.packageName === "tamer-insets");
|
|
203
|
+
const devClientInit = hasDevClient
|
|
204
|
+
? `
|
|
205
|
+
devClientManager = DevClientManager(this) { lynxView?.renderTemplateUrl("main.lynx.bundle", "") }
|
|
206
|
+
devClientManager?.connect()
|
|
207
|
+
`
|
|
208
|
+
: "";
|
|
209
|
+
const devClientField = hasDevClient ? ` private var devClientManager: DevClientManager? = null
|
|
210
|
+
` : "";
|
|
211
|
+
const devClientCleanup = hasDevClient
|
|
212
|
+
? `
|
|
213
|
+
devClientManager?.disconnect()
|
|
214
|
+
`
|
|
215
|
+
: "";
|
|
216
|
+
const devClientImports = hasDevClient
|
|
217
|
+
? `
|
|
218
|
+
import ${vars.packageName}.DevClientManager`
|
|
219
|
+
: "";
|
|
220
|
+
const routerImport = `
|
|
221
|
+
import com.nanofuxion.tamerrouter.TamerRouterNativeModule`;
|
|
222
|
+
const insetsImport = hasTamerInsets ? `
|
|
223
|
+
import com.nanofuxion.tamerinsets.TamerInsetsModule` : "";
|
|
224
|
+
const insetsAttach = hasTamerInsets ? `
|
|
225
|
+
TamerInsetsModule.attachHostView(lynxView)` : "";
|
|
226
|
+
const insetsDetach = hasTamerInsets ? `
|
|
227
|
+
TamerInsetsModule.attachHostView(null)` : "";
|
|
228
|
+
const insetsListenerBlock = hasTamerInsets
|
|
229
|
+
? ""
|
|
230
|
+
: `
|
|
231
|
+
ViewCompat.setOnApplyWindowInsetsListener(lynxView!!) { view, insets ->
|
|
232
|
+
val imeVisible = insets.isVisible(WindowInsetsCompat.Type.ime())
|
|
233
|
+
val imeHeight = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom
|
|
234
|
+
view.updatePadding(bottom = if (imeVisible) imeHeight else 0)
|
|
235
|
+
insets
|
|
236
|
+
}`;
|
|
237
|
+
const insetsImports = hasTamerInsets
|
|
238
|
+
? ""
|
|
239
|
+
: `
|
|
240
|
+
import androidx.core.view.WindowInsetsCompat
|
|
241
|
+
import androidx.core.view.updatePadding
|
|
242
|
+
import androidx.core.view.ViewCompat`;
|
|
243
|
+
return `package ${vars.packageName}
|
|
244
|
+
|
|
245
|
+
import android.os.Bundle
|
|
246
|
+
import androidx.appcompat.app.AppCompatActivity
|
|
247
|
+
import androidx.core.view.WindowCompat
|
|
248
|
+
import androidx.core.view.WindowInsetsControllerCompat${insetsImports}
|
|
249
|
+
import com.lynx.tasm.LynxView
|
|
250
|
+
import com.lynx.tasm.LynxViewBuilder${devClientImports}${routerImport}${insetsImport}
|
|
251
|
+
|
|
252
|
+
class ProjectActivity : AppCompatActivity() {
|
|
253
|
+
private var lynxView: LynxView? = null
|
|
254
|
+
${devClientField}
|
|
255
|
+
override fun onCreate(savedInstanceState: Bundle?) {
|
|
256
|
+
super.onCreate(savedInstanceState)
|
|
257
|
+
WindowCompat.setDecorFitsSystemWindows(window, false)
|
|
258
|
+
WindowInsetsControllerCompat(window, window.decorView).isAppearanceLightStatusBars = true
|
|
259
|
+
lynxView = buildLynxView()
|
|
260
|
+
setContentView(lynxView)${insetsListenerBlock}
|
|
261
|
+
TamerRouterNativeModule.attachHostView(lynxView)${insetsAttach}
|
|
262
|
+
lynxView?.renderTemplateUrl("main.lynx.bundle", "")${devClientInit}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
@Deprecated("Deprecated in Java")
|
|
266
|
+
override fun onBackPressed() {
|
|
267
|
+
TamerRouterNativeModule.requestBack { consumed ->
|
|
268
|
+
if (!consumed) {
|
|
269
|
+
runOnUiThread { super.onBackPressed() }
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
override fun onDestroy() {
|
|
275
|
+
TamerRouterNativeModule.attachHostView(null)${insetsDetach}
|
|
276
|
+
lynxView?.destroy()
|
|
277
|
+
lynxView = null${devClientCleanup}
|
|
278
|
+
super.onDestroy()
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
private fun buildLynxView(): LynxView {
|
|
282
|
+
val viewBuilder = LynxViewBuilder()
|
|
283
|
+
viewBuilder.setTemplateProvider(TemplateProvider(this))
|
|
284
|
+
return viewBuilder.build(this)
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
`;
|
|
288
|
+
}
|
|
289
|
+
export function getStandaloneMainActivity(vars) {
|
|
290
|
+
const hasDevClient = vars.devMode === "embedded";
|
|
291
|
+
const devClientImports = hasDevClient
|
|
292
|
+
? `
|
|
293
|
+
import android.Manifest
|
|
294
|
+
import android.content.BroadcastReceiver
|
|
295
|
+
import android.content.Context
|
|
296
|
+
import android.content.Intent
|
|
297
|
+
import android.content.IntentFilter
|
|
298
|
+
import androidx.activity.result.contract.ActivityResultContracts
|
|
299
|
+
import com.google.zxing.integration.android.IntentIntegrator
|
|
300
|
+
import com.google.zxing.integration.android.IntentResult
|
|
301
|
+
import com.nanofuxion.tamerdevclient.DevClientModule
|
|
302
|
+
`
|
|
303
|
+
: "";
|
|
304
|
+
const devClientUriInit = hasDevClient ? "" : "";
|
|
305
|
+
const devClientInit = hasDevClient
|
|
306
|
+
? `
|
|
307
|
+
DevClientModule.attachHostActivity(this)
|
|
308
|
+
DevClientModule.attachLynxView(lynxView)
|
|
309
|
+
DevClientModule.attachCameraPermissionRequester { onGranted ->
|
|
310
|
+
pendingScanOnPermissionGranted = onGranted
|
|
311
|
+
cameraPermissionLauncher.launch(Manifest.permission.CAMERA)
|
|
312
|
+
}
|
|
313
|
+
DevClientModule.attachScanLauncher {
|
|
314
|
+
scanResultLauncher.launch(IntentIntegrator(this).setPrompt("Scan dev server QR").createScanIntent())
|
|
315
|
+
}
|
|
316
|
+
DevClientModule.attachReloadProjectLauncher {
|
|
317
|
+
startActivity(Intent(this@MainActivity, ProjectActivity::class.java).addFlags(
|
|
318
|
+
Intent.FLAG_ACTIVITY_NEW_DOCUMENT or Intent.FLAG_ACTIVITY_MULTIPLE_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
|
|
319
|
+
))
|
|
320
|
+
}
|
|
321
|
+
reloadReceiver = object : BroadcastReceiver() {
|
|
322
|
+
override fun onReceive(ctx: Context, intent: Intent) {
|
|
323
|
+
if (intent.action == DevClientModule.ACTION_RELOAD_PROJECT) {
|
|
324
|
+
runOnUiThread {
|
|
325
|
+
startActivity(Intent(this@MainActivity, ProjectActivity::class.java).addFlags(
|
|
326
|
+
Intent.FLAG_ACTIVITY_NEW_DOCUMENT or Intent.FLAG_ACTIVITY_MULTIPLE_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
|
|
327
|
+
))
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
333
|
+
registerReceiver(reloadReceiver, IntentFilter(DevClientModule.ACTION_RELOAD_PROJECT), Context.RECEIVER_NOT_EXPORTED)
|
|
334
|
+
} else {
|
|
335
|
+
registerReceiver(reloadReceiver, IntentFilter(DevClientModule.ACTION_RELOAD_PROJECT))
|
|
336
|
+
}
|
|
337
|
+
`
|
|
338
|
+
: "";
|
|
339
|
+
const devClientField = hasDevClient
|
|
340
|
+
? ` private var reloadReceiver: BroadcastReceiver? = null
|
|
341
|
+
private val currentUri = "dev-client.lynx.bundle"
|
|
342
|
+
private var pendingScanOnPermissionGranted: Runnable? = null
|
|
343
|
+
private val cameraPermissionLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted ->
|
|
344
|
+
if (granted) pendingScanOnPermissionGranted?.run()
|
|
345
|
+
pendingScanOnPermissionGranted = null
|
|
346
|
+
}
|
|
347
|
+
private val scanResultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
|
348
|
+
val scanResult = IntentIntegrator.parseActivityResult(result.resultCode, result.data)
|
|
349
|
+
scanResult?.contents?.let { DevClientModule.instance?.deliverScanResult(it) }
|
|
350
|
+
}
|
|
351
|
+
`
|
|
352
|
+
: "";
|
|
353
|
+
const devClientCleanup = hasDevClient
|
|
354
|
+
? `
|
|
355
|
+
override fun onDestroy() {
|
|
356
|
+
reloadReceiver?.let { unregisterReceiver(it) }
|
|
357
|
+
DevClientModule.attachReloadProjectLauncher(null)
|
|
358
|
+
DevClientModule.attachLynxView(null)
|
|
359
|
+
super.onDestroy()
|
|
360
|
+
}
|
|
361
|
+
`
|
|
362
|
+
: "";
|
|
363
|
+
return `package ${vars.packageName}
|
|
364
|
+
|
|
365
|
+
import android.os.Build
|
|
366
|
+
import android.os.Bundle
|
|
367
|
+
import androidx.appcompat.app.AppCompatActivity
|
|
368
|
+
import androidx.core.view.WindowCompat
|
|
369
|
+
import androidx.core.view.WindowInsetsCompat
|
|
370
|
+
import androidx.core.view.WindowInsetsControllerCompat
|
|
371
|
+
import androidx.core.view.updatePadding
|
|
372
|
+
import com.lynx.tasm.LynxView
|
|
373
|
+
import com.lynx.tasm.LynxViewBuilder${devClientImports}
|
|
374
|
+
|
|
375
|
+
class MainActivity : AppCompatActivity() {
|
|
376
|
+
${devClientField} private var lynxView: LynxView? = null
|
|
377
|
+
|
|
378
|
+
override fun onCreate(savedInstanceState: Bundle?) {
|
|
379
|
+
super.onCreate(savedInstanceState)
|
|
380
|
+
WindowCompat.setDecorFitsSystemWindows(window, false)
|
|
381
|
+
WindowInsetsControllerCompat(window, window.decorView).isAppearanceLightStatusBars = true
|
|
382
|
+
lynxView = buildLynxView()
|
|
383
|
+
setContentView(lynxView)
|
|
384
|
+
androidx.core.view.ViewCompat.setOnApplyWindowInsetsListener(lynxView!!) { view, insets ->
|
|
385
|
+
val imeVisible = insets.isVisible(WindowInsetsCompat.Type.ime())
|
|
386
|
+
val imeHeight = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom
|
|
387
|
+
view.updatePadding(bottom = if (imeVisible) imeHeight else 0)
|
|
388
|
+
insets
|
|
389
|
+
}
|
|
390
|
+
${devClientUriInit}lynxView?.renderTemplateUrl(${hasDevClient ? 'currentUri' : 'uri'}, "")${devClientInit}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
private fun buildLynxView(): LynxView {
|
|
394
|
+
val viewBuilder = LynxViewBuilder()
|
|
395
|
+
viewBuilder.setTemplateProvider(TemplateProvider(this))
|
|
396
|
+
return viewBuilder.build(this)
|
|
397
|
+
}${devClientCleanup}
|
|
398
|
+
}
|
|
399
|
+
`;
|
|
400
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export const LYNX_EXPLORER_REF = 'https://github.com/lynx-family/lynx/tree/develop/explorer';
|
|
2
|
+
export const LYNX_RAW_BASE = 'https://raw.githubusercontent.com/lynx-family/lynx/develop/explorer';
|
|
3
|
+
export async function fetchExplorerFile(relativePath) {
|
|
4
|
+
const url = `${LYNX_RAW_BASE}/${relativePath}`;
|
|
5
|
+
const res = await fetch(url);
|
|
6
|
+
if (!res.ok)
|
|
7
|
+
throw new Error(`Failed to fetch ${url}: ${res.status}`);
|
|
8
|
+
return res.text();
|
|
9
|
+
}
|