From 7e67282df997498b3c71fe984ebd8726ce327cd9 Mon Sep 17 00:00:00 2001 From: Adrian Castro <22133246+castdrian@users.noreply.github.com> Date: Sat, 23 Mar 2024 10:23:07 +0100 Subject: [PATCH] feat: update checker --- apps/expo/babel.config.js | 1 + apps/expo/package.json | 2 + apps/expo/src/app/(tabs)/settings.tsx | 43 +++++++-- apps/expo/src/lib/update.ts | 46 +++++++++ pnpm-lock.yaml | 129 ++++++++++++++++++++++++++ 5 files changed, 212 insertions(+), 9 deletions(-) create mode 100644 apps/expo/src/lib/update.ts diff --git a/apps/expo/babel.config.js b/apps/expo/babel.config.js index ccd7e7f..1967743 100644 --- a/apps/expo/babel.config.js +++ b/apps/expo/babel.config.js @@ -4,6 +4,7 @@ module.exports = function (api) { return { presets: ["babel-preset-expo"], plugins: [ + "@babel/plugin-transform-class-static-block", "react-native-reanimated/plugin", [ "module-resolver", diff --git a/apps/expo/package.json b/apps/expo/package.json index e9878b9..f91b050 100644 --- a/apps/expo/package.json +++ b/apps/expo/package.json @@ -23,6 +23,7 @@ "@movie-web/colors": "*", "@movie-web/provider-utils": "*", "@movie-web/tmdb": "*", + "@octokit/rest": "^20.0.2", "@react-native-anywhere/polyfill-base64": "0.0.1-alpha.0", "@react-native-async-storage/async-storage": "1.21.0", "@react-navigation/native": "^6.1.9", @@ -35,6 +36,7 @@ "burnt": "^0.12.2", "class-variance-authority": "^0.7.0", "expo": "~50.0.14", + "expo-application": "~5.8.3", "expo-av": "~13.10.5", "expo-brightness": "~11.8.0", "expo-build-properties": "~0.11.1", diff --git a/apps/expo/src/app/(tabs)/settings.tsx b/apps/expo/src/app/(tabs)/settings.tsx index d22312e..6c0ca15 100644 --- a/apps/expo/src/app/(tabs)/settings.tsx +++ b/apps/expo/src/app/(tabs)/settings.tsx @@ -1,6 +1,10 @@ import type { SelectProps } from "tamagui"; import React, { useEffect, useState } from "react"; +import { TouchableOpacity } from "react-native-gesture-handler"; +import * as Application from "expo-application"; +import * as Linking from "expo-linking"; import { FontAwesome, MaterialIcons } from "@expo/vector-icons"; +import { useToastController } from "@tamagui/toast"; import { Adapt, Label, @@ -17,6 +21,7 @@ import { import type { ThemeStoreOption } from "~/stores/theme"; import ScreenLayout from "~/components/layout/ScreenLayout"; +import { checkForUpdate } from "~/lib/update"; import { getGestureControls, saveGestureControls } from "~/settings"; import { useThemeStore } from "~/stores/theme"; @@ -30,6 +35,7 @@ const themeOptions: ThemeStoreOption[] = [ export default function SettingsScreen() { const [gestureControlsEnabled, setGestureControlsEnabled] = useState(true); + const toastController = useToastController(); useEffect(() => { void getGestureControls().then((enabled) => { @@ -42,13 +48,31 @@ export default function SettingsScreen() { await saveGestureControls(isEnabled); }; + const handleVersionPress = async () => { + const url = await checkForUpdate(); + if (url) { + toastController.show("Update available", { + burntOptions: { preset: "none" }, + native: true, + }); + await Linking.openURL(url); + } else { + toastController.show("No updates available", { + burntOptions: { preset: "none" }, + native: true, + }); + } + }; + return ( - - Player - + + + + + @@ -61,14 +85,15 @@ export default function SettingsScreen() { - - - - - - + + + + v{Application.nativeApplicationVersion} + + + ); } diff --git a/apps/expo/src/lib/update.ts b/apps/expo/src/lib/update.ts new file mode 100644 index 0000000..c303b53 --- /dev/null +++ b/apps/expo/src/lib/update.ts @@ -0,0 +1,46 @@ +import * as Application from "expo-application"; +import { Octokit } from "@octokit/rest"; + +function isVersionHigher(newVersion: string, currentVersion: string): boolean { + const parseVersion = (version: string) => + version + .replace(/^v/, "") + .split(".") + .map((part) => parseInt(part, 10)); + + const newParts = parseVersion(newVersion); + const currentParts = parseVersion(currentVersion); + const maxLength = Math.max(newParts.length, currentParts.length); + + for (let i = 0; i < maxLength; i++) { + const newPart = newParts[i] ?? 0; + const currentPart = currentParts[i] ?? 0; + + if (newPart !== currentPart) { + return newPart > currentPart; + } + } + + return false; +} + +export async function checkForUpdate(): Promise { + const octokit = new Octokit(); + + const res = await octokit.repos + .getLatestRelease({ + owner: "movie-web", + repo: "native-app", + }) + .catch(() => undefined); + + if (!res) return; + + const latestVersion: string = res.data.tag_name; + const currentVersion: string = + Application.nativeApplicationVersion ?? "0.0.0"; + + if (isVersionHigher(latestVersion, currentVersion)) { + return res.data.html_url; + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dde9c5e..e3aa451 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -41,6 +41,9 @@ importers: '@movie-web/tmdb': specifier: '*' version: link:../../packages/tmdb + '@octokit/rest': + specifier: ^20.0.2 + version: 20.0.2 '@react-native-anywhere/polyfill-base64': specifier: 0.0.1-alpha.0 version: 0.0.1-alpha.0 @@ -77,6 +80,9 @@ importers: expo: specifier: ~50.0.14 version: 50.0.14(@babel/core@7.23.9)(@react-native/babel-preset@0.73.21) + expo-application: + specifier: ~5.8.3 + version: 5.8.3(expo@50.0.14) expo-av: specifier: ~13.10.5 version: 13.10.5(expo@50.0.14) @@ -2909,6 +2915,109 @@ packages: rimraf: 3.0.2 dev: false + /@octokit/auth-token@4.0.0: + resolution: {integrity: sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==} + engines: {node: '>= 18'} + dev: false + + /@octokit/core@5.1.0: + resolution: {integrity: sha512-BDa2VAMLSh3otEiaMJ/3Y36GU4qf6GI+VivQ/P41NC6GHcdxpKlqV0ikSZ5gdQsmS3ojXeRx5vasgNTinF0Q4g==} + engines: {node: '>= 18'} + dependencies: + '@octokit/auth-token': 4.0.0 + '@octokit/graphql': 7.0.2 + '@octokit/request': 8.2.0 + '@octokit/request-error': 5.0.1 + '@octokit/types': 12.6.0 + before-after-hook: 2.2.3 + universal-user-agent: 6.0.1 + dev: false + + /@octokit/endpoint@9.0.4: + resolution: {integrity: sha512-DWPLtr1Kz3tv8L0UvXTDP1fNwM0S+z6EJpRcvH66orY6Eld4XBMCSYsaWp4xIm61jTWxK68BrR7ibO+vSDnZqw==} + engines: {node: '>= 18'} + dependencies: + '@octokit/types': 12.6.0 + universal-user-agent: 6.0.1 + dev: false + + /@octokit/graphql@7.0.2: + resolution: {integrity: sha512-OJ2iGMtj5Tg3s6RaXH22cJcxXRi7Y3EBqbHTBRq+PQAqfaS8f/236fUrWhfSn8P4jovyzqucxme7/vWSSZBX2Q==} + engines: {node: '>= 18'} + dependencies: + '@octokit/request': 8.2.0 + '@octokit/types': 12.6.0 + universal-user-agent: 6.0.1 + dev: false + + /@octokit/openapi-types@20.0.0: + resolution: {integrity: sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==} + dev: false + + /@octokit/plugin-paginate-rest@9.2.1(@octokit/core@5.1.0): + resolution: {integrity: sha512-wfGhE/TAkXZRLjksFXuDZdmGnJQHvtU/joFQdweXUgzo1XwvBCD4o4+75NtFfjfLK5IwLf9vHTfSiU3sLRYpRw==} + engines: {node: '>= 18'} + peerDependencies: + '@octokit/core': '5' + dependencies: + '@octokit/core': 5.1.0 + '@octokit/types': 12.6.0 + dev: false + + /@octokit/plugin-request-log@4.0.1(@octokit/core@5.1.0): + resolution: {integrity: sha512-GihNqNpGHorUrO7Qa9JbAl0dbLnqJVrV8OXe2Zm5/Y4wFkZQDfTreBzVmiRfJVfE4mClXdihHnbpyyO9FSX4HA==} + engines: {node: '>= 18'} + peerDependencies: + '@octokit/core': '5' + dependencies: + '@octokit/core': 5.1.0 + dev: false + + /@octokit/plugin-rest-endpoint-methods@10.4.1(@octokit/core@5.1.0): + resolution: {integrity: sha512-xV1b+ceKV9KytQe3zCVqjg+8GTGfDYwaT1ATU5isiUyVtlVAO3HNdzpS4sr4GBx4hxQ46s7ITtZrAsxG22+rVg==} + engines: {node: '>= 18'} + peerDependencies: + '@octokit/core': '5' + dependencies: + '@octokit/core': 5.1.0 + '@octokit/types': 12.6.0 + dev: false + + /@octokit/request-error@5.0.1: + resolution: {integrity: sha512-X7pnyTMV7MgtGmiXBwmO6M5kIPrntOXdyKZLigNfQWSEQzVxR4a4vo49vJjTWX70mPndj8KhfT4Dx+2Ng3vnBQ==} + engines: {node: '>= 18'} + dependencies: + '@octokit/types': 12.6.0 + deprecation: 2.3.1 + once: 1.4.0 + dev: false + + /@octokit/request@8.2.0: + resolution: {integrity: sha512-exPif6x5uwLqv1N1irkLG1zZNJkOtj8bZxuVHd71U5Ftuxf2wGNvAJyNBcPbPC+EBzwYEbBDdSFb8EPcjpYxPQ==} + engines: {node: '>= 18'} + dependencies: + '@octokit/endpoint': 9.0.4 + '@octokit/request-error': 5.0.1 + '@octokit/types': 12.6.0 + universal-user-agent: 6.0.1 + dev: false + + /@octokit/rest@20.0.2: + resolution: {integrity: sha512-Ux8NDgEraQ/DMAU1PlAohyfBBXDwhnX2j33Z1nJNziqAfHi70PuxkFYIcIt8aIAxtRE7KVuKp8lSR8pA0J5iOQ==} + engines: {node: '>= 18'} + dependencies: + '@octokit/core': 5.1.0 + '@octokit/plugin-paginate-rest': 9.2.1(@octokit/core@5.1.0) + '@octokit/plugin-request-log': 4.0.1(@octokit/core@5.1.0) + '@octokit/plugin-rest-endpoint-methods': 10.4.1(@octokit/core@5.1.0) + dev: false + + /@octokit/types@12.6.0: + resolution: {integrity: sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==} + dependencies: + '@octokit/openapi-types': 20.0.0 + dev: false + /@pkgjs/parseargs@0.11.0: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -5881,6 +5990,10 @@ packages: engines: {node: '>=10.0.0'} dev: true + /before-after-hook@2.2.3: + resolution: {integrity: sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==} + dev: false + /better-opn@3.0.2: resolution: {integrity: sha512-aVNobHnJqLiUelTaHat9DZ1qM2w0C0Eym4LPI/3JxOnSokGVdsl1T1kN7TFvsEAD8G47A6VKQ0TVHqbBnYMJlQ==} engines: {node: '>=12.0.0'} @@ -6902,6 +7015,10 @@ packages: prop-types: 15.8.1 dev: false + /deprecation@2.3.1: + resolution: {integrity: sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==} + dev: false + /dequal@2.0.3: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} @@ -7591,6 +7708,14 @@ packages: signal-exit: 3.0.7 strip-final-newline: 2.0.0 + /expo-application@5.8.3(expo@50.0.14): + resolution: {integrity: sha512-IISxzpPX+Xe4ynnwX8yY52T6dm1g9sME1GCj4lvUlrdc5xeTPM6U35x7Wj82V7lLWBaVGe+/Tg9EeKqfylCEwA==} + peerDependencies: + expo: '*' + dependencies: + expo: 50.0.14(@babel/core@7.23.9)(@react-native/babel-preset@0.73.21) + dev: false + /expo-asset@9.0.2(expo@50.0.14): resolution: {integrity: sha512-PzYKME1MgUOoUvwtdzhAyXkjXOXGiSYqGKG/MsXwWr0Ef5wlBaBm2DCO9V6KYbng5tBPFu6hTjoRNil1tBOSow==} dependencies: @@ -13189,6 +13314,10 @@ packages: crypto-random-string: 2.0.0 dev: false + /universal-user-agent@6.0.1: + resolution: {integrity: sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==} + dev: false + /universalify@0.1.2: resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} engines: {node: '>= 4.0.0'}