17 Commits

Author SHA1 Message Date
23c2cd53c5 chore: release v1.0.15 2026-10-30 20:38:57 -07:00
7b6fc838b8 add Android edge-to-edge display support and emulator testing instructions 2026-10-30 20:35:33 -07:00
60b24bf9e4 chore: release v1.0.14 2026-10-28 20:46:40 -07:00
3958f751ee feat: launch player and frontend servers in a single Node process 2026-10-28 20:45:10 -07:00
72b752fc02 chore: release v1.0.13 2026-10-28 20:33:25 -07:00
c7463364da fix: improve Android release check networking and restore loading screen logo 2026-10-28 20:31:57 -07:00
084d215b23 chore: release v1.0.12 2026-10-28 20:21:02 -07:00
a4b3a77153 fix: robust payload download, multi-endpoint check and auto-recovery 2026-10-28 20:19:40 -07:00
Devin Bot
9b9d0dfc49 chore: release v1.0.11 2026-10-25 20:05:35 -07:00
Devin Bot
7f7a969698 chore: release v1.0.10 2026-10-25 11:36:59 -07:00
Devin Bot
fd738eeba5 fixed crashing issues 2026-10-23 17:08:29 -07:00
Devin Bot
a28239cf60 chore: release v1.0.9 2026-10-23 17:07:28 -07:00
Devin Bot
966115c456 added logo img 2026-10-23 15:38:27 -07:00
2c0cc5c933 Merge branch 'main' of https://gitlab.com/vyla-entertainment/go 2026-10-03 12:16:10 -07:00
0e385aae9e chore: release v1.0.8 2026-10-03 12:15:18 -07:00
cd18f09142 Android TV build support 2026-09-26 09:41:32 -04:00
bd321520c9 chore: release v1.0.7 2026-09-16 18:47:31 -07:00
14 changed files with 6204 additions and 78 deletions

View File

