better UI + AppIcon

This commit is contained in:
Philipp Neumann
2025-11-21 15:18:44 +01:00
parent e47f3189f6
commit 8f4e3ab956
73 changed files with 3234 additions and 405 deletions

View File

@@ -9,7 +9,7 @@ android {
apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
dependencies {
implementation project(':capacitor-screen-orientation')
}

View File

@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8" ?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
@@ -8,7 +7,6 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode|navigation"
android:name=".MainActivity"
@@ -16,12 +14,12 @@
android:theme="@style/AppTheme.NoActionBarLaunch"
android:launchMode="singleTask"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
</intent-filter>
</activity>
<provider
@@ -29,12 +27,17 @@
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"></meta-data>
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" />
</provider>
</application>
<uses-feature
android:name="android.software.leanback"
android:required="false" />
<uses-feature
android:name="android.hardware.touchscreen"
android:required="false" />
<!-- Permissions -->
<uses-permission android:name="android.permission.INTERNET" />

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.6 KiB

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@@ -1,5 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
<background>
<inset android:drawable="@mipmap/ic_launcher_background" android:inset="16.7%" />
</background>
<foreground>
<inset android:drawable="@mipmap/ic_launcher_foreground" android:inset="16.7%" />
</foreground>
</adaptive-icon>

View File

@@ -1,5 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
<background>
<inset android:drawable="@mipmap/ic_launcher_background" android:inset="16.7%" />
</background>
<foreground>
<inset android:drawable="@mipmap/ic_launcher_foreground" android:inset="16.7%" />
</foreground>
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 531 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 573 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 277 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 868 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 850 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 350 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 697 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.6 KiB

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

@@ -1,3 +1,6 @@
// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN
include ':capacitor-android'
project(':capacitor-android').projectDir = new File('../../../node_modules/@capacitor/android/capacitor')
include ':capacitor-screen-orientation'
project(':capacitor-screen-orientation').projectDir = new File('../../../node_modules/@capacitor/screen-orientation/android')

View File

@@ -0,0 +1,20 @@
<svg
width="512"
height="512"
viewBox="0 0 26.458333 26.458333"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
>
<rect x="0" y="0" width="26.458333" height="26.458333" fill="#09090b"/>
<g transform="translate(13.2291665, 13.2291665) scale(0.60) translate(-13.2291665, -13.2291665)">
<path
d="M 1.5344249,1.5344211 V 9.2976453 C 10.141372,9.3506226 17.107729,16.31697 17.160697,24.923917 h 7.763224 V 1.5344211 H 17.26052 V 7.6582313 L 17.095435,7.5123325 C 12.813137,3.6822243 7.2773806,1.558425 1.5344327,1.5344191 Z"
fill="#FFFFFF"></path>
<path
d="m 1.5344249,10.321451 v 7.472042 c 3.9146312,0.04963 7.080792,3.215792 7.1304217,7.130424 H 16.136889 C 16.086774,16.878739 9.5796027,10.37157 1.5344249,10.321451 Z"
fill="#FFFFFF"></path>
<path
d="m 1.5344249,18.699055 v 6.224862 H 7.7592875 C 7.7148345,21.504871 4.9534705,18.743512 1.5344249,18.699055 Z"
fill="#FFFFFF"></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 702 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -5,18 +5,23 @@
"scripts": {
"dev": "astro dev",
"build": "astro build",
"build:cap": "astro build && npx cap copy",
"build:cap": "astro build && bunx cap copy",
"preview": "astro preview",
"astro": "astro",
"sync:android": "npm run build && npx cap sync android"
"sync:android": "bun run build && bunx cap sync android"
},
"dependencies": {
"@astrojs/node": "9.5.0",
"@capacitor/android": "^7.4.3",
"@capacitor/cli": "^7.4.3",
"@capacitor/core": "^7.4.3",
"@capacitor/ios": "^7.4.3",
"@astrojs/node": "9.5.0",
"@capacitor/screen-orientation": "^7.0.2",
"@nestri/input": "*",
"astro": "5.15.1"
},
"devDependencies": {
"@capacitor/assets": "^3.0.5",
"sharp": "^0.34.5"
}
}

View File

@@ -0,0 +1,46 @@
{
"icons": [
{
"src": "../icons/icon-48.webp",
"type": "image/png",
"sizes": "48x48",
"purpose": "any maskable"
},
{
"src": "../icons/icon-72.webp",
"type": "image/png",
"sizes": "72x72",
"purpose": "any maskable"
},
{
"src": "../icons/icon-96.webp",
"type": "image/png",
"sizes": "96x96",
"purpose": "any maskable"
},
{
"src": "../icons/icon-128.webp",
"type": "image/png",
"sizes": "128x128",
"purpose": "any maskable"
},
{
"src": "../icons/icon-192.webp",
"type": "image/png",
"sizes": "192x192",
"purpose": "any maskable"
},
{
"src": "../icons/icon-256.webp",
"type": "image/png",
"sizes": "256x256",
"purpose": "any maskable"
},
{
"src": "../icons/icon-512.webp",
"type": "image/png",
"sizes": "512x512",
"purpose": "any maskable"
}
]
}

