feat: update image requirements to landscape orientation and add CI/CD workflow
Some checks failed
Build App / build (push) Failing after 3m44s
43
.gitea/workflows/ci.yml
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
name: Build App
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: 🏗 Setup repo
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: 🏗 Setup Node
|
||||||
|
uses: actions/setup-node@v2
|
||||||
|
with:
|
||||||
|
node-version: 22
|
||||||
|
|
||||||
|
- name: 🏗 Setup Expo and EAS
|
||||||
|
uses: expo/expo-github-action@v7
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.EXPO_TOKEN }}
|
||||||
|
expo-version: latest
|
||||||
|
eas-version: latest
|
||||||
|
|
||||||
|
- name: 📦 Install dependencies
|
||||||
|
run: npm install
|
||||||
|
working-directory: App
|
||||||
|
|
||||||
|
- name: 👷 Build app
|
||||||
|
run: |
|
||||||
|
eas build --local \
|
||||||
|
--non-interactive \
|
||||||
|
--output=./app-build \
|
||||||
|
--platform=android \
|
||||||
|
--profile=preview
|
||||||
|
working-directory: App
|
||||||
|
|
||||||
|
|
||||||
|
- name: 📤 Upload build artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: android-preview-build.zip
|
||||||
|
path: App/app-build/*
|
||||||
|
if-no-files-found: error
|
||||||
4
.github/copilot-instructions.md
vendored
@@ -37,9 +37,9 @@ Images/rotator assets stored in MinIO at `content2.reversed.dev`, bucket `tv-con
|
|||||||
|
|
||||||
`settings` is a persistent object with global TV display configuration (typed as `SettingsState` in `tv/src/types.ts`).
|
`settings` is a persistent object with global TV display configuration (typed as `SettingsState` in `tv/src/types.ts`).
|
||||||
|
|
||||||
- **`background_url`** — a portrait image (height > width) rendered as a full-screen `object-cover` background behind all TV content. Empty string = no background.
|
- **`background_url`** — a landscape image (width > height) rendered as a full-screen `object-cover` background behind all TV content. Empty string = no background.
|
||||||
- Set via `push_settings { background_url }`, cleared by passing `""`.
|
- Set via `push_settings { background_url }`, cleared by passing `""`.
|
||||||
- The constraint (portrait orientation) is enforced in the mobile picker (`mobile/src/pages/settings.tsx`) — `asset.width >= asset.height` is rejected.
|
- The constraint (landscape orientation) is enforced in the mobile picker (`mobile/src/pages/settings.tsx`) — `asset.width <= asset.height` is rejected.
|
||||||
- Images are uploaded first via `push_upload_images` to get a MinIO URL, then saved via `push_settings`.
|
- Images are uploaded first via `push_upload_images` to get a MinIO URL, then saved via `push_settings`.
|
||||||
- The Settings tab is visible in the mobile bottom nav (no `hideInNav`).
|
- The Settings tab is visible in the mobile bottom nav (no `hideInNav`).
|
||||||
|
|
||||||
|
|||||||
@@ -1,30 +1,37 @@
|
|||||||
{
|
{
|
||||||
"expo": {
|
"expo": {
|
||||||
"name": "mobile",
|
"name": "TV Control",
|
||||||
"slug": "mobile",
|
"slug": "tv-control",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"orientation": "portrait",
|
"orientation": "portrait",
|
||||||
"icon": "./assets/icon.png",
|
"icon": "./assets/icon.png",
|
||||||
"userInterfaceStyle": "light",
|
"userInterfaceStyle": "automatic",
|
||||||
"newArchEnabled": true,
|
"newArchEnabled": true,
|
||||||
"splash": {
|
"splash": {
|
||||||
"image": "./assets/splash-icon.png",
|
"image": "./assets/splash-icon.png",
|
||||||
"resizeMode": "contain",
|
"resizeMode": "contain",
|
||||||
"backgroundColor": "#ffffff"
|
"backgroundColor": "#8c8c8c"
|
||||||
},
|
},
|
||||||
"ios": {
|
"ios": {
|
||||||
"supportsTablet": true
|
"supportsTablet": false,
|
||||||
|
"bundleIdentifier": "dev.reversed.tvcontrol"
|
||||||
},
|
},
|
||||||
"android": {
|
"android": {
|
||||||
"adaptiveIcon": {
|
"adaptiveIcon": {
|
||||||
"foregroundImage": "./assets/adaptive-icon.png",
|
"foregroundImage": "./assets/adaptive-icon.png",
|
||||||
"backgroundColor": "#ffffff"
|
"backgroundColor": "#8c8c8c"
|
||||||
},
|
},
|
||||||
|
"package": "dev.reversed.tvcontrol",
|
||||||
"edgeToEdgeEnabled": true,
|
"edgeToEdgeEnabled": true,
|
||||||
"predictiveBackGestureEnabled": false
|
"predictiveBackGestureEnabled": false
|
||||||
},
|
},
|
||||||
"web": {
|
"web": {
|
||||||
"favicon": "./assets/favicon.png"
|
"favicon": "./assets/favicon.png"
|
||||||
|
},
|
||||||
|
"extra": {
|
||||||
|
"eas": {
|
||||||
|
"projectId": "4e663169-e9d3-4ca5-95e3-4c2477fcd072"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 584 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 584 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 584 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 584 KiB |
30
mobile/eas.json
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"cli": {
|
||||||
|
"version": ">= 16.0.0",
|
||||||
|
"appVersionSource": "local"
|
||||||
|
},
|
||||||
|
"build": {
|
||||||
|
"development": {
|
||||||
|
"developmentClient": true,
|
||||||
|
"distribution": "internal",
|
||||||
|
"android": {
|
||||||
|
"buildType": "apk"
|
||||||
|
},
|
||||||
|
"ios": {
|
||||||
|
"simulator": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"preview": {
|
||||||
|
"distribution": "internal",
|
||||||
|
"android": {
|
||||||
|
"buildType": "apk"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"production": {
|
||||||
|
"autoIncrement": true,
|
||||||
|
"android": {
|
||||||
|
"buildType": "app-bundle"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -39,7 +39,7 @@ export function SettingsPage() {
|
|||||||
.finally(() => setLoading(false));
|
.finally(() => setLoading(false));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// ── Pick a portrait image from the gallery
|
// ── Pick a landscape image from the gallery
|
||||||
const handlePickBackground = async () => {
|
const handlePickBackground = async () => {
|
||||||
const { granted } = await ImagePicker.requestMediaLibraryPermissionsAsync();
|
const { granted } = await ImagePicker.requestMediaLibraryPermissionsAsync();
|
||||||
if (!granted) {
|
if (!granted) {
|
||||||
@@ -57,9 +57,9 @@ export function SettingsPage() {
|
|||||||
|
|
||||||
const asset = result.assets[0];
|
const asset = result.assets[0];
|
||||||
|
|
||||||
// Enforce portrait orientation (height must exceed width)
|
// Enforce landscape orientation (width must exceed height)
|
||||||
if (asset.width >= asset.height) {
|
if (asset.width <= asset.height) {
|
||||||
setStatus("Please pick a portrait image (height must be greater than width).");
|
setStatus("Please pick a landscape image (width must be greater than height).");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,7 +150,7 @@ export function SettingsPage() {
|
|||||||
<View style={styles.section}>
|
<View style={styles.section}>
|
||||||
<Text style={shared.sectionLabel}>TV Background</Text>
|
<Text style={shared.sectionLabel}>TV Background</Text>
|
||||||
<Text style={shared.hint}>
|
<Text style={shared.hint}>
|
||||||
Displayed behind all content on the TV. Must be a portrait image (taller than wide).
|
Displayed behind all content on the TV. Must be a landscape image (wider than tall).
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
{loading ? (
|
{loading ? (
|
||||||
@@ -246,7 +246,7 @@ const styles = StyleSheet.create({
|
|||||||
},
|
},
|
||||||
preview: {
|
preview: {
|
||||||
width: "100%",
|
width: "100%",
|
||||||
aspectRatio: 9 / 16,
|
aspectRatio: 16 / 9,
|
||||||
},
|
},
|
||||||
pendingBadge: {
|
pendingBadge: {
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
@@ -266,7 +266,7 @@ const styles = StyleSheet.create({
|
|||||||
},
|
},
|
||||||
emptyPreview: {
|
emptyPreview: {
|
||||||
width: "100%",
|
width: "100%",
|
||||||
aspectRatio: 9 / 16,
|
aspectRatio: 16 / 9,
|
||||||
borderRadius: 14,
|
borderRadius: 14,
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
borderColor: colors.border,
|
borderColor: colors.border,
|
||||||
|
|||||||