diff --git a/android/app/build.gradle b/android/app/build.gradle index 1f0f98bb..defe2d78 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -87,8 +87,8 @@ android { applicationId 'com.thoulee.jointplayer' minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 5010004 - versionName "5.10.4" + versionCode 5011000 + versionName "5.11.0" } signingConfigs { debug { diff --git a/ios/jointplayer.xcodeproj/project.pbxproj b/ios/jointplayer.xcodeproj/project.pbxproj index 16e41861..f31004ee 100644 --- a/ios/jointplayer.xcodeproj/project.pbxproj +++ b/ios/jointplayer.xcodeproj/project.pbxproj @@ -480,7 +480,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = 5010004; + CURRENT_PROJECT_VERSION = 5011000; ENABLE_BITCODE = NO; INFOPLIST_FILE = jointplayer/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -507,7 +507,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = 5010004; + CURRENT_PROJECT_VERSION = 5011000; INFOPLIST_FILE = jointplayer/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", diff --git a/ios/jointplayer/Info.plist b/ios/jointplayer/Info.plist index 951f5de4..b2bebc25 100644 --- a/ios/jointplayer/Info.plist +++ b/ios/jointplayer/Info.plist @@ -17,11 +17,11 @@ CFBundlePackageType APPL CFBundleShortVersionString - 5.10.4 + 5.11.0 CFBundleSignature ???? CFBundleVersion - 5010004 + 5011000 LSRequiresIPhoneOS NSAppTransportSecurity diff --git a/ios/jointplayerTests/Info.plist b/ios/jointplayerTests/Info.plist index 4def5b59..c77e8ba7 100644 --- a/ios/jointplayerTests/Info.plist +++ b/ios/jointplayerTests/Info.plist @@ -15,10 +15,10 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 5.10.4 + 5.11.0 CFBundleSignature ???? CFBundleVersion - 5010004 + 5011000 diff --git a/package.json b/package.json index 86e7ecfd..dd251ec1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jointplayer", - "version": "5.10.4", + "version": "5.11.0", "private": true, "homepage": "https://github.com/thoulee21/joint-player", "displayName": "Joint Player", diff --git a/src/components/UpdateChecker.tsx b/src/components/UpdateChecker.tsx index 3c6ace2f..b3ee2ebc 100644 --- a/src/components/UpdateChecker.tsx +++ b/src/components/UpdateChecker.tsx @@ -1,128 +1,150 @@ import * as Updates from 'expo-updates'; import React, { useCallback } from 'react'; -import { Alert, ToastAndroid } from 'react-native'; +import { Alert, Linking, ToastAndroid } from 'react-native'; import HapticFeedback, { HapticFeedbackTypes } from 'react-native-haptic-feedback'; import { ActivityIndicator, List, useTheme } from 'react-native-paper'; import RNRestart from 'react-native-restart'; +import useSWR from 'swr'; +import packageData from '../../package.json'; import { useAppSelector } from '../hook'; import { selectDevModeEnabled } from '../redux/slices'; +import type { Main } from '../types/latestRelease'; export const UpdateChecker = () => { - const appTheme = useTheme(); - const isDev = useAppSelector(selectDevModeEnabled); + const appTheme = useTheme(); + const isDev = useAppSelector(selectDevModeEnabled); - const { - currentlyRunning, - isUpdatePending, - isChecking, - isDownloading, - lastCheckForUpdateTimeSinceRestart: lastCheck, - availableUpdate, - } = Updates.useUpdates(); + const userRepo = packageData.homepage.split('/').slice(-2).join('/'); + const { data } = useSWR
(`https://api.github.com/repos/${userRepo}/releases/latest`); + const latestRelease = data?.tag_name; + const isLatest = latestRelease === packageData.version; - const showCurrent = useCallback(() => { - if (isDev) { - HapticFeedback.trigger(HapticFeedbackTypes.effectClick); - if (availableUpdate) { - Alert.alert( - 'Available update info', - JSON.stringify(availableUpdate, null, 2) - ); - } else { - Alert.alert( - 'Current update info', - JSON.stringify(currentlyRunning, null, 2) - ); - } - } - }, [availableUpdate, currentlyRunning, isDev]); - - const fetchUpdateAndRestart = async () => { - try { - await Updates.fetchUpdateAsync(); - RNRestart.Restart(); - } catch (err) { - ToastAndroid.show( - `Error updating app: ${JSON.stringify(err)}`, - ToastAndroid.LONG - ); - } - }; + const { + currentlyRunning, + isUpdatePending, + isChecking, + isDownloading, + lastCheckForUpdateTimeSinceRestart: lastCheck, + availableUpdate, + } = Updates.useUpdates(); - const performUpdateAlert = useCallback(() => { + const showCurrent = useCallback(() => { + if (isDev) { + HapticFeedback.trigger(HapticFeedbackTypes.effectClick); + if (availableUpdate) { Alert.alert( - availableUpdate?.createdAt - ? `New update available:\n${availableUpdate?.createdAt.toLocaleString()}` - : 'New update available', - 'Do you want to update now?', - [ - { text: 'Cancel' }, - { - text: 'OK', - onPress: isUpdatePending - ? () => RNRestart.Restart() - : fetchUpdateAndRestart, - }, - ] + 'Available update info', + JSON.stringify(availableUpdate, null, 2) ); - }, [availableUpdate?.createdAt, isUpdatePending]); + } else { + Alert.alert( + 'Current update info', + JSON.stringify(currentlyRunning, null, 2) + ); + } + } + }, [availableUpdate, currentlyRunning, isDev]); - const checkForUpdate = async () => { - try { - const updateCheckRes = await Updates.checkForUpdateAsync(); + const fetchUpdateAndRestart = async () => { + try { + await Updates.fetchUpdateAsync(); + RNRestart.Restart(); + } catch (err) { + ToastAndroid.show( + `Error updating app: ${JSON.stringify(err)}`, + ToastAndroid.LONG + ); + } + }; - if (updateCheckRes.isAvailable) { - performUpdateAlert(); - } else { - ToastAndroid.show('No updates available', ToastAndroid.SHORT); - } - } catch (err) { - Alert.alert( - 'Error checking for updates', - JSON.stringify(err, null, 2) - ); - } - }; + const performUpdateAlert = useCallback(() => { + Alert.alert( + availableUpdate?.createdAt + ? `New update available:\n${availableUpdate?.createdAt.toLocaleString()}` + : 'New update available', + 'Do you want to update now?', + [ + { text: 'Cancel' }, + { + text: 'OK', + onPress: isUpdatePending + ? () => RNRestart.Restart() + : fetchUpdateAndRestart, + }, + ] + ); + }, [availableUpdate?.createdAt, isUpdatePending]); - const handleUpdatePress = isUpdatePending ? performUpdateAlert : checkForUpdate; - const description = isDownloading - ? 'Downloading update...' - : isUpdatePending - ? 'Update is pending...' - : isChecking - ? 'Checking for updates...' - : `Last checked: ${lastCheck?.toLocaleString() || 'Never'}`; + const checkForUpdate = async () => { + try { + const updateCheckRes = await Updates.checkForUpdateAsync(); - const renderUpdateIcon = useCallback((props: any) => { - const updateIcon = isUpdatePending - ? 'progress-download' - : 'cloud-download-outline'; + if (updateCheckRes.isAvailable) { + performUpdateAlert(); + } else { + ToastAndroid.show('No updates available', ToastAndroid.SHORT); + } + } catch (err) { + Alert.alert( + 'Error checking for updates', + JSON.stringify(err, null, 2) + ); + } + }; - return ( - - ); - }, [appTheme.colors.primary, isUpdatePending]); + const handleUpdatePress = isLatest ? + isUpdatePending ? performUpdateAlert : checkForUpdate + : () => Alert.alert( + 'New version available', + `Your version: ${packageData.version}\nLatest version: ${latestRelease}`, + [ + { text: 'Cancel' }, + { + text: 'OK', onPress: () => { + Linking.openURL(data?.assets_url || 'https://github.com'); + } + } + ] + ); - const isProcessing = isChecking || isDownloading; - const renderActivityIndicator = useCallback((props: any) => ( - isProcessing ? : null - ), [isProcessing]); + const description = isDownloading + ? 'Downloading update...' + : isUpdatePending + ? 'Update is pending...' + : isChecking + ? 'Checking for updates...' + : `Last checked: ${lastCheck?.toLocaleString() || 'Never'}`; + + const renderUpdateIcon = useCallback((props: any) => { + const updateIcon = isUpdatePending + ? 'progress-download' + : 'cloud-download-outline'; return ( - + ); + }, [appTheme.colors.primary, isUpdatePending]); + + const isProcessing = isChecking || isDownloading; + const renderActivityIndicator = useCallback((props: any) => ( + isProcessing ? : null + ), [isProcessing]); + + return ( + + ); }; diff --git a/src/types/latestRelease.d.ts b/src/types/latestRelease.d.ts new file mode 100644 index 00000000..855d1826 --- /dev/null +++ b/src/types/latestRelease.d.ts @@ -0,0 +1,58 @@ +export interface Main { + url: string; + assets_url: string; + upload_url: string; + html_url: string; + id: number; + author: Author; + node_id: string; + tag_name: string; + target_commitish: string; + name: string; + draft: boolean; + prerelease: boolean; + created_at: Date; + published_at: Date; + assets: Asset[]; + tarball_url: string; + zipball_url: string; + body: string; +} + +export interface Asset { + url: string; + id: number; + node_id: string; + name: string; + label: string; + uploader: Author; + content_type: string; + state: string; + size: number; + download_count: number; + created_at: Date; + updated_at: Date; + browser_download_url: string; +} + +export interface Author { + login: string; + id: number; + node_id: string; + avatar_url: string; + gravatar_id: string; + url: string; + html_url: string; + followers_url: string; + following_url: string; + gists_url: string; + starred_url: string; + subscriptions_url: string; + organizations_url: string; + repos_url: string; + events_url: string; + received_events_url: string; + type: string; + user_view_type: string; + site_admin: boolean; +}