@@ -64,4 +64,9 @@ Force-stop and clear app data:
```powershell
& "$env:LOCALAPPDATA\Android\Sdk\platform-tools\adb.exe" shell am force-stop com.vyla.go
& "$env:LOCALAPPDATA\Android\Sdk\platform-tools\adb.exe" shell pm clear com.vyla.go
```
```
Fix emulator DNS timeouts (if emulator gets network timeouts connecting to GitLab):
```powershell
& "$env:LOCALAPPDATA\Android\Sdk\platform-tools\adb.exe" shell settings put global private_dns_mode off
```

View File

@@ -1,6 +1,6 @@
{
"name": "vyla-go",
"version": "1.0.6",
"version": "1.0.15",
"description": "Vyla Go Mobile Launcher",
"main": "main.js",
"scripts": {
@@ -16,7 +16,7 @@
"@tauri-apps/plugin-opener": "^2.0.0",
"@tauri-apps/plugin-process": "^2.0.0",
"@tauri-apps/plugin-shell": "^2.0.0",
"@vyla-entertainment/sdk": "^1.11.0",
"@vyla-entertainment/sdk": "^1.18.0",
"adm-zip": "^0.6.0",
"express": "^4.19.2"
},

1
src-tauri/Cargo.lock generated
View File

@@ -4593,6 +4593,7 @@ dependencies = [
"tauri-plugin-opener",
"tauri-plugin-process",
"tauri-plugin-shell",
"tokio",
"urlencoding",
"zip",
]

View File

@@ -23,3 +23,4 @@ reqwest = { version = "0.12", default-features = false, features = ["rustls-tls"
zip = { version = "2.2", default-features = false, features = ["deflate"] }
futures-util = "0.3"
urlencoding = "2.1"
tokio = { version = "1", features = ["time"] }

View File

@@ -13,6 +13,14 @@ val tauriProperties = Properties().apply {
}
}
configurations.all {
resolutionStrategy {
force("com.fasterxml.jackson.core:jackson-databind:2.13.5")
force("com.fasterxml.jackson.core:jackson-core:2.13.5")
force("com.fasterxml.jackson.core:jackson-annotations:2.13.5")
}
}
android {
compileSdk = 36
namespace = "com.vyla.go"
@@ -23,6 +31,9 @@ android {
targetSdk = 36
versionCode = tauriProperties.getProperty("tauri.android.versionCode", "1").toInt()
versionName = tauriProperties.getProperty("tauri.android.versionName", "1.0")
ndk {
abiFilters.addAll(listOf("arm64-v8a", "armeabi-v7a", "x86_64"))
}
}
signingConfigs {
create("release") {
@@ -59,6 +70,11 @@ android {
useLegacyPackaging = true
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
isCoreLibraryDesugaringEnabled = true
}
}
rust {
@@ -74,6 +90,7 @@ dependencies {
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.4")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.0")
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.4")
}
apply(from = "tauri.build.gradle.kts")

View File

@@ -2,15 +2,19 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-feature android:name="android.hardware.touchscreen" android:required="false" />
<uses-feature android:name="android.software.leanback" android:required="false" />
<application
android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:banner="@drawable/tv_banner"
android:usesCleartextTraffic="true"
android:networkSecurityConfig="@xml/network_security_config"
android:largeHeap="true"
android:extractNativeLibs="true"
android:supportsRtl="true"
android:theme="@style/Theme.vyla_go">
<activity
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode"
@@ -22,6 +26,10 @@
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

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
)
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -25,6 +25,135 @@ const RUNTIME_INTERCEPT_SCRIPT: &str = r#"
})();
"#;
const NODE_PRELOAD_SCRIPT: &str = r#"
const Module = require('module');
const origLoad = Module._load;
const stub = new Proxy(function () {}, {
get: (t, k) => (typeof k === 'symbol' || k === 'then' ? undefined : stub),
apply: () => stub,
construct: () => stub,
});
function buildTlsShim() {
class Session {
constructor(opts) {
this.opts = opts || {};
this.headers = this.opts.headers || {};
}
async request(method, url, options) {
options = options || {};
const headers = Object.assign({}, this.headers, options.headers || {});
const init = {
method,
headers,
redirect: options.followRedirects === false ? 'manual' : 'follow',
};
let body = options.body !== undefined ? options.body : options.data;
if (options.json !== undefined) {
body = JSON.stringify(options.json);
if (!headers['content-type'] && !headers['Content-Type']) {
headers['content-type'] = 'application/json';
}
} else if (body !== undefined && body !== null && typeof body === 'object' && !Buffer.isBuffer(body) && !(body instanceof URLSearchParams)) {
body = new URLSearchParams(body).toString();
if (!headers['content-type'] && !headers['Content-Type']) {
headers['content-type'] = 'application/x-www-form-urlencoded';
}
}
if (body !== undefined && body !== null && method !== 'GET' && method !== 'HEAD') {
init.body = body;
}
let ms = Number(options.timeout || this.opts.timeout || 30000);
if (ms < 1000) ms = ms * 1000;
init.signal = AbortSignal.timeout(ms);
const res = await fetch(url, init);
const text = await res.text();
const outHeaders = {};
res.headers.forEach((v, k) => { outHeaders[k] = v; });
return {
id: String(Date.now()),
status: res.status,
ok: res.ok,
success: res.ok,
url: res.url,
target: res.url,
headers: outHeaders,
cookies: {},
body: text,
text: async () => text,
json: async () => JSON.parse(text),
};
}
get(url, o) { return this.request('GET', url, o); }
post(url, o) { return this.request('POST', url, o); }
put(url, o) { return this.request('PUT', url, o); }
patch(url, o) { return this.request('PATCH', url, o); }
delete(url, o) { return this.request('DELETE', url, o); }
head(url, o) { return this.request('HEAD', url, o); }
options(url, o) { return this.request('OPTIONS', url, o); }
async close() {}
async free() {}
}
const ClientIdentifier = new Proxy({}, { get: (t, k) => (typeof k === 'symbol' ? undefined : String(k)) });
const mod = {
Session,
ClientIdentifier,
initTLS: async () => {},
destroyTLS: async () => {},
};
mod.default = mod;
return mod;
}
let tlsShim = null;
Module._load = function (request) {
if (request === 'node-tls-client') {
if (!tlsShim) {
tlsShim = buildTlsShim();
console.error('node-tls-client replaced with fetch shim');
}
return tlsShim;
}
try {
return origLoad.apply(this, arguments);
} catch (e) {
const msg = String(e && e.message);
if (/koffi/i.test(request) || /koffi/i.test(msg)) {
console.error('stubbed native module: ' + request + ' (' + msg + ')');
return stub;
}
throw e;
}
};
process.on('uncaughtException', (e) => console.error('uncaughtException', e && e.stack || 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")]
{
@@ -135,6 +264,13 @@ fn spawn_service_manager(base_dir: &std::path::Path) {
.map(|d| d.to_string_lossy().to_string())
.unwrap_or_default();
let tmp_dir = base_dir.join("tmp");
let _ = std::fs::create_dir_all(&tmp_dir);
std::env::set_var("HOME", base_dir.to_string_lossy().to_string());
std::env::set_var("TMPDIR", tmp_dir.to_string_lossy().to_string());
let preload_path = base_dir.join("preload.js");
let _ = std::fs::write(&preload_path, NODE_PRELOAD_SCRIPT);
let preload_str = preload_path.to_string_lossy().to_string();
std::env::set_var("PORT", "3000");
std::env::set_var("NODE_ENV", "production");
std::env::set_var("TMDB_API_KEY", "3f6703f3e2a0b2b40fe4fbb3e1546244");
@@ -147,29 +283,39 @@ 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();
std::thread::spawn(move || {
let _ = std::thread::Builder::new().stack_size(16 * 1024 * 1024).spawn(move || {
#[cfg(target_os = "android")]
{
let _ = std::env::set_current_dir(&player_dir_str);
let arg0 = std::ffi::CString::new("node").unwrap();
let arg1 = std::ffi::CString::new(server_js.as_str()).unwrap();
let parts: [&str; 4] = ["node", "--require", preload_str.as_str(), server_js.as_str()];
let mut buf: Vec<u8> = Vec::new();
let mut offsets: Vec<usize> = Vec::new();
for p in parts.iter() {
offsets.push(buf.len());
buf.extend_from_slice(p.as_bytes());
buf.push(0);
}
buf.shrink_to_fit();
let mut argv: Vec<*mut std::os::raw::c_char> = vec![
arg0.as_ptr() as *mut _,
arg1.as_ptr() as *mut _,
std::ptr::null_mut(),
];
let base = buf.as_mut_ptr() as *mut std::os::raw::c_char;
let mut argv: Vec<*mut std::os::raw::c_char> = offsets
.iter()
.map(|o| unsafe { base.add(*o) })
.collect();
argv.push(std::ptr::null_mut());
std::mem::forget(buf);
android_log("calling node::Start via FFI");
let ret = unsafe { node_ffi::_ZN4node5StartEiPPc(2, argv.as_mut_ptr()) };
let ret = unsafe { node_ffi::_ZN4node5StartEiPPc(4, argv.as_mut_ptr()) };
android_log(&format!("node::Start returned: {}", ret));
drop(arg0);
drop(arg1);
}
#[cfg(not(target_os = "android"))]
@@ -179,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) {
@@ -231,44 +378,160 @@ async fn download_and_extract_payload(app: tauri::AppHandle, window: WebviewWind
let base_dir = get_app_dir(&app);
std::fs::create_dir_all(&base_dir).map_err(|e| format!("Storage dir error: {}", e))?;
let marker = base_dir.join(".installed");
let frontend_index = base_dir.join("frontend").join("index.html");
if frontend_index.exists() {
let server_script = base_dir.join("player").join("server.js");
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);
update_ui(&window, "Launching Vyla Go...", Some(100));
return Ok(base_dir);
}
update_ui(&window, "Connecting to GitLab...", Some(10));
let local_payload_candidates = [
base_dir.join("update-payload.zip"),
base_dir.join("temp-payload.zip"),
PathBuf::from("/sdcard/Download/update-payload.zip"),
PathBuf::from("/data/local/tmp/update-payload.zip"),
];
for candidate in &local_payload_candidates {
if candidate.exists() {
if let Ok(meta) = std::fs::metadata(candidate) {
if meta.len() > 10_000_000 {
if let Ok(f) = File::open(candidate) {
if let Ok(archive) = zip::ZipArchive::new(f) {
if archive.len() > 0 {
android_log(&format!("Found valid local payload: {}", candidate.display()));
update_ui(&window, "Unpacking local package...", Some(80));
let extract_dir = base_dir.clone();
let candidate_path = candidate.clone();
let extract_ok = tauri::async_runtime::spawn_blocking(move || -> Result<(), String> {
let zip_file = File::open(&candidate_path).map_err(|e| format!("Open zip error: {}", e))?;
let mut archive = zip::ZipArchive::new(zip_file).map_err(|e| format!("Zip parse error: {}", e))?;
archive.extract(&extract_dir).map_err(|e| format!("Extraction error: {}", e))?;
let _ = std::fs::write(extract_dir.join(".installed"), "ok");
apply_patches(&extract_dir);
Ok(())
})
.await
.map_err(|e| e.to_string())?;
if extract_ok.is_ok() {
spawn_service_manager(&base_dir);
update_ui(&window, "Launching Vyla Go...", Some(100));
return Ok(base_dir);
}
}
}
}
}
}
}
}
let client = reqwest::Client::builder()
.user_agent("Mozilla/5.0 (Android) VylaGo/1.0")
.connect_timeout(std::time::Duration::from_secs(30))
.connect_timeout(std::time::Duration::from_secs(15))
.timeout(std::time::Duration::from_secs(180))
.redirect(reqwest::redirect::Policy::limited(10))
.local_address(std::net::IpAddr::V4(std::net::Ipv4Addr::UNSPECIFIED))
.http1_only()
.pool_max_idle_per_host(0)
.build()
.map_err(|e| format!("Client error: {}", e))?;
let releases_url = "https://gitlab.com/api/v4/projects/vyla-entertainment%2Fhome/releases";
let res = client.get(releases_url).send().await.map_err(|e| format!("Release check failed: {}", e))?;
let json: serde_json::Value = res.json().await.map_err(|e| format!("JSON error: {}", e))?;
let release_endpoints = [
"https://gitlab.com/api/v4/projects/vyla-entertainment%2Fgo/releases",
"https://gitlab.com/api/v4/projects/vyla-entertainment%2Fhome/releases",
];
let download_url = json.get(0)
.and_then(|r| r.get("assets"))
.and_then(|a| a.get("links"))
.and_then(|l| l.as_array())
.and_then(|links| {
links.iter().find(|item| {
item.get("name").and_then(|n| n.as_str()) == Some("update-payload.zip")
})
})
.and_then(|asset| asset.get("url"))
.and_then(|u| u.as_str())
.ok_or_else(|| "Could not locate update-payload.zip link in release assets".to_string())?;
let mut download_url: Option<String> = None;
let mut last_error = String::new();
const MAX_RETRIES: u32 = 4;
for attempt in 1..=MAX_RETRIES {
let msg = if attempt == 1 {
"Connecting to GitLab...".to_string()
} else {
format!("Connecting to GitLab (attempt {}/{})...", attempt, MAX_RETRIES)
};
update_ui(&window, &msg, Some(10 + (attempt - 1) * 3));
for endpoint in &release_endpoints {
android_log(&format!("Checking endpoint: {} (attempt {})", endpoint, attempt));
match client.get(*endpoint).send().await {
Ok(res) => {
if res.status().is_success() {
if let Ok(json) = res.json::<serde_json::Value>().await {
if let Some(releases) = json.as_array() {
for rel in releases {
if let Some(links) = rel.get("assets").and_then(|a| a.get("links")).and_then(|l| l.as_array()) {
for item in links {
if item.get("name").and_then(|n| n.as_str()) == Some("update-payload.zip") {
if let Some(url) = item.get("url").and_then(|u| u.as_str()) {
download_url = Some(url.to_string());
break;
}
}
}
}
if download_url.is_some() {
break;
}
}
}
}
} else {
last_error = format!("HTTP {}", res.status());
}
}
Err(e) => {
let mut detail = format!("{}", e);
let mut src = StdError::source(&e);
while let Some(s) = src {
detail.push_str(&format!(" <- {}", s));
src = s.source();
}
android_log(&format!("Release check failed for {}: {}", endpoint, detail));
last_error = detail;
}
}
if download_url.is_some() {
break;
}
}
if download_url.is_some() {
break;
}
if attempt < MAX_RETRIES {
let backoff = attempt * 2;
update_ui(
&window,
&format!("Connection pending, retrying in {}s...", backoff),
Some(10 + attempt * 3),
);
tokio::time::sleep(std::time::Duration::from_secs(backoff as u64)).await;
}
}
let download_url = download_url.ok_or_else(|| {
format!(
"Could not connect to release server ({})",
if last_error.is_empty() { "timeout" } else { &last_error }
)
})?;
update_ui(&window, "Downloading update payload...", Some(25));
android_log(&format!("Downloading from: {}", download_url));
let response = client.get(download_url).send().await.map_err(|e| format!("Download error: {} (source: {:?})", e, StdError::source(&e)))?; android_log(&format!("Response status: {}", response.status()));
let response = client.get(&download_url).send().await.map_err(|e| format!("Download error: {} (source: {:?})", e, StdError::source(&e)))?;
android_log(&format!("Response status: {}", response.status()));
let total_size = response.content_length().unwrap_or(0) as usize;
let temp_zip_path = base_dir.join("temp-payload.zip");
@@ -287,7 +550,7 @@ async fn download_and_extract_payload(app: tauri::AppHandle, window: WebviewWind
downloaded += chunk.len();
let percent = if total_size > 0 {
10 + ((downloaded as f64 / total_size as f64) * 75.0) as u32
25 + ((downloaded as f64 / total_size as f64) * 65.0) as u32
} else {
35
};
@@ -310,7 +573,8 @@ async fn download_and_extract_payload(app: tauri::AppHandle, window: WebviewWind
let zip_file = File::open(&zip_path_clone).map_err(|e| format!("Open zip error: {}", e))?;
let mut archive = zip::ZipArchive::new(zip_file).map_err(|e| format!("Zip parse error: {}", e))?;
archive.extract(&extract_dir).map_err(|e| format!("Extraction error: {}", e))?;
let _ = std::fs::remove_file(zip_path_clone);
let _ = std::fs::remove_file(&zip_path_clone);
let _ = std::fs::write(extract_dir.join(".installed"), "ok");
apply_patches(&extract_dir);
Ok(())
})
@@ -362,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())
@@ -433,25 +725,32 @@ pub fn run() {
}
tauri::async_runtime::spawn(async move {
std::thread::sleep(std::time::Duration::from_millis(400));
std::thread::sleep(std::time::Duration::from_millis(600));
if let Some(window) = handle.get_webview_window("main") {
update_ui(&window, "Initializing Vyla Go engine...", Some(5));
match download_and_extract_payload(handle.clone(), window.clone()).await {
Ok(_) => {
std::thread::sleep(std::time::Duration::from_millis(400));
let _ = window.eval(&format!(
"{}; window.location.replace('http://vyla.localhost/frontend/index.html');",
ANDROID_VIDEO_FIX_SCRIPT
));
}
Err(err) => {
update_ui(
&window,
&format!("Error: {}", err),
Some(0),
);
loop {
match download_and_extract_payload(handle.clone(), window.clone()).await {
Ok(_) => {
std::thread::sleep(std::time::Duration::from_millis(400));
let _ = window.eval(&format!(
"{}; window.location.replace('http://localhost:3001/');",
ANDROID_VIDEO_FIX_SCRIPT
));
break;
}
Err(err) => {
android_log(&format!("Installer error: {}", err));
for remaining in (1..=8).rev() {
update_ui(
&window,
&format!("Error: {}. Retrying in {}s...", err, remaining),
Some(0),
);
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
}
}
}
}
}

View File

@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "Vyla Go",
"version": "1.0.6",
"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/home-frontend/-/raw/main/assets/images/logo.png" alt="Logo" />
<img src="/white_logo.png" alt="Logo" />
</div>
<div class="bottom-dock">

BIN
ui/white_logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB