4 Commits

6 changed files with 130 additions and 31 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "vyla-go",
"version": "1.0.13",
"version": "1.0.15",
"description": "Vyla Go Mobile Launcher",
"main": "main.js",
"scripts": {

View File

@@ -1,36 +1,73 @@
package com.vyla.go
import android.graphics.Bitmap
import android.graphics.Color
import android.os.Build
import android.os.Bundle
import android.view.View
import android.view.ViewGroup
import android.webkit.WebChromeClient
import android.view.WindowManager
import android.webkit.JavascriptInterface
import android.webkit.WebView
import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
class MainActivity : TauriActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
private var webView: WebView? = null
private var insetTop = 0f
private var insetRight = 0f
private var insetBottom = 0f
private var insetLeft = 0f
val root = findViewById<ViewGroup>(android.R.id.content)
root.viewTreeObserver.addOnGlobalLayoutListener {
findAndHookWebView(root)
}
private inner class InsetBridge {
@JavascriptInterface
fun get(): String =
"{\"top\":$insetTop,\"right\":$insetRight,\"bottom\":$insetBottom,\"left\":$insetLeft}"
}
private fun findAndHookWebView(view: View) {
if (view is WebView) {
view.webChromeClient = object : WebChromeClient() {
override fun getDefaultVideoPoster(): Bitmap {
return Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)
}
}
return
override fun onCreate(savedInstanceState: Bundle?) {
WindowCompat.setDecorFitsSystemWindows(window, false)
window.statusBarColor = Color.TRANSPARENT
window.navigationBarColor = Color.TRANSPARENT
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
window.isNavigationBarContrastEnforced = false
window.isStatusBarContrastEnforced = false
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
window.attributes.layoutInDisplayCutoutMode =
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
}
super.onCreate(savedInstanceState)
WindowInsetsControllerCompat(window, window.decorView).apply {
isAppearanceLightStatusBars = false
isAppearanceLightNavigationBars = false
}
ViewCompat.setOnApplyWindowInsetsListener(window.decorView) { _, insets ->
val bars = insets.getInsets(
WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.displayCutout()
)
val d = resources.displayMetrics.density
insetTop = bars.top / d
insetRight = bars.right / d
insetBottom = bars.bottom / d
insetLeft = bars.left / d
pushInsets()
insets
}
ViewCompat.requestApplyInsets(window.decorView)
}
if (view is ViewGroup) {
for (i in 0 until view.childCount) {
findAndHookWebView(view.getChildAt(i))
}
override fun onWebViewCreate(webView: WebView) {
this.webView = webView
webView.addJavascriptInterface(InsetBridge(), "VylaInsets")
}
private fun pushInsets() {
val wv = webView ?: return
wv.post {
wv.evaluateJavascript(
"window.__vylaApplyInsets&&window.__vylaApplyInsets($insetTop,$insetRight,$insetBottom,$insetLeft)",
null
)
}
}
}

View File

@@ -132,6 +132,28 @@ process.on('uncaughtException', (e) => console.error('uncaughtException', e && e
process.on('unhandledRejection', (e) => console.error('unhandledRejection', e && e.stack || e));
"#;
const LAUNCHER_SCRIPT: &str = r#"
const path = require('path');
const { pathToFileURL } = require('url');
async function boot(file, port) {
process.env.PORT = String(port);
try {
await import(pathToFileURL(file).href);
} catch (e) {
console.error('failed to start ' + file, e && e.stack || e);
} finally {
process.env.PORT = '3000';
}
}
(async () => {
const root = process.env.VYLA_ROOT;
await boot(path.join(root, 'player', 'server.js'), 3000);
await boot(path.join(root, 'frontend', 'server.js'), 3001);
})();
"#;
fn android_log(msg: &str) {
#[cfg(target_os = "android")]
{
@@ -261,7 +283,10 @@ fn spawn_service_manager(base_dir: &std::path::Path) {
);
}
let server_js = server_script.to_string_lossy().to_string();
let launcher_path = base_dir.join("launcher.js");
let _ = std::fs::write(&launcher_path, LAUNCHER_SCRIPT);
std::env::set_var("VYLA_ROOT", base_dir.to_string_lossy().to_string());
let server_js = launcher_path.to_string_lossy().to_string();
let player_dir_str = player_dir.to_string_lossy().to_string();
let _ = std::thread::Builder::new().stack_size(16 * 1024 * 1024).spawn(move || {
@@ -300,6 +325,7 @@ fn spawn_service_manager(base_dir: &std::path::Path) {
});
let _ = wait_for_port(3000, Duration::from_secs(45));
let _ = wait_for_port(3001, Duration::from_secs(45));
}
fn apply_directory_replacements(dir: &std::path::Path, find_val: &str, replace_val: &str) {
@@ -355,7 +381,8 @@ async fn download_and_extract_payload(app: tauri::AppHandle, window: WebviewWind
let marker = base_dir.join(".installed");
let frontend_index = base_dir.join("frontend").join("index.html");
let server_script = base_dir.join("player").join("server.js");
if marker.exists() && frontend_index.exists() && server_script.exists() {
let frontend_server = base_dir.join("frontend").join("server.js");
if marker.exists() && frontend_index.exists() && server_script.exists() && frontend_server.exists() {
android_log("App already installed, launching...");
apply_patches(&base_dir);
spawn_service_manager(&base_dir);
@@ -363,7 +390,6 @@ async fn download_and_extract_payload(app: tauri::AppHandle, window: WebviewWind
return Ok(base_dir);
}
// 1. Check if a valid local archive already exists on device
let local_payload_candidates = [
base_dir.join("update-payload.zip"),
base_dir.join("temp-payload.zip"),
@@ -600,9 +626,37 @@ const ANDROID_VIDEO_FIX_SCRIPT: &str = r#"
})();
"#;
const EDGE_TO_EDGE_SCRIPT: &str = r#"
(function() {
var m = document.querySelector('meta[name=viewport]');
if (m && m.content.indexOf('viewport-fit') === -1) {
m.content += ', viewport-fit=cover';
}
function apply(t, r, b, l) {
var s = document.documentElement.style;
s.setProperty('--safe-top', t + 'px');
s.setProperty('--safe-right', r + 'px');
s.setProperty('--safe-bottom', b + 'px');
s.setProperty('--safe-left', l + 'px');
}
window.__vylaApplyInsets = apply;
try {
if (window.VylaInsets) {
var v = JSON.parse(window.VylaInsets.get());
apply(v.top, v.right, v.bottom, v.left);
}
} catch (e) {}
})();
"#;
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.on_page_load(|webview, payload| {
if payload.event() == tauri::webview::PageLoadEvent::Finished {
let _ = webview.eval(EDGE_TO_EDGE_SCRIPT);
}
})
.plugin(tauri_plugin_shell::init())
.plugin(tauri_plugin_process::init())
.plugin(tauri_plugin_opener::init())
@@ -681,7 +735,7 @@ pub fn run() {
Ok(_) => {
std::thread::sleep(std::time::Duration::from_millis(400));
let _ = window.eval(&format!(
"{}; window.location.replace('http://vyla.localhost/frontend/index.html');",
"{}; window.location.replace('http://localhost:3001/');",
ANDROID_VIDEO_FIX_SCRIPT
));
break;

View File

@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "Vyla Go",
"version": "1.0.13",
"version": "1.0.15",
"identifier": "com.vyla.go",
"build": {
"beforeDevCommand": "",

View File

@@ -3,9 +3,17 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<meta name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
<title>Vyla Go</title>
<style>
:root {
--safe-top: env(safe-area-inset-top, 0px);
--safe-right: env(safe-area-inset-right, 0px);
--safe-bottom: env(safe-area-inset-bottom, 0px);
--safe-left: env(safe-area-inset-left, 0px);
}
* {
box-sizing: border-box;
margin: 0;
@@ -62,7 +70,7 @@
left: 0;
right: 0;
z-index: 10;
padding: 20px 16px 30px 16px;
padding: 20px calc(16px + var(--safe-right)) calc(30px + var(--safe-bottom)) calc(16px + var(--safe-left));
display: flex;
flex-direction: column;
align-items: center;
@@ -122,7 +130,7 @@
</div>
<div class="center-brand">
<img src="https://gitlab.com/vyla-entertainment/go/-/raw/main/assets/logos/white_logo.png" alt="Logo" />
<img src="/white_logo.png" alt="Logo" />
</div>
<div class="bottom-dock">

View File

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB