11import * as Application from 'expo-application' ;
22import * as Constants from 'expo-constants' ;
33import * as Updates from 'expo-updates' ;
4- import React , { useEffect , useState } from 'react' ;
4+ import React , { useMemo , useState } from 'react' ;
55import { Image , StyleSheet , View } from 'react-native' ;
66import { ScrollView } from 'react-native-gesture-handler' ;
77
@@ -12,6 +12,11 @@ import ThemeBasedView from '@/components/view';
1212
1313import useVisualScheme from '@/store/visualScheme' ;
1414
15+ import {
16+ checkAndDownloadUpdateAsync ,
17+ type EasUpdateProgress ,
18+ } from '@/utils/easUpdate' ;
19+
1520import { UpdateInfo } from '@/types/updateInfo' ;
1621
1722// eslint-disable-next-line @typescript-eslint/no-require-imports
@@ -20,19 +25,124 @@ const mxLogo = require('../../assets/images/mx-logo.png');
2025function CheckUpdate ( ) : React . ReactNode {
2126 const version = Application . nativeApplicationVersion ;
2227 const updateInfo = Constants . default . expoConfig ?. extra
23- ?. updateInfo as UpdateInfo ;
24- const [ loading , setLoading ] = useState ( false ) ;
28+ ?. updateInfo as UpdateInfo | undefined ;
29+ const [ manualProgress , setManualProgress ] = useState <
30+ EasUpdateProgress | 'restarting' | null
31+ > ( null ) ;
32+ const [ hasDownloadedUpdate , setHasDownloadedUpdate ] = useState ( false ) ;
2533 const currentStyle = useVisualScheme ( state => state . currentStyle ) ;
26- const { isUpdateAvailable, isUpdatePending } = Updates . useUpdates ( ) ;
27- useEffect ( ( ) => {
28- if ( isUpdatePending ) {
29- void Updates . reloadAsync ( ) ;
34+ const {
35+ downloadProgress,
36+ isChecking,
37+ isDownloading,
38+ isRestarting,
39+ isStartupProcedureRunning,
40+ isUpdateAvailable,
41+ isUpdatePending,
42+ } = Updates . useUpdates ( ) ;
43+
44+ const canRestart = isUpdatePending || hasDownloadedUpdate ;
45+ const isBusy =
46+ manualProgress !== null ||
47+ isChecking ||
48+ isDownloading ||
49+ isRestarting ||
50+ isStartupProcedureRunning ;
51+
52+ const buttonLabel = useMemo ( ( ) => {
53+ if ( manualProgress === 'restarting' || isRestarting ) return '正在重启…' ;
54+ if ( manualProgress === 'downloading' || isDownloading ) {
55+ return downloadProgress === undefined
56+ ? '正在下载…'
57+ : `正在下载 ${ Math . round ( downloadProgress * 100 ) } %` ;
58+ }
59+ if (
60+ manualProgress === 'checking' ||
61+ isChecking ||
62+ isStartupProcedureRunning
63+ )
64+ return '正在检查…' ;
65+ if ( canRestart ) return '立即重启应用' ;
66+ if ( isUpdateAvailable && ! isUpdatePending ) return '下载更新' ;
67+ return '检查更新' ;
68+ } , [
69+ canRestart ,
70+ downloadProgress ,
71+ isChecking ,
72+ isDownloading ,
73+ isRestarting ,
74+ isStartupProcedureRunning ,
75+ isUpdateAvailable ,
76+ isUpdatePending ,
77+ manualProgress ,
78+ ] ) ;
79+
80+ const statusText = useMemo ( ( ) => {
81+ if ( manualProgress === 'restarting' || isRestarting )
82+ return '正在应用更新,请稍候。' ;
83+ if ( manualProgress === 'downloading' || isDownloading )
84+ return '正在下载更新,请保持网络连接。' ;
85+ if (
86+ manualProgress === 'checking' ||
87+ isChecking ||
88+ isStartupProcedureRunning
89+ )
90+ return '正在检查是否有可用更新。' ;
91+ if ( canRestart ) return '更新已下载,重启应用后即可使用。' ;
92+ if ( isUpdateAvailable && ! isUpdatePending )
93+ return '发现可用更新,点击按钮开始下载。' ;
94+ return '更新会在后台自动检查,也可以在这里手动检查。' ;
95+ } , [
96+ canRestart ,
97+ isChecking ,
98+ isDownloading ,
99+ isRestarting ,
100+ isStartupProcedureRunning ,
101+ isUpdateAvailable ,
102+ isUpdatePending ,
103+ manualProgress ,
104+ ] ) ;
105+
106+ const handleUpdatePress = async ( ) => {
107+ if ( isBusy ) return ;
108+
109+ if ( canRestart ) {
110+ setManualProgress ( 'restarting' ) ;
111+ try {
112+ await Updates . reloadAsync ( ) ;
113+ } catch {
114+ setManualProgress ( null ) ;
115+ Toast . show ( { text : '应用更新失败,请稍后重试。' } ) ;
116+ }
117+ return ;
118+ }
119+
120+ if ( __DEV__ || ! Updates . isEnabled ) {
121+ Toast . show ( { text : '当前构建不支持热更新检查。' } ) ;
122+ return ;
30123 }
31- } , [ isUpdatePending ] ) ;
32124
33- useEffect ( ( ) => {
34- if ( isUpdateAvailable ) Updates . fetchUpdateAsync ( ) . then ( _r => { } ) ;
35- } , [ isUpdateAvailable ] ) ;
125+ setManualProgress ( 'checking' ) ;
126+ try {
127+ const result = await checkAndDownloadUpdateAsync ( {
128+ hasAvailableUpdate : isUpdateAvailable ,
129+ onProgress : setManualProgress ,
130+ } ) ;
131+
132+ if ( result . status === 'downloaded' ) {
133+ setHasDownloadedUpdate ( true ) ;
134+ Toast . show ( { text : '更新已下载,可以立即重启应用。' } ) ;
135+ } else if ( result . status === 'up-to-date' ) {
136+ Toast . show ( { text : '已是最新版' , icon : 'success' } ) ;
137+ } else {
138+ Toast . show ( { text : '当前构建未启用热更新。' } ) ;
139+ }
140+ } catch {
141+ Toast . show ( { text : '检查更新失败,请检查网络后重试。' } ) ;
142+ } finally {
143+ setManualProgress ( null ) ;
144+ }
145+ } ;
36146
37147 return (
38148 < ThemeBasedView style = { styles . container } >
@@ -44,61 +154,46 @@ function CheckUpdate(): React.ReactNode {
44154 </ TypoText >
45155 < View style = { styles . versionBlock } >
46156 < TypoText level = { 2 } bold style = { styles . versionTitle } >
47- 热更新版本 { updateInfo . otaVersion }
157+ 热更新版本 { updateInfo ? .otaVersion ?? Updates . runtimeVersion }
48158 </ TypoText >
49159 < TypoText level = "body" > 应用版本 { version } </ TypoText >
50- < TypoText level = "body" > { updateInfo . updateTime || '' } </ TypoText >
160+ < TypoText level = "body" > { updateInfo ? .updateTime ?? '' } </ TypoText >
51161 </ View >
52162 < View style = { styles . divider } />
53163 < View style = { styles . sectionBlock } >
54164 < TypoText level = { 3 } bold style = { styles . sectionTitle } >
55165 新增功能:
56166 </ TypoText >
57167 < TypoText level = "body" style = { styles . sectionContent } >
58- { ( updateInfo . newFeatures || [ ] ) . join ( '\n' ) }
168+ { ( updateInfo ? .newFeatures ?? [ ] ) . join ( '\n' ) }
59169 </ TypoText >
60170 < TypoText level = { 3 } bold style = { styles . sectionTitle } >
61171 Bug修复:
62172 </ TypoText >
63173 < TypoText level = "body" style = { styles . sectionContent } >
64- { ( updateInfo . fixedIssues || [ ] ) . join ( '\n' ) }
174+ { ( updateInfo ? .fixedIssues ?? [ ] ) . join ( '\n' ) }
65175 </ TypoText >
66176 < TypoText level = { 3 } bold style = { styles . sectionTitle } >
67177 已知问题:
68178 </ TypoText >
69179 < TypoText level = "body" style = { styles . sectionContent } >
70- { ( updateInfo . knownIssues || [ ] ) . join ( '\n' ) }
180+ { ( updateInfo ? .knownIssues ?? [ ] ) . join ( '\n' ) }
71181 </ TypoText >
72182 </ View >
73183 < View style = { styles . divider } />
74184 < Button
75185 style = { [ styles . updateButton , currentStyle ?. button_style ] }
76- onPress = { ( ) => {
77- setLoading ( true ) ;
78- if ( __DEV__ ) {
79- Toast . show ( { text : '已是最新版' , icon : 'success' } ) ;
80- setLoading ( false ) ;
81- } else {
82- Updates . checkForUpdateAsync ( )
83- . then ( res => {
84- if ( ! res . isAvailable ) {
85- Toast . show ( { text : '已是最新版' , icon : 'success' } ) ;
86- }
87- } )
88- . catch ( err => {
89- Toast . show ( { text : err . toString ( ) } ) ;
90- } )
91- . finally ( ( ) => {
92- setLoading ( false ) ;
93- } ) ;
94- }
95- } }
96- isLoading = { loading }
186+ onPress = { ( ) => void handleUpdatePress ( ) }
187+ isLoading = { isBusy }
97188 >
98- 检 查 更 新
189+ { buttonLabel }
99190 </ Button >
100- < TypoText level = "body" style = { styles . bottomTip } >
101- 更新后请重启应用以确保新功能生效。
191+ < TypoText
192+ level = "body"
193+ style = { styles . bottomTip }
194+ accessibilityLiveRegion = "polite"
195+ >
196+ { statusText }
102197 </ TypoText >
103198 </ View >
104199 </ ScrollView >
0 commit comments