mirror of
https://github.com/twinksoftware/Gearslip.git
synced 2026-11-10 16:02:25 +01:00
- Weather from Open-Meteo with a place name, a forecast screen and a nav bar icon; a fresh location fix is requested when the last one is stale - Map controls are solid, one size, and fade out after four seconds; pinch to zoom works because every finger now reaches the app - Map templates draw their content in a floating card; toggle and radio rows show their state, the Back and Pan actions work, and details text and buttons are larger - Home player gains Lyrics and Minimise buttons; minimised, it lives in the nav bar - Media apps that hide their library show what they are and can still be controlled; apps that refuse Gearslip as a host say why - Debug mode switch, session report in the log, and log sharing as a file attachment - Launcher, phone apps, weather and calendar load at startup - Web app opens DuckDuckGo by default - Home screen lists the setup steps; the launcher marks apps tested and working
68 lines
2.9 KiB
Kotlin
68 lines
2.9 KiB
Kotlin
package app.seb3thehacker.gearslip
|
|
|
|
import android.content.Context
|
|
import java.io.File
|
|
|
|
/** Decides what the car screen shows. */
|
|
object ProjectedContent {
|
|
|
|
/**
|
|
* Precedence: the URL saved in Settings, then a `url.txt` in the external files dir (the
|
|
* pre-Settings way, still honoured so existing setups keep working), then [DEFAULT_URL].
|
|
*/
|
|
fun resolve(context: Context): String {
|
|
val saved = AppSettings.startupUrl(context)
|
|
if (saved.isNotEmpty()) {
|
|
val url = normalize(saved)
|
|
GearslipLog.i("projecting startup URL from settings: $url")
|
|
return url
|
|
}
|
|
val legacy = context.getExternalFilesDir(null)?.let { File(it, "url.txt") }
|
|
if (legacy != null && legacy.isFile) {
|
|
val url = legacy.readText().trim()
|
|
if (url.isNotEmpty()) {
|
|
GearslipLog.i("projecting url.txt override: $url")
|
|
return url
|
|
}
|
|
}
|
|
return DEFAULT_URL
|
|
}
|
|
|
|
/** What the Web app opens until a page is chosen in Settings. */
|
|
const val DEFAULT_URL = "https://duckduckgo.com"
|
|
|
|
/** A bare "example.com" is a URL the user meant; raw HTML is passed through untouched. */
|
|
private fun normalize(value: String): String =
|
|
if (value.startsWith("<") || "://" in value) value else "https://$value"
|
|
|
|
/** Big targets, a live clock, and tap feedback - all verifiable at a glance in a car. */
|
|
val TEST_PAGE = """
|
|
<html><head><meta name="viewport" content="width=device-width,initial-scale=1">
|
|
<style>
|
|
html{height:100%}
|
|
body{margin:0;font-family:sans-serif;background:#101418;color:#fff;
|
|
display:flex;flex-direction:column;height:100%}
|
|
h1{margin:12px;font-size:28px}
|
|
#t{margin:0 12px 8px;font-size:20px;color:#8ab4f8}
|
|
.row{display:flex;flex:1;gap:8px;padding:8px}
|
|
.b{flex:1;display:flex;align-items:center;justify-content:center;
|
|
font-size:26px;border-radius:12px;background:#243043}
|
|
.hit{background:#2e7d32}
|
|
</style></head><body>
|
|
<h1>Gearslip — projected from the phone</h1>
|
|
<div id="t">starting…</div>
|
|
<div class="row">
|
|
<div class="b" id="a">TAP A</div><div class="b" id="b">TAP B</div>
|
|
<div class="b" id="c">TAP C</div>
|
|
</div>
|
|
<script>
|
|
setInterval(function(){document.getElementById('t').textContent=new Date().toLocaleTimeString();},1000);
|
|
['a','b','c'].forEach(function(id){
|
|
var e=document.getElementById(id);
|
|
e.addEventListener('touchstart',function(){e.classList.add('hit');e.textContent='HIT '+id.toUpperCase();});
|
|
e.addEventListener('touchend',function(){setTimeout(function(){e.classList.remove('hit');e.textContent='TAP '+id.toUpperCase();},400);});
|
|
});
|
|
</script></body></html>
|
|
""".trimIndent()
|
|
}
|