Skip to content

Commit 38fc41f

Browse files
author
Developer
committed
feat:热更新
1 parent 4a69a8e commit 38fc41f

8 files changed

Lines changed: 278 additions & 21 deletions

File tree

.github/workflows/release.yml

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
name: Release APK
2+
3+
on:
4+
push:
5+
branches:
6+
- master
7+
8+
jobs:
9+
release:
10+
runs-on: ubuntu-latest
11+
if: "!contains(github.event.head_commit.message, '[skip ci]')"
12+
13+
steps:
14+
- name: Checkout
15+
uses: actions/checkout@v4
16+
with:
17+
fetch-depth: 0
18+
token: ${{ secrets.GITHUB_TOKEN }}
19+
20+
- name: Setup Node 20
21+
uses: actions/setup-node@v4
22+
with:
23+
node-version: '20'
24+
cache: 'npm'
25+
26+
- name: Install dependencies
27+
run: npm ci
28+
29+
- name: Bump version
30+
id: bump
31+
run: |
32+
NEW_VERSION=$(node scripts/bump-version.js)
33+
echo "version=$NEW_VERSION" >> $GITHUB_OUTPUT
34+
35+
- name: Commit version bump
36+
run: |
37+
git config user.name "github-actions[bot]"
38+
git config user.email "github-actions[bot]@users.noreply.github.com"
39+
git add app.json package.json android/app/build.gradle
40+
git commit -m "chore: bump version to v${{ steps.bump.outputs.version }} [skip ci]"
41+
git push
42+
43+
- name: Setup Java 17
44+
uses: actions/setup-java@v4
45+
with:
46+
distribution: 'temurin'
47+
java-version: '17'
48+
49+
- name: Setup Android SDK
50+
uses: android-actions/setup-android@v3
51+
52+
- name: Grant Gradle execute permission
53+
run: chmod +x android/gradlew
54+
55+
- name: Build Release APK
56+
run: |
57+
cd android
58+
./gradlew assembleRelease --no-daemon
59+
60+
- name: Create GitHub Release
61+
env:
62+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
63+
run: |
64+
gh release create "v${{ steps.bump.outputs.version }}" \
65+
--title "JKVideo v${{ steps.bump.outputs.version }}" \
66+
--generate-notes \
67+
android/app/build/outputs/apk/release/app-release.apk

app.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,7 @@
2626
"permissions": [
2727
"android.permission.RECORD_AUDIO",
2828
"android.permission.MODIFY_AUDIO_SETTINGS",
29-
"android.permission.RECORD_AUDIO",
30-
"android.permission.MODIFY_AUDIO_SETTINGS"
29+
"android.permission.REQUEST_INSTALL_PACKAGES"
3130
],
3231
"package": "com.anonymous.jkvideo"
3332
},

app/settings.tsx

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
import React from 'react';
2-
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
2+
import { View, Text, TouchableOpacity, StyleSheet, ActivityIndicator } from 'react-native';
33
import { SafeAreaView } from 'react-native-safe-area-context';
44
import { useRouter } from 'expo-router';
55
import { Ionicons } from '@expo/vector-icons';
66
import { useAuthStore } from '../store/authStore';
77
import { useSettingsStore } from '../store/settingsStore';
8+
import { useCheckUpdate } from '../hooks/useCheckUpdate';
89

