mirror of
https://github.com/Jellify-Music/App.git
synced 2026-01-06 02:50:30 -06:00
PR try on (#665)
* feat:- PR changes * feat:- PR changes * feat:- OTA * feat:- OTA * fix: tests
This commit is contained in:
32
.github/workflows/publish-ota-update-pr.yml
vendored
Normal file
32
.github/workflows/publish-ota-update-pr.yml
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
name: Publish Over-the-Air Update PR
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'src/**'
|
||||
|
||||
jobs:
|
||||
publish-ota-update:
|
||||
runs-on: macos-15
|
||||
steps:
|
||||
- name: 🛒 Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
token: ${{ secrets.SIGNING_REPO_PAT }}
|
||||
|
||||
- name: 🖥 Setup Node 20
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- name: 🧵 Run yarn
|
||||
run: yarn install --network-concurrency 1
|
||||
|
||||
- name: 👩💻 Configure Git
|
||||
run: |
|
||||
git config --global user.email "violet@cosmonautical.cloud"
|
||||
git config --global user.name "anultravioletaurora"
|
||||
|
||||
- name: 🤖 Publish Android Update
|
||||
run: yarn sendOTA:PR ${{ github.event.pull_request.number }}
|
||||
env:
|
||||
SIGNING_REPO_PAT: ${{ secrets.SIGNING_REPO_PAT }}
|
||||
@@ -8,6 +8,8 @@ jest.mock('react-native-nitro-ota', () => ({
|
||||
checkForUpdates: jest.fn().mockResolvedValue(null),
|
||||
downloadUpdate: jest.fn().mockResolvedValue(undefined),
|
||||
})),
|
||||
reloadApp: jest.fn(),
|
||||
getStoredOtaVersion: jest.fn(() => null),
|
||||
}))
|
||||
|
||||
// Update the existing nitro-modules mock to include createHybridObject
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"createBundle:ios": "mkdir -p ios/App-Bundles && react-native bundle --platform ios --dev false --entry-file index.js --bundle-output ios/App-Bundles/main.jsbundle --assets-dest ios/App-Bundles",
|
||||
"sendOTA:android": "bash scripts/ota-android.sh",
|
||||
"sendOTA:iOS": "bash scripts/ota-iOS.sh",
|
||||
"sendOTA:PR": "bash scripts/ota-PR.sh",
|
||||
"android-build": "cd android && ./gradlew generateCodegenArtifactsFromSchema && ./gradlew assembleRelease",
|
||||
"postinstall": "patch-package"
|
||||
},
|
||||
|
||||
53
scripts/ota-PR.sh
Normal file
53
scripts/ota-PR.sh
Normal file
@@ -0,0 +1,53 @@
|
||||
if [ -z "$1" ]; then
|
||||
echo "Error: Version argument is required"
|
||||
echo "Usage: $0 <version>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
version="$1"
|
||||
target_branch="PULL_REQUEST_${version}_android"
|
||||
cd android
|
||||
git clone https://github.com/Jellify-Music/App-Bundles.git
|
||||
cd App-Bundles
|
||||
if git ls-remote --exit-code --heads origin "$target_branch" >/dev/null 2>&1; then
|
||||
echo "Branch '$target_branch' already exists on remote."
|
||||
git checkout "$target_branch"
|
||||
else
|
||||
echo "Branch '$target_branch' does not exist on remote. Attempting to create it..."
|
||||
git checkout -b "$target_branch"
|
||||
fi
|
||||
cd ../..
|
||||
yarn createBundle:android
|
||||
cd android/App-Bundles
|
||||
bash ../../scripts/getRandomVersion.sh
|
||||
git add .
|
||||
git commit -m "OTA-Update - $(date +'%b %d %H:%M')"
|
||||
git push https://x-access-token:$SIGNING_REPO_PAT@github.com/Jellify-Music/App-Bundles.git "$target_branch"
|
||||
cd ..
|
||||
rm -rf App-Bundles
|
||||
cd ..
|
||||
|
||||
|
||||
target_branch="PULL_REQUEST_${version}_ios"
|
||||
cd ios
|
||||
rm -rf App-Bundles
|
||||
git clone https://github.com/Jellify-Music/App-Bundles.git
|
||||
cd App-Bundles
|
||||
if git ls-remote --exit-code --heads origin "$target_branch" >/dev/null 2>&1; then
|
||||
echo "Branch '$target_branch' already exists on remote."
|
||||
git checkout "$target_branch"
|
||||
else
|
||||
echo "Branch '$target_branch' does not exist on remote. Attempting to create it..."
|
||||
git checkout -b "$target_branch"
|
||||
fi
|
||||
rm -rf Readme.md
|
||||
cd ../..
|
||||
yarn createBundle:ios
|
||||
cd ios/App-Bundles
|
||||
bash ../../scripts/getRandomVersion.sh
|
||||
git add .
|
||||
git commit -m "OTA-Update - $(date +'%b %d %H:%M')"
|
||||
git push https://x-access-token:$SIGNING_REPO_PAT@github.com/Jellify-Music/App-Bundles.git "$target_branch"
|
||||
cd ..
|
||||
rm -rf App-Bundles
|
||||
cd ..
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
import Animated, { useSharedValue, useAnimatedStyle, withTiming } from 'react-native-reanimated'
|
||||
import DeviceInfo from 'react-native-device-info'
|
||||
import { OTA_UPDATE_ENABLED } from '../../configs/config'
|
||||
import { githubOTA, OTAUpdateManager, reloadApp } from 'react-native-nitro-ota'
|
||||
import { githubOTA, OTAUpdateManager, reloadApp, getStoredOtaVersion } from 'react-native-nitro-ota'
|
||||
|
||||
const version = DeviceInfo.getVersion()
|
||||
|
||||
@@ -24,6 +24,9 @@ const { downloadUrl, versionUrl } = githubOTA({
|
||||
ref: gitBranch, // optional, defaults to 'main'
|
||||
})
|
||||
|
||||
const otaVersion = getStoredOtaVersion()
|
||||
const isPRUpdate = otaVersion ? otaVersion.startsWith('PULL_REQUEST') : false
|
||||
|
||||
const otaManager = new OTAUpdateManager(downloadUrl, versionUrl)
|
||||
|
||||
export const downloadUpdate = (showCatchAlert: boolean = false) => {
|
||||
@@ -77,7 +80,7 @@ const GitUpdateModal = () => {
|
||||
|
||||
useEffect(() => {
|
||||
console.log('OTA_UPDATE_ENABLED', OTA_UPDATE_ENABLED)
|
||||
if (__DEV__ || !OTA_UPDATE_ENABLED) {
|
||||
if (__DEV__ || !OTA_UPDATE_ENABLED || isPRUpdate) {
|
||||
return
|
||||
}
|
||||
onCheckGitVersion()
|
||||
|
||||
26
src/components/OtaUpdates/otaPR.tsx
Normal file
26
src/components/OtaUpdates/otaPR.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { Platform, Alert } from 'react-native'
|
||||
|
||||
import { githubOTA, OTAUpdateManager, reloadApp } from 'react-native-nitro-ota'
|
||||
|
||||
export const downloadPRUpdate = (prNumber: number) => {
|
||||
const gitBranch = `PULL_REQUEST_${prNumber}_${Platform.OS}`
|
||||
const { downloadUrl, versionUrl } = githubOTA({
|
||||
githubUrl: 'https://github.com/Jellify-Music/App-Bundles',
|
||||
otaVersionPath: 'ota.version', // optional, defaults to 'ota.version'
|
||||
ref: gitBranch, // optional, defaults to 'main'
|
||||
})
|
||||
const otaManager = new OTAUpdateManager(downloadUrl, versionUrl)
|
||||
|
||||
otaManager
|
||||
.downloadUpdate()
|
||||
.then(() => {
|
||||
Alert.alert('Jellify has been updated with the PR', 'Restart to apply the changes', [
|
||||
{ text: 'OK', onPress: () => reloadApp() },
|
||||
{ text: 'Cancel', style: 'cancel' },
|
||||
])
|
||||
})
|
||||
.catch((error) => {
|
||||
Alert.alert('PR is not available or to be found')
|
||||
})
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react'
|
||||
import React, { useState } from 'react'
|
||||
import SignOut from './sign-out-button'
|
||||
import { SettingsStackParamList } from '../../../screens/Settings/types'
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack'
|
||||
@@ -7,42 +7,109 @@ import { Text } from '../../Global/helpers/text'
|
||||
import SettingsListGroup from './settings-list-group'
|
||||
import HTTPS from '../../../constants/protocols'
|
||||
import { useJellifyUser, useJellifyLibrary, useJellifyServer } from '../../../stores'
|
||||
import { SwitchWithLabel } from '../../Global/helpers/switch-with-label'
|
||||
import { useDeveloperOptionsEnabled, usePrId } from '../../../stores/settings/developer'
|
||||
import { YStack, XStack } from 'tamagui'
|
||||
import Input from '../../Global/helpers/input'
|
||||
import Button from '../../Global/helpers/button'
|
||||
import Icon from '../../Global/components/icon'
|
||||
import { Alert } from 'react-native'
|
||||
import { SettingsTabList } from '../types'
|
||||
import { downloadPRUpdate } from '../../OtaUpdates/otaPR'
|
||||
|
||||
export default function AccountTab(): React.JSX.Element {
|
||||
const [server] = useJellifyServer()
|
||||
const [user] = useJellifyUser()
|
||||
const [library] = useJellifyLibrary()
|
||||
const [developerOptionsEnabled, setDeveloperOptionsEnabled] = useDeveloperOptionsEnabled()
|
||||
const [prId, setPrId] = usePrId()
|
||||
const [localPrId, setLocalPrId] = useState(prId)
|
||||
|
||||
const navigation = useNavigation<NativeStackNavigationProp<SettingsStackParamList>>()
|
||||
|
||||
const handleSubmitPr = () => {
|
||||
if (localPrId.trim()) {
|
||||
setPrId(localPrId.trim())
|
||||
downloadPRUpdate(Number(localPrId.trim()))
|
||||
} else {
|
||||
Alert.alert('Error', 'Please enter a valid PR ID')
|
||||
}
|
||||
}
|
||||
|
||||
const settingsList: SettingsTabList = [
|
||||
{
|
||||
title: 'Username',
|
||||
subTitle: 'You are awesome!',
|
||||
iconName: 'account-music',
|
||||
iconColor: '$borderColor',
|
||||
children: <Text>{user?.name ?? 'Unknown User'}</Text>,
|
||||
},
|
||||
{
|
||||
title: 'Selected Library',
|
||||
subTitle: 'Tap to change library',
|
||||
iconName: 'book-music',
|
||||
iconColor: '$borderColor',
|
||||
children: <Text>{library?.musicLibraryName ?? 'Unknown Library'}</Text>,
|
||||
onPress: () => navigation.navigate('LibrarySelection'),
|
||||
},
|
||||
{
|
||||
title: server?.name ?? 'Untitled Server',
|
||||
subTitle: server?.version ?? 'Unknown Jellyfin Version',
|
||||
iconName: server?.url.includes(HTTPS) ? 'lock' : 'lock-open',
|
||||
iconColor: server?.url.includes(HTTPS) ? '$success' : '$borderColor',
|
||||
children: <Text>{server?.address ?? 'Unknown Server'}</Text>,
|
||||
},
|
||||
{
|
||||
title: 'Developer Options',
|
||||
subTitle: 'Enable advanced developer features',
|
||||
iconName: developerOptionsEnabled ? 'code-braces' : 'code-braces-box',
|
||||
iconColor: developerOptionsEnabled ? '$success' : '$borderColor',
|
||||
children: (
|
||||
<YStack gap='$2'>
|
||||
<SwitchWithLabel
|
||||
checked={developerOptionsEnabled}
|
||||
onCheckedChange={setDeveloperOptionsEnabled}
|
||||
size={'$2'}
|
||||
label={developerOptionsEnabled ? 'Enabled' : 'Disabled'}
|
||||
/>
|
||||
{developerOptionsEnabled && (
|
||||
<YStack gap='$3' paddingTop='$2'>
|
||||
<Text color='$borderColor'>
|
||||
Enter PR ID to test pull request builds
|
||||
</Text>
|
||||
<XStack gap='$2' alignItems='center' width='80%'>
|
||||
<Input
|
||||
flex={1}
|
||||
placeholder='Enter PR ID'
|
||||
value={localPrId}
|
||||
onChangeText={setLocalPrId}
|
||||
keyboardType='numeric'
|
||||
size='$3'
|
||||
/>
|
||||
<Button
|
||||
size='$3'
|
||||
backgroundColor='$primary'
|
||||
color='$background'
|
||||
onPress={handleSubmitPr}
|
||||
circular
|
||||
icon={<Icon name='check' color='$background' small />}
|
||||
/>
|
||||
</XStack>
|
||||
{prId && (
|
||||
<Text color='$success' fontSize={'$2'}>
|
||||
{`Current PR ID: ${prId}`}
|
||||
</Text>
|
||||
)}
|
||||
</YStack>
|
||||
)}
|
||||
</YStack>
|
||||
),
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<>
|
||||
<SettingsListGroup
|
||||
settingsList={[
|
||||
{
|
||||
title: 'Username',
|
||||
subTitle: 'You are awesome!',
|
||||
iconName: 'account-music',
|
||||
iconColor: '$borderColor',
|
||||
children: <Text>{user?.name ?? 'Unknown User'}</Text>,
|
||||
},
|
||||
{
|
||||
title: 'Selected Library',
|
||||
subTitle: 'Tap to change library',
|
||||
iconName: 'book-music',
|
||||
iconColor: '$borderColor',
|
||||
children: <Text>{library?.musicLibraryName ?? 'Unknown Library'}</Text>,
|
||||
onPress: () => navigation.navigate('LibrarySelection'),
|
||||
},
|
||||
{
|
||||
title: server?.name ?? 'Untitled Server',
|
||||
subTitle: server?.version ?? 'Unknown Jellyfin Version',
|
||||
iconName: server?.url.includes(HTTPS) ? 'lock' : 'lock-open',
|
||||
iconColor: server?.url.includes(HTTPS) ? '$success' : '$borderColor',
|
||||
children: <Text>{server?.address ?? 'Unknown Server'}</Text>,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<SettingsListGroup settingsList={settingsList} />
|
||||
<SignOut navigation={navigation} />
|
||||
</>
|
||||
)
|
||||
|
||||
46
src/stores/settings/developer.ts
Normal file
46
src/stores/settings/developer.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { mmkvStateStorage } from '../../constants/storage'
|
||||
import { create } from 'zustand'
|
||||
import { createJSONStorage, devtools, persist } from 'zustand/middleware'
|
||||
|
||||
type DeveloperSettingsStore = {
|
||||
developerOptionsEnabled: boolean
|
||||
setDeveloperOptionsEnabled: (enabled: boolean) => void
|
||||
|
||||
prId: string
|
||||
setPrId: (prId: string) => void
|
||||
}
|
||||
|
||||
export const useDeveloperSettingsStore = create<DeveloperSettingsStore>()(
|
||||
devtools(
|
||||
persist(
|
||||
(set) => ({
|
||||
developerOptionsEnabled: false,
|
||||
setDeveloperOptionsEnabled: (developerOptionsEnabled) =>
|
||||
set({ developerOptionsEnabled }),
|
||||
|
||||
prId: '',
|
||||
setPrId: (prId) => set({ prId }),
|
||||
}),
|
||||
{
|
||||
name: 'developer-settings-storage',
|
||||
storage: createJSONStorage(() => mmkvStateStorage),
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
export const useDeveloperOptionsEnabled: () => [boolean, (enabled: boolean) => void] = () => {
|
||||
const developerOptionsEnabled = useDeveloperSettingsStore(
|
||||
(state) => state.developerOptionsEnabled,
|
||||
)
|
||||
const setDeveloperOptionsEnabled = useDeveloperSettingsStore(
|
||||
(state) => state.setDeveloperOptionsEnabled,
|
||||
)
|
||||
return [developerOptionsEnabled, setDeveloperOptionsEnabled]
|
||||
}
|
||||
|
||||
export const usePrId: () => [string, (prId: string) => void] = () => {
|
||||
const prId = useDeveloperSettingsStore((state) => state.prId)
|
||||
const setPrId = useDeveloperSettingsStore((state) => state.setPrId)
|
||||
return [prId, setPrId]
|
||||
}
|
||||
Reference in New Issue
Block a user