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

3120
bun.lock

File diff suppressed because it is too large Load Diff

View File

@@ -21,6 +21,7 @@
"core-js-pure",
"esbuild",
"protobufjs",
"sharp",
"workerd"
],
"workspaces": {

View File

@@ -0,0 +1,13 @@
> Why do I have a folder named ".expo" in my project?
The ".expo" folder is created when an Expo project is started using "expo start" command.
> What do the files contain?
- "devices.json": contains information about devices that have recently opened this project. This is used to populate the "Development sessions" list in your development builds.
- "settings.json": contains the server configuration that is used to serve the application manifest.
> Should I commit the ".expo" folder?
No, you should not share the ".expo" folder. It does not contain any information that is relevant for other developers working on the project, it is specific to your machine.
Upon project creation, the ".expo" folder is already added to your ".gitignore" file.

View File

@@ -0,0 +1,3 @@
{
"devices": []
}

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" ?>
<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,70 +1,59 @@
---
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>
<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">
<h1>Nestri Play</h1>
<div>
<label for="room">Room</label>
<input type="text" id="room" name="room" required list="room-list">
<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>
<label for="peerURL">peerURL</label>
<input type="text" id="peerURL" name="peerURL" required list="peerURL-list">
<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">Join</button>
<button type="submit" id="submit-btn">Join Stream</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</DefaultLayout>
<script>
import { navigate } from "astro:transitions/client";
@@ -74,25 +63,9 @@ import { navigate } from "astro:transitions/client";
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;
}
}
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') || '[]');
// Load values from localStorage
const storedRooms = JSON.parse(localStorage.getItem('nestri-rooms') || '[]');
const storedPeerURLs = JSON.parse(localStorage.getItem('nestri-peerURLs') || '[]');
if (roomList) {
storedRooms.forEach(room => {
@@ -122,17 +95,15 @@ import { navigate } from "astro:transitions/client";
const room = roomInput.value;
const peerURL = peerURLInput.value;
// Save values to cookies
// 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);
setCookie('nestri-rooms', JSON.stringify(newRooms), 365);
setCookie('nestri-peerURLs', JSON.stringify(newPeerURLs), 365);
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>
</body>
</html>

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