View File

@@ -1,138 +1,109 @@
---
import { ClientRouter } from "astro:transitions";
import { navigate } from "astro:transitions/client";
import DefaultLayout from "../layouts/DefaultLayout.astro";
import "../styles/index.css";
---
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width">
<title>Nestri Play</title>
<DefaultLayout>
<ClientRouter />
<style>
body {
font-family: sans-serif;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
}
form {
display: flex;
flex-direction: column;
gap: 1rem;
padding: 2rem;
border: 1px solid #ccc;
border-radius: 8px;
}
div {
display: flex;
flex-direction: column;
}
label {
margin-bottom: 0.5rem;
}
input {
padding: 0.5rem;
border: 1px solid #ccc;
border-radius: 4px;
}
button {
padding: 0.75rem;
border: none;
border-radius: 4px;
background-color: #007bff;
color: white;
cursor: pointer;
}
button:hover {
background-color: #0056b3;
}
</style>
</head>
<body>
<form id="join-form">
<h1>Nestri Play</h1>
<div>
<label for="room">Room</label>
<input type="text" id="room" name="room" required list="room-list">
<datalist id="room-list"></datalist>
<div class="page-container">
<div class="grid-system">
<div class="grid-root">
<div class="grid-block">
<div class="stack">
<div class="header-stack">
<h2>Nestri Play</h2>
<p class="subtitle">
Enter your room details to join the stream.
</p>
</div>
<form id="join-form">
<div class="form-stack">
<div class="input-group">
<input
type="text"
id="room"
name="room"
required
list="room-list"
placeholder="Room Name"
>
<datalist id="room-list"></datalist>
</div>
<div class="input-group">
<input
type="text"
id="peerURL"
name="peerURL"
required
list="peerURL-list"
placeholder="Peer URL"
>
<datalist id="peerURL-list"></datalist>
</div>
<button type="submit" id="submit-btn">Join Stream</button>
</div>
</form>
</div>
</div>
</div>
</div>
<div>
<label for="peerURL">peerURL</label>
<input type="text" id="peerURL" name="peerURL" required list="peerURL-list">
<datalist id="peerURL-list"></datalist>
</div>
<button type="submit">Join</button>
</form>
</div>
</DefaultLayout>
<script>
import { navigate } from "astro:transitions/client";
<script>
import { navigate } from "astro:transitions/client";
const roomInput = document.getElementById('room') as HTMLInputElement;
const peerURLInput = document.getElementById('peerURL') as HTMLInputElement;
const roomList = document.getElementById('room-list');
const peerURLList = document.getElementById('peerURL-list');
const roomInput = document.getElementById('room') as HTMLInputElement;
const peerURLInput = document.getElementById('peerURL') as HTMLInputElement;
const roomList = document.getElementById('room-list');
const peerURLList = document.getElementById('peerURL-list');
// Load values from cookies
function getCookie(name) {
const value = `; ${document.cookie}`;
const parts = value.split(`; ${name}=`);
if (parts.length === 2) {
const cookieValue = parts.pop()?.split(';').shift();
return cookieValue ? decodeURIComponent(cookieValue) : undefined;
}
}
// Load values from localStorage
const storedRooms = JSON.parse(localStorage.getItem('nestri-rooms') || '[]');
const storedPeerURLs = JSON.parse(localStorage.getItem('nestri-peerURLs') || '[]');
function setCookie(name, value, days) {
const d = new Date();
d.setTime(d.getTime() + (days*24*60*60*1000));
const expires = "expires="+ d.toUTCString();
document.cookie = name + "=" + encodeURIComponent(value) + ";" + expires + ";path=/";
}
const storedRooms = JSON.parse(getCookie('nestri-rooms') || '[]');
const storedPeerURLs = JSON.parse(getCookie('nestri-peerURLs') || '[]');
if (roomList) {
storedRooms.forEach(room => {
const option = document.createElement('option');
option.value = room;
roomList.appendChild(option);
});
}
if (peerURLList) {
storedPeerURLs.forEach(peerURL => {
const option = document.createElement('option');
option.value = peerURL;
peerURLList.appendChild(option);
});
}
if (storedRooms.length > 0 && roomInput) {
roomInput.value = storedRooms[0];
}
if (storedPeerURLs.length > 0 && peerURLInput) {
peerURLInput.value = storedPeerURLs[0];
}
document.getElementById('join-form')?.addEventListener('submit', function(event) {
event.preventDefault();
const room = roomInput.value;
const peerURL = peerURLInput.value;
// Save values to cookies
const newRooms = [room, ...storedRooms.filter(r => r !== room)].slice(0, 10);
const newPeerURLs = [peerURL, ...storedPeerURLs.filter(p => p !== peerURL)].slice(0, 10);
setCookie('nestri-rooms', JSON.stringify(newRooms), 365);
setCookie('nestri-peerURLs', JSON.stringify(newPeerURLs), 365);
if (room && peerURL) {
navigate(`/play/index.html?peerURL=${encodeURIComponent(peerURL)}#${room}`);
}
if (roomList) {
storedRooms.forEach(room => {
const option = document.createElement('option');
option.value = room;
roomList.appendChild(option);
});
</script>
</body>
</html>
}
if (peerURLList) {
storedPeerURLs.forEach(peerURL => {
const option = document.createElement('option');
option.value = peerURL;
peerURLList.appendChild(option);
});
}
if (storedRooms.length > 0 && roomInput) {
roomInput.value = storedRooms[0];
}
if (storedPeerURLs.length > 0 && peerURLInput) {
peerURLInput.value = storedPeerURLs[0];
}
document.getElementById('join-form')?.addEventListener('submit', function(event) {
event.preventDefault();
const room = roomInput.value;
const peerURL = peerURLInput.value;
// Save values to localStorage
const newRooms = [room, ...storedRooms.filter(r => r !== room)].slice(0, 10);
const newPeerURLs = [peerURL, ...storedPeerURLs.filter(p => p !== peerURL)].slice(0, 10);
localStorage.setItem('nestri-rooms', JSON.stringify(newRooms));
localStorage.setItem('nestri-peerURLs', JSON.stringify(newPeerURLs));
if (room && peerURL) {
navigate(`/play/index.html?peerURL=${encodeURIComponent(peerURL)}#${room}`);
}
});
</script>

View File

@@ -24,6 +24,22 @@ if (envs_map.size > 0) {
<script>
import { Mouse, Keyboard, Controller, WebRTCStream } from "@nestri/input";
import { ScreenOrientation } from '@capacitor/screen-orientation';
// Lock to landscape
const lockOrientation = async () => {
try {
await ScreenOrientation.lock({ orientation: 'landscape' });
} catch (e) {
console.warn("Screen orientation lock failed:", e);
}
};
lockOrientation();
window.addEventListener('beforeunload', () => {
ScreenOrientation.unlock();
});
const ENVS = document.getElementById("ENVS")!.dataset.envs as string;
let ENVS_MAP: Map<string, string | undefined> | null = null;
if (ENVS && ENVS.length > 0) {

View File

@@ -0,0 +1,144 @@
:root {
--color-background: #191919;
--color-surface: #252525;
--color-primary: #f97316; /* Orange-500 */
--color-primary-hover: #ea580c; /* Orange-600 */
--color-text-main: #ffffff;
--color-text-muted: #a1a1aa;
--color-border: #3f3f46;
color-scheme: dark;
}
.page-container {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
width: 100%;
padding: 2rem;
box-sizing: border-box;
}
.grid-system {
width: 100%;
max-width: 1400px;
}
.grid-root {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 1rem;
}
@media (min-width: 768px) {
.grid-root {
grid-template-columns: repeat(12, 1fr);
}
}
.grid-block {
grid-column: 1 / -1;
display: flex;
flex-direction: column;
align-items: center;
}
@media (min-width: 768px) {
.grid-block {
grid-column: 4 / 10; /* Center in the middle 6 columns */
}
}
.stack {
display: flex;
flex-direction: column;
gap: 3rem;
width: 100%;
max-width: 500px;
text-align: center;
}
.header-stack {
display: flex;
flex-direction: column;
gap: 1rem;
align-items: center;
}
h2 {
font-size: 2.5rem;
font-weight: 700;
margin: 0;
color: var(--color-text-main);
letter-spacing: -0.02em;
}
.subtitle {
color: var(--color-text-muted);
font-size: 1rem;
line-height: 1.5;
margin: 0;
}
.form-stack {
display: flex;
flex-direction: column;
gap: 1rem;
width: 100%;
}
.input-group {
width: 100%;
}
input {
width: 100%;
padding: 0.75rem 1rem;
background-color: var(--color-surface);
border: 2px solid transparent;
border-radius: 4px;
color: var(--color-text-main);
font-size: 1rem;
transition: all 0.2s ease;
box-sizing: border-box;
caret-color: var(--color-primary);
}
input:focus {
outline: none;
border-color: var(--color-primary);
background-color: #2a2a2a;
color: var(--color-text-main);
}
input::placeholder {
color: #52525b;
}
button {
width: 100%;
padding: 0.75rem 1.5rem;
background-color: var(--color-primary);
color: white;
border: none;
border-radius: 9999px; /* Full rounded like signup */
font-size: 1rem;
font-weight: 600;
text-transform: uppercase;
cursor: pointer;
transition: background-color 0.2s ease;
margin-top: 1rem;
}
button:hover {
background-color: var(--color-primary-hover);
}
/* Autofill styling fix for dark mode */
input:-webkit-autofill,
input:-webkit-autofill:hover,
input:-webkit-autofill:focus,
input:-webkit-autofill:active{
-webkit-box-shadow: 0 0 0 30px var(--color-surface) inset !important;
-webkit-text-fill-color: var(--color-text-main) !important;
}