diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml new file mode 100644 index 0000000..c49fd9c --- /dev/null +++ b/.gitea/workflows/ci.yml @@ -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 diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index d5aff2c..56ba576 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -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`). -- **`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 `""`. -- 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`. - The Settings tab is visible in the mobile bottom nav (no `hideInNav`). diff --git a/mobile/app.json b/mobile/app.json index 3c7754f..2a589ad 100644 --- a/mobile/app.json +++ b/mobile/app.json @@ -1,30 +1,37 @@ { "expo": { - "name": "mobile", - "slug": "mobile", + "name": "TV Control", + "slug": "tv-control", "version": "1.0.0", "orientation": "portrait", "icon": "./assets/icon.png", - "userInterfaceStyle": "light", + "userInterfaceStyle": "automatic", "newArchEnabled": true, "splash": { "image": "./assets/splash-icon.png", "resizeMode": "contain", - "backgroundColor": "#ffffff" + "backgroundColor": "#8c8c8c" }, "ios": { - "supportsTablet": true + "supportsTablet": false, + "bundleIdentifier": "dev.reversed.tvcontrol" }, "android": { "adaptiveIcon": { "foregroundImage": "./assets/adaptive-icon.png", - "backgroundColor": "#ffffff" + "backgroundColor": "#8c8c8c" }, + "package": "dev.reversed.tvcontrol", "edgeToEdgeEnabled": true, "predictiveBackGestureEnabled": false }, "web": { "favicon": "./assets/favicon.png" + }, + "extra": { + "eas": { + "projectId": "4e663169-e9d3-4ca5-95e3-4c2477fcd072" + } } } } diff --git a/mobile/assets/adaptive-icon.png b/mobile/assets/adaptive-icon.png index 03d6f6b..caeef57 100644 Binary files a/mobile/assets/adaptive-icon.png and b/mobile/assets/adaptive-icon.png differ diff --git a/mobile/assets/favicon.png b/mobile/assets/favicon.png index e75f697..caeef57 100644 Binary files a/mobile/assets/favicon.png and b/mobile/assets/favicon.png differ diff --git a/mobile/assets/icon.png b/mobile/assets/icon.png index a0b1526..caeef57 100644 Binary files a/mobile/assets/icon.png and b/mobile/assets/icon.png differ diff --git a/mobile/assets/splash-icon.png b/mobile/assets/splash-icon.png index 03d6f6b..caeef57 100644 Binary files a/mobile/assets/splash-icon.png and b/mobile/assets/splash-icon.png differ diff --git a/mobile/eas.json b/mobile/eas.json new file mode 100644 index 0000000..f4b158d --- /dev/null +++ b/mobile/eas.json @@ -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" + } + } + } +} diff --git a/mobile/src/pages/settings.tsx b/mobile/src/pages/settings.tsx index 6c60169..7db40f6 100644 --- a/mobile/src/pages/settings.tsx +++ b/mobile/src/pages/settings.tsx @@ -39,7 +39,7 @@ export function SettingsPage() { .finally(() => setLoading(false)); }, []); - // ── Pick a portrait image from the gallery + // ── Pick a landscape image from the gallery const handlePickBackground = async () => { const { granted } = await ImagePicker.requestMediaLibraryPermissionsAsync(); if (!granted) { @@ -57,9 +57,9 @@ export function SettingsPage() { const asset = result.assets[0]; - // Enforce portrait orientation (height must exceed width) - if (asset.width >= asset.height) { - setStatus("Please pick a portrait image (height must be greater than width)."); + // Enforce landscape orientation (width must exceed height) + if (asset.width <= asset.height) { + setStatus("Please pick a landscape image (width must be greater than height)."); return; } @@ -150,7 +150,7 @@ export function SettingsPage() { TV Background - 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). {loading ? ( @@ -246,7 +246,7 @@ const styles = StyleSheet.create({ }, preview: { width: "100%", - aspectRatio: 9 / 16, + aspectRatio: 16 / 9, }, pendingBadge: { position: "absolute", @@ -266,7 +266,7 @@ const styles = StyleSheet.create({ }, emptyPreview: { width: "100%", - aspectRatio: 9 / 16, + aspectRatio: 16 / 9, borderRadius: 14, borderWidth: 1, borderColor: colors.border,