910
export default function SettingsScreen() {
1011
const router = useRouter();
1112
const { isLoggedIn, logout } = useAuthStore();
1213
const { coverQuality, setCoverQuality } = useSettingsStore();
14+
const { currentVersion, isChecking, downloadProgress, checkUpdate } = useCheckUpdate();
1315

1416
const handleLogout = async () => {
1517
await logout();
@@ -26,6 +28,35 @@ export default function SettingsScreen() {
2628
<View style={styles.spacer} />
2729
</View>
2830

31+
<View style={styles.section}>
32+
<Text style={styles.sectionLabel}>版本信息</Text>
33+
<View style={styles.versionRow}>
34+
<Text style={styles.versionLabel}>当前版本</Text>
35+
<Text style={styles.versionValue}>v{currentVersion}</Text>
36+
</View>
37+
</View>
38+
39+
<View style={styles.section}>
40+
<Text style={styles.sectionLabel}>更新</Text>
41+
<TouchableOpacity
42+
style={styles.updateBtn}
43+
onPress={checkUpdate}
44+
activeOpacity={0.7}
45+
disabled={isChecking || downloadProgress !== null}
46+
>
47+
{isChecking ? (
48+
<>
49+
<ActivityIndicator size="small" color="#00AEEC" style={{ marginRight: 8 }} />
50+
<Text style={styles.updateBtnText}>检查中...</Text>
51+
</>
52+
) : downloadProgress !== null ? (
53+
<Text style={styles.updateBtnText}>下载中 {downloadProgress}%</Text>
54+
) : (
55+
<Text style={styles.updateBtnText}>检查更新</Text>
56+
)}
57+
</TouchableOpacity>
58+
</View>
59+
2960
<View style={styles.section}>
3061
<Text style={styles.sectionLabel}>封面图清晰度</Text>
3162
<View style={styles.optionRow}>
@@ -102,6 +133,19 @@ const styles = StyleSheet.create({
102133
optionActive: { borderColor: '#00AEEC', backgroundColor: '#e8f7fd' },
103134
optionText: { fontSize: 14, color: '#666' },
104135
optionTextActive: { color: '#00AEEC', fontWeight: '600' },
136+
versionRow: {
137+
flexDirection: 'row',
138+
justifyContent: 'space-between',
139+
alignItems: 'center',
140+
},
141+
versionLabel: { fontSize: 14, color: '#212121' },
142+
versionValue: { fontSize: 14, color: '#999' },
143+
updateBtn: {
144+
flexDirection: 'row',
145+
alignItems: 'center',
146+
paddingVertical: 6,
147+
},
148+
updateBtnText: { fontSize: 14, color: '#00AEEC', fontWeight: '600' },
105149
logoutBtn: {
106150
margin: 24,
107151
paddingVertical: 12,

app/video/[bvid].tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -460,7 +460,7 @@ const styles = StyleSheet.create({
460460
paddingBottom: 0,
461461
paddingTop: 12,
462462
},
463-
avatar: { width: 48, height: 48, borderRadius: 19, marginRight: 10 },
463+
avatar: { width: 48, height: 48, borderRadius:30, marginRight: 10 },
464464
upName: { flex: 1, fontSize: 14, color: "#212121", fontWeight: "500" },
465465
followBtn: {
466466
backgroundColor: "#00AEEC",

hooks/useCheckUpdate.ts

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import { useState } from 'react';
2+
import { Alert, Platform } from 'react-native';
3+
import * as FileSystem from 'expo-file-system';
4+
import * as IntentLauncher from 'expo-intent-launcher';
5+
import Constants from 'expo-constants';
6+
7+
const GITHUB_API = 'https://api.github.com/repos/tiajinsha/JKVideo/releases/latest';
8+
9+
function compareVersions(a: string, b: string): number {
10+
const pa = a.replace(/^v/, '').split('.').map(Number);
11+
const pb = b.replace(/^v/, '').split('.').map(Number);
12+
for (let i = 0; i < 3; i++) {
13+
if ((pa[i] ?? 0) > (pb[i] ?? 0)) return 1;
14+
if ((pa[i] ?? 0) < (pb[i] ?? 0)) return -1;
15+
}
16+
return 0;
17+
}
18+
19+
export function useCheckUpdate() {
20+
const currentVersion = Constants.expoConfig?.version ?? '0.0.0';
21+
const [isChecking, setIsChecking] = useState(false);
22+
const [downloadProgress, setDownloadProgress] = useState<number | null>(null);
23+
24+
const checkUpdate = async () => {
25+
setIsChecking(true);
26+
try {
27+
const res = await fetch(GITHUB_API, {
28+
headers: { Accept: 'application/vnd.github+json' },
29+
});
30+
if (!res.ok) throw new Error(`GitHub API ${res.status}`);
31+
const data = await res.json();
32+
33+
const latestVersion: string = data.tag_name ?? '';
34+
const apkAsset = (data.assets as any[]).find((a) =>
35+
(a.name as string).endsWith('.apk')
36+
);
37+
const downloadUrl: string = apkAsset?.browser_download_url ?? '';
38+
const releaseNotes: string = data.body ?? '';
39+
40+
if (compareVersions(latestVersion, currentVersion) <= 0) {
41+
Alert.alert('已是最新版本', `当前版本 v${currentVersion} 已是最新`);
42+
return;
43+
}
44+
45+
Alert.alert(
46+
`发现新版本 ${latestVersion}`,
47+
releaseNotes || '有新版本可用,是否立即下载?',
48+
[
49+
{ text: '取消', style: 'cancel' },
50+
{
51+
text: '下载安装',
52+
onPress: () => downloadAndInstall(downloadUrl, latestVersion),
53+
},
54+
]
55+
);
56+
} catch (e: any) {
57+
Alert.alert('检查失败', e?.message ?? '网络错误,请稍后重试');
58+
} finally {
59+
setIsChecking(false);
60+
}
61+
};
62+
63+
const downloadAndInstall = async (url: string, version: string) => {
64+
if (Platform.OS !== 'android') {
65+
Alert.alert('提示', '自动安装仅支持 Android 设备');
66+
return;
67+
}
68+
const localUri = FileSystem.cacheDirectory + `JKVideo-${version}.apk`;
69+
try {
70+
setDownloadProgress(0);
71+
const downloadResumable = FileSystem.createDownloadResumable(
72+
url,
73+
localUri,
74+
{},
75+
({ totalBytesWritten, totalBytesExpectedToWrite }) => {
76+
if (totalBytesExpectedToWrite > 0) {
77+
setDownloadProgress(
78+
Math.round((totalBytesWritten / totalBytesExpectedToWrite) * 100)
79+
);
80+
}
81+
}
82+
);
83+
await downloadResumable.downloadAsync();
84+
setDownloadProgress(null);
85+
86+
const contentUri = await FileSystem.getContentUriAsync(localUri);
87+
await IntentLauncher.startActivityAsync('android.intent.action.VIEW', {
88+
data: contentUri,
89+
flags: 1,
90+
type: 'application/vnd.android.package-archive',
91+
});
92+
} catch (e: any) {
93+
setDownloadProgress(null);
94+
Alert.alert('下载失败', e?.message ?? '请稍后重试');
95+
}
96+
};
97+
98+
return { currentVersion, isChecking, downloadProgress, checkUpdate };
99+
}

package-lock.json

Lines changed: 28 additions & 17 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@
1717
"expo": "~55.0.5",
1818
"expo-av": "^16.0.8",
1919
"expo-clipboard": "~55.0.9",
20+
"expo-constants": "~55.0.9",
2021
"expo-dev-client": "~55.0.11",
2122
"expo-file-system": "~55.0.10",
23+
"expo-intent-launcher": "~55.0.9",
2224
"expo-linear-gradient": "~55.0.8",
2325
"expo-media-library": "~55.0.10",
2426
"expo-network": "~55.0.9",

0 commit comments

Comments
 (0)