From 9b418a11c26988fa56e384848f4ad4037471c7ae Mon Sep 17 00:00:00 2001 From: space Date: Sun, 1 Mar 2026 11:23:48 +0100 Subject: [PATCH] update --- functions/control/main.py | 44 +++++---- mobile/package.json | 2 + mobile/pnpm-lock.yaml | 188 +++++++++++++++++++++++++++++++++++++ mobile/src/pages/index.tsx | 148 ++++++++++++++++++++++++++--- tv/src/App.tsx | 72 ++++++++++++-- 5 files changed, 414 insertions(+), 40 deletions(-) diff --git a/functions/control/main.py b/functions/control/main.py index 5e30418..2ba4b44 100644 --- a/functions/control/main.py +++ b/functions/control/main.py @@ -1,38 +1,44 @@ import json +DEFAULT_STATE = {"text": "", "image_url": ""} + +def _read_state(): + try: + with open("/app/state.json", "r") as f: + return json.load(f) + except (FileNotFoundError, json.JSONDecodeError): + return dict(DEFAULT_STATE) + +def _write_state(state): + with open("/app/state.json", "w") as f: + json.dump(state, f) + def main(args): route = args.get("route") - - # Verify file exists, if not create it with default content - try: - with open("/app/state.json", "r") as f: - pass - except FileNotFoundError: - with open("/app/state.json", "w") as f: - json.dump({"text": ""}, f) - f.flush() - + body = args.get("body") body = json.loads(body) if body else {} - + if route.startswith("push"): if route == "push_text": - with open("/app/state.json", "r") as f: - current = json.load(f) + current = _read_state() current["text"] = body.get("text", "") - with open("/app/state.json", "w") as f: - json.dump(current, f) + _write_state(current) + return {"status": "success"} + elif route == "push_image_url": + current = _read_state() + current["image_url"] = body.get("image_url", "") + _write_state(current) return {"status": "success"} else: return {"status": "error", "message": "Unknown push route"} elif route.startswith("pull"): + current = _read_state() if route == "pull_text": - with open("/app/state.json", "r") as f: - current = json.load(f) return {"status": "success", "text": current.get("text", "")} + elif route == "pull_image_url": + return {"status": "success", "image_url": current.get("image_url", "")} elif route == "pull_full": - with open("/app/state.json", "r") as f: - current = json.load(f) return {"status": "success", "state": current} else: return {"status": "error", "message": "Unknown pull route"} \ No newline at end of file diff --git a/mobile/package.json b/mobile/package.json index 0913b95..030d105 100644 --- a/mobile/package.json +++ b/mobile/package.json @@ -10,7 +10,9 @@ }, "dependencies": { "expo": "~54.0.33", + "expo-image-picker": "^55.0.10", "expo-status-bar": "~3.0.9", + "minio": "^8.0.7", "react": "19.1.0", "react-native": "0.81.5" }, diff --git a/mobile/pnpm-lock.yaml b/mobile/pnpm-lock.yaml index 12622b0..ab7d429 100644 --- a/mobile/pnpm-lock.yaml +++ b/mobile/pnpm-lock.yaml @@ -11,9 +11,15 @@ importers: expo: specifier: ~54.0.33 version: 54.0.33(@babel/core@7.29.0)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + expo-image-picker: + specifier: ^55.0.10 + version: 55.0.10(expo@54.0.33(@babel/core@7.29.0)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)) expo-status-bar: specifier: ~3.0.9 version: 3.0.9(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + minio: + specifier: ^8.0.7 + version: 8.0.7 react: specifier: 19.1.0 version: 19.1.0 @@ -894,6 +900,9 @@ packages: async-limiter@1.0.1: resolution: {integrity: sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==} + async@3.2.6: + resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} + babel-jest@29.7.0: resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -981,6 +990,9 @@ packages: resolution: {integrity: sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==} engines: {node: '>=0.6'} + block-stream2@2.1.0: + resolution: {integrity: sha512-suhjmLI57Ewpmq00qaygS8UgEq2ly2PCItenIyhMqVjo4t4pGzqMvfgJuX8iWTeSDdfSSqS6j38fL4ToNL7Pfg==} + bplist-creator@0.1.0: resolution: {integrity: sha512-sXaHZicyEEmY86WyueLTQesbeoH/mquvarJaQNbjuOQO+7gbFcDEWqKmcWA4cOTLzFlfgvkiVxolk1k5bBIpmg==} @@ -1006,6 +1018,9 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} + browser-or-node@2.1.1: + resolution: {integrity: sha512-8CVjaLJGuSKMVTxJ2DpBl5XnlNDiT4cQFeuCJJrvJmts9YrTZDizTX7PjC2s6W4x+MBGZeEY6dGMrF04/6Hgqg==} + browserslist@4.28.1: resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} @@ -1014,6 +1029,10 @@ packages: bser@2.1.1: resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} + buffer-crc32@1.0.0: + resolution: {integrity: sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==} + engines: {node: '>=8.0.0'} + buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} @@ -1159,6 +1178,10 @@ packages: supports-color: optional: true + decode-uri-component@0.2.2: + resolution: {integrity: sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==} + engines: {node: '>=0.10'} + deep-extend@0.6.0: resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} engines: {node: '>=4.0.0'} @@ -1250,6 +1273,9 @@ packages: resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} engines: {node: '>=6'} + eventemitter3@5.0.4: + resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} + expo-asset@12.0.12: resolution: {integrity: sha512-CsXFCQbx2fElSMn0lyTdRIyKlSXOal6ilLJd+yeZ6xaC7I9AICQgscY5nj0QcwgA+KYYCCEQEBndMsmj7drOWQ==} peerDependencies: @@ -1276,6 +1302,16 @@ packages: react: '*' react-native: '*' + expo-image-loader@55.0.0: + resolution: {integrity: sha512-NOjp56wDrfuA5aiNAybBIjqIn1IxKeGJ8CECWZncQ/GzjZfyTYAHTCyeApYkdKkMBLHINzI4BbTGSlbCa0fXXQ==} + peerDependencies: + expo: '*' + + expo-image-picker@55.0.10: + resolution: {integrity: sha512-uspDWjNBNjUD//MLzu7oEtT9cuNgW5pd6HxPfAJffRIdkdCUCYxLYXRp5pfB6JtW640eCdvm2QEQrQC172Y62g==} + peerDependencies: + expo: '*' + expo-keep-awake@15.0.8: resolution: {integrity: sha512-YK9M1VrnoH1vLJiQzChZgzDvVimVoriibiDIFLbQMpjYBnvyfUeHJcin/Gx1a+XgupNXy92EQJLgI/9ZuXajYQ==} peerDependencies: @@ -1325,6 +1361,13 @@ packages: fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + fast-xml-builder@1.0.0: + resolution: {integrity: sha512-fpZuDogrAgnyt9oDDz+5DBz0zgPdPZz6D4IR7iESxRXElrlGTRkHJ9eEt+SACRJwT0FNFrt71DFQIUFBJfX/uQ==} + + fast-xml-parser@5.4.1: + resolution: {integrity: sha512-BQ30U1mKkvXQXXkAGcuyUA/GA26oEB7NzOtsxCDtyu62sjGw5QraKFhx2Em3WQNjPw9PG6MQ9yuIIgkSDfGu5A==} + hasBin: true + fb-watchman@2.0.2: resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} @@ -1341,6 +1384,10 @@ packages: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} + filter-obj@1.1.0: + resolution: {integrity: sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==} + engines: {node: '>=0.10.0'} + finalhandler@1.1.2: resolution: {integrity: sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==} engines: {node: '>= 0.8'} @@ -1472,6 +1519,10 @@ packages: invariant@2.2.4: resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==} + ipaddr.js@2.3.0: + resolution: {integrity: sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg==} + engines: {node: '>= 10'} + is-core-module@2.16.1: resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} engines: {node: '>= 0.4'} @@ -1669,6 +1720,9 @@ packages: lodash.throttle@4.1.1: resolution: {integrity: sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==} + lodash@4.17.23: + resolution: {integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==} + log-symbols@2.2.0: resolution: {integrity: sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==} engines: {node: '>=4'} @@ -1858,6 +1912,10 @@ packages: minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + minio@8.0.7: + resolution: {integrity: sha512-E737MgufW8CeQAsTAtnEMrxZ9scMSf29kkhZoXzDTKj/Jszzo2SfeZUH9wbDQH2Rsq6TCtl/yQL0+XdVKZansQ==} + engines: {node: ^16 || ^18 || >=20} + minipass@7.1.3: resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} engines: {node: '>=16 || 14 >=14.17'} @@ -2069,6 +2127,10 @@ packages: resolution: {integrity: sha512-Uu7ii+FQy4Qf82G4xu7ShHhjhGahEpCWc3x8UavY3CTcWV+ufmmCtwkr7ZKsX42jdL0kr1B5FKUeqJvAn51jzQ==} hasBin: true + query-string@7.1.3: + resolution: {integrity: sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==} + engines: {node: '>=6'} + queue@6.0.2: resolution: {integrity: sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==} @@ -2111,6 +2173,10 @@ packages: resolution: {integrity: sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==} engines: {node: '>=0.10.0'} + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + regenerate-unicode-properties@10.2.2: resolution: {integrity: sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==} engines: {node: '>=4'} @@ -2250,6 +2316,10 @@ packages: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} + split-on-first@1.1.0: + resolution: {integrity: sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==} + engines: {node: '>=6'} + sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} @@ -2276,10 +2346,23 @@ packages: resolution: {integrity: sha512-uyQK/mx5QjHun80FLJTfaWE7JtwfRMKBLkMne6udYOmvH0CawotVa7TfgYHzAnpphn4+TweIx1QKMnRIbipmUg==} engines: {node: '>= 0.10.0'} + stream-chain@2.2.5: + resolution: {integrity: sha512-1TJmBx6aSWqZ4tx7aTpBDXK0/e2hhcNSTV8+CbFJtDjbb+I1mZ8lHit0Grw9GRT+6JbIrrDd8esncgBi8aBXGA==} + + stream-json@1.9.1: + resolution: {integrity: sha512-uWkjJ+2Nt/LO9Z/JyKZbMusL8Dkh97uUBTv3AJQ74y07lVahLY4eEFsPsE97pxYBwr8nnjMAIch5eqI0gPShyw==} + + strict-uri-encode@2.0.0: + resolution: {integrity: sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==} + engines: {node: '>=4'} + string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + strip-ansi@5.2.0: resolution: {integrity: sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==} engines: {node: '>=6'} @@ -2292,6 +2375,9 @@ packages: resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} engines: {node: '>=0.10.0'} + strnum@2.2.0: + resolution: {integrity: sha512-Y7Bj8XyJxnPAORMZj/xltsfo55uOiyHcU2tnAVzHUnSJR/KsEX+9RoDeXEnsXtl/CX4fAcrt64gZ13aGaWPeBg==} + structured-headers@0.4.1: resolution: {integrity: sha512-0MP/Cxx5SzeeZ10p/bZI0S6MpgD+yxAhi1BOQ34jgnMXsCq3j1t6tQnZu+KdlL7dvJTLT3g9xN8tl10TqgFMcg==} @@ -2347,6 +2433,9 @@ packages: throat@5.0.0: resolution: {integrity: sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==} + through2@4.0.2: + resolution: {integrity: sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==} + tinyglobby@0.2.15: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} @@ -2415,6 +2504,9 @@ packages: peerDependencies: browserslist: '>= 4.21.0' + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + utils-merge@1.0.1: resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} engines: {node: '>= 0.4.0'} @@ -2513,6 +2605,10 @@ packages: resolution: {integrity: sha512-eLTh0kA8uHceqesPqSE+VvO1CDDJWMwlQfB6LuN6T8w6MaDJ8Txm8P7s5cHD0miF0V+GGTZrDQfxPZQVsur33w==} engines: {node: '>=4.0.0'} + xml2js@0.6.2: + resolution: {integrity: sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==} + engines: {node: '>=4.0.0'} + xmlbuilder@11.0.1: resolution: {integrity: sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==} engines: {node: '>=4.0'} @@ -3786,6 +3882,8 @@ snapshots: async-limiter@1.0.1: {} + async@3.2.6: {} + babel-jest@29.7.0(@babel/core@7.29.0): dependencies: '@babel/core': 7.29.0 @@ -3927,6 +4025,10 @@ snapshots: big-integer@1.6.52: {} + block-stream2@2.1.0: + dependencies: + readable-stream: 3.6.2 + bplist-creator@0.1.0: dependencies: stream-buffers: 2.2.0 @@ -3956,6 +4058,8 @@ snapshots: dependencies: fill-range: 7.1.1 + browser-or-node@2.1.1: {} + browserslist@4.28.1: dependencies: baseline-browser-mapping: 2.10.0 @@ -3968,6 +4072,8 @@ snapshots: dependencies: node-int64: 0.4.0 + buffer-crc32@1.0.0: {} + buffer-from@1.1.2: {} buffer@5.7.1: @@ -4107,6 +4213,8 @@ snapshots: dependencies: ms: 2.1.3 + decode-uri-component@0.2.2: {} + deep-extend@0.6.0: {} deepmerge@4.3.1: {} @@ -4161,6 +4269,8 @@ snapshots: event-target-shim@5.0.1: {} + eventemitter3@5.0.4: {} + expo-asset@12.0.12(expo@54.0.33(@babel/core@7.29.0)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): dependencies: '@expo/image-utils': 0.8.12 @@ -4192,6 +4302,15 @@ snapshots: react: 19.1.0 react-native: 0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0) + expo-image-loader@55.0.0(expo@54.0.33(@babel/core@7.29.0)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)): + dependencies: + expo: 54.0.33(@babel/core@7.29.0)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + + expo-image-picker@55.0.10(expo@54.0.33(@babel/core@7.29.0)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)): + dependencies: + expo: 54.0.33(@babel/core@7.29.0)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + expo-image-loader: 55.0.0(expo@54.0.33(@babel/core@7.29.0)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)) + expo-keep-awake@15.0.8(expo@54.0.33(@babel/core@7.29.0)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react@19.1.0): dependencies: expo: 54.0.33(@babel/core@7.29.0)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) @@ -4256,6 +4375,13 @@ snapshots: fast-json-stable-stringify@2.1.0: {} + fast-xml-builder@1.0.0: {} + + fast-xml-parser@5.4.1: + dependencies: + fast-xml-builder: 1.0.0 + strnum: 2.2.0 + fb-watchman@2.0.2: dependencies: bser: 2.1.1 @@ -4268,6 +4394,8 @@ snapshots: dependencies: to-regex-range: 5.0.1 + filter-obj@1.1.0: {} + finalhandler@1.1.2: dependencies: debug: 2.6.9 @@ -4393,6 +4521,8 @@ snapshots: dependencies: loose-envify: 1.4.0 + ipaddr.js@2.3.0: {} + is-core-module@2.16.1: dependencies: hasown: 2.0.2 @@ -4584,6 +4714,8 @@ snapshots: lodash.throttle@4.1.1: {} + lodash@4.17.23: {} + log-symbols@2.2.0: dependencies: chalk: 2.4.2 @@ -4994,6 +5126,22 @@ snapshots: minimist@1.2.8: {} + minio@8.0.7: + dependencies: + async: 3.2.6 + block-stream2: 2.1.0 + browser-or-node: 2.1.1 + buffer-crc32: 1.0.0 + eventemitter3: 5.0.4 + fast-xml-parser: 5.4.1 + ipaddr.js: 2.3.0 + lodash: 4.17.23 + mime-types: 2.1.35 + query-string: 7.1.3 + stream-json: 1.9.1 + through2: 4.0.2 + xml2js: 0.6.2 + minipass@7.1.3: {} minizlib@3.1.0: @@ -5169,6 +5317,13 @@ snapshots: qrcode-terminal@0.11.0: {} + query-string@7.1.3: + dependencies: + decode-uri-component: 0.2.2 + filter-obj: 1.1.0 + split-on-first: 1.1.0 + strict-uri-encode: 2.0.0 + queue@6.0.2: dependencies: inherits: 2.0.4 @@ -5248,6 +5403,12 @@ snapshots: react@19.1.0: {} + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + regenerate-unicode-properties@10.2.2: dependencies: regenerate: 1.4.2 @@ -5380,6 +5541,8 @@ snapshots: source-map@0.6.1: {} + split-on-first@1.1.0: {} + sprintf-js@1.0.3: {} stack-utils@2.0.6: @@ -5398,12 +5561,24 @@ snapshots: stream-buffers@2.2.0: {} + stream-chain@2.2.5: {} + + stream-json@1.9.1: + dependencies: + stream-chain: 2.2.5 + + strict-uri-encode@2.0.0: {} + string-width@4.2.3: dependencies: emoji-regex: 8.0.0 is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + strip-ansi@5.2.0: dependencies: ansi-regex: 4.1.1 @@ -5414,6 +5589,8 @@ snapshots: strip-json-comments@2.0.1: {} + strnum@2.2.0: {} + structured-headers@0.4.1: {} sucrase@3.35.1: @@ -5481,6 +5658,10 @@ snapshots: throat@5.0.0: {} + through2@4.0.2: + dependencies: + readable-stream: 3.6.2 + tinyglobby@0.2.15: dependencies: fdir: 6.5.0(picomatch@4.0.3) @@ -5527,6 +5708,8 @@ snapshots: escalade: 3.2.0 picocolors: 1.1.1 + util-deprecate@1.0.2: {} + utils-merge@1.0.1: {} uuid@7.0.3: {} @@ -5592,6 +5775,11 @@ snapshots: sax: 1.4.4 xmlbuilder: 11.0.1 + xml2js@0.6.2: + dependencies: + sax: 1.4.4 + xmlbuilder: 11.0.1 + xmlbuilder@11.0.1: {} xmlbuilder@15.1.1: {} diff --git a/mobile/src/pages/index.tsx b/mobile/src/pages/index.tsx index 394ff79..4a757c7 100644 --- a/mobile/src/pages/index.tsx +++ b/mobile/src/pages/index.tsx @@ -1,9 +1,24 @@ +import * as ImagePicker from "expo-image-picker"; +import * as Minio from "minio"; import { useState } from "react"; -import { Button, StyleSheet, Text, View } from "react-native"; -import { TextInput } from "react-native"; +import { ActivityIndicator, Button, Image, StyleSheet, Text, TextInput, View } from "react-native"; + +const minioClient = new Minio.Client({ + endPoint: "content2.reversed.dev", + port: 443, + useSSL: true, + accessKey: "tv-control", + secretKey: "0gEjOlnVAECrqJx5Nd77y7Hhouc5faVf0WttjcRH", +}); + +const bucket = "tv-control"; +const BASE_URL = + "https://shsf-api.reversed.dev/api/exec/15/1c44d8b5-4065-4a54-b259-748561021329"; export function IndexPage() { const [text, setText] = useState(""); + const [uploading, setUploading] = useState(false); + const [previewUri, setPreviewUri] = useState(null); const handleSend = () => { if (text.trim() === "") { @@ -11,14 +26,12 @@ export function IndexPage() { return; } - fetch("https://shsf-api.reversed.dev/api/exec/15/1c44d8b5-4065-4a54-b259-748561021329/push_text", { + fetch(`${BASE_URL}/push_text`, { method: "POST", - headers: { - "Content-Type": "application/json", - }, + headers: { "Content-Type": "application/json" }, body: JSON.stringify({ text }), }) - .then((response) => response.json()) + .then((r) => r.json()) .then((data) => { if (data.status === "success") { alert("Text sent successfully!"); @@ -32,18 +45,100 @@ export function IndexPage() { }); }; + const handlePullText = () => { + fetch(`${BASE_URL}/pull_text`) + .then((r) => r.json()) + .then((data) => { + alert(data.text ?? "No text received."); + }) + .catch((error) => { + console.error("Error pulling text:", error); + alert("Error pulling text."); + }); + }; + + const handleTakePhoto = async () => { + const { granted } = await ImagePicker.requestCameraPermissionsAsync(); + if (!granted) { + alert("Camera permission is required to take photos."); + return; + } + + const result = await ImagePicker.launchCameraAsync({ + mediaTypes: "images", + quality: 0.8, + }); + + if (result.canceled) return; + + const asset = result.assets[0]; + setPreviewUri(asset.uri); + setUploading(true); + + try { + const fileName = `tv-image-${Date.now()}.jpg`; + + // Get a presigned URL for PUT upload (pure signing — no HTTP call) + const presignedUrl = await minioClient.presignedPutObject( + bucket, + fileName, + 60 * 60 + ); + + // Upload the image blob using native fetch + const blob = await fetch(asset.uri).then((r) => r.blob()); + await fetch(presignedUrl, { + method: "PUT", + body: blob, + headers: { "Content-Type": "image/jpeg" }, + }); + + const imageUrl = `https://content2.reversed.dev/${bucket}/${fileName}`; + + // Push the public URL to the server + await fetch(`${BASE_URL}/push_image_url`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ image_url: imageUrl }), + }); + + alert("Image uploaded and sent to TV!"); + } catch (error) { + console.error("Upload error:", error); + alert("Failed to upload image."); + } finally { + setUploading(false); + } + }; + return ( - Update Title - You typed "{text}" + TV Control - + + Text - ) : ( -
-

- Fullscreen Active, yay -

+
+ {imageUrl && ( + TV display + )} + {text && ( +

+ {text} +

+ )}
)}