_layout.tsx 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. import { AnimatedSplashOverlay } from '@/components/animated-icon';
  2. import { antdTheme } from '@/constants/antd-theme';
  3. import { Colors } from '@/constants/theme';
  4. import '@/global.css';
  5. import api from '@/utils/api';
  6. import { AuthProvider } from '@/utils/auth';
  7. import { getGlobalStorage } from '@/utils/storage';
  8. import { Modal, Provider, Toast } from '@ant-design/react-native';
  9. import type { Theme } from '@react-navigation/native';
  10. import {
  11. DefaultTheme as ReactNavigationDefaultTheme,
  12. ThemeProvider,
  13. } from '@react-navigation/native';
  14. import * as application from 'expo-application';
  15. import { BlurView } from 'expo-blur';
  16. import { useFonts } from 'expo-font';
  17. import { Image } from 'expo-image';
  18. import { Stack } from 'expo-router';
  19. import * as SplashScreen from 'expo-splash-screen';
  20. import * as updates from 'expo-updates';
  21. import { useEffect, useState } from 'react';
  22. import { Linking, Platform, View } from 'react-native';
  23. SplashScreen.preventAutoHideAsync();
  24. // import * as app from 'expo-'
  25. export const DefaultTheme: Theme = {
  26. ...ReactNavigationDefaultTheme,
  27. colors: {
  28. ...ReactNavigationDefaultTheme.colors,
  29. primary: antdTheme.brand_primary,
  30. background: antdTheme.fill_body,
  31. card: antdTheme.fill_base,
  32. text: antdTheme.color_text_base,
  33. border: antdTheme.border_color_base,
  34. notification: antdTheme.brand_error,
  35. },
  36. };
  37. export default function RootLayout() {
  38. const [initializing, setInitlizing] = useState(true);
  39. // const colorScheme = useColorScheme();
  40. const [fontsLoaded] = useFonts({
  41. antoutline: require('@ant-design/icons-react-native/fonts/antoutline.ttf'),
  42. });
  43. useEffect(() => {
  44. const now = Date.now() / 1000;
  45. async function checkApp() {
  46. try {
  47. const lastTime = parseInt(getGlobalStorage().getString('last_update_app') || '0');
  48. if (now - lastTime < 15 * 60000) {
  49. return;
  50. }
  51. const {
  52. version,
  53. package: pkg,
  54. type,
  55. force,
  56. } = await api.post<{ version: string, package: string, type: 'apk' | 'url', force: boolean }>('common/check_version', {
  57. platform: Platform.OS,
  58. version: application.nativeApplicationVersion,
  59. }, { timeout: 5000 });
  60. getGlobalStorage().set('last_update_app', now);
  61. if (!version || version === application.nativeApplicationVersion) {
  62. return;
  63. }
  64. const va = version.split('.');
  65. let vb = (application.nativeApplicationVersion || '').split('.');
  66. let update = false;
  67. for (let i = 0; i < va.length; i++) {
  68. if (vb.length <= i || parseInt(va[i]) > parseInt(vb[i])) {
  69. update = true;
  70. break;
  71. }
  72. }
  73. if (!update) {
  74. return;
  75. }
  76. SplashScreen.hide();
  77. let ok = true;
  78. do {
  79. ok = await new Promise<boolean>((resolve, reject) => {
  80. const actions: any[] = [
  81. {
  82. text: '立即更新',
  83. onPress: async () => {
  84. if (type === 'url' || type === 'apk') {
  85. if (pkg) {
  86. if (await Linking.canOpenURL(pkg)) {
  87. Linking.openURL(pkg);
  88. resolve(true);
  89. return;
  90. }
  91. }
  92. Modal.alert('无法自动更新', "请前往应用商店更新");
  93. resolve(false);
  94. return;
  95. }
  96. // todo other
  97. resolve(false);
  98. }
  99. }
  100. ];
  101. if (!force) {
  102. actions.unshift({
  103. text: '否',
  104. style: { color: Colors.textSecondary },
  105. onPress: () => {
  106. resolve(true);
  107. }
  108. });
  109. }
  110. Modal.alert(`${version}可更新`, force ? '请更新后继续使用' : '是否立即更新?', actions);
  111. });
  112. } while (!ok);
  113. } catch (e) {
  114. console.warn(e);
  115. }
  116. }
  117. (async () => {
  118. if (__DEV__) {
  119. setInitlizing(false);
  120. SplashScreen.hide();
  121. return;
  122. }
  123. await checkApp();
  124. SplashScreen.hide();
  125. while (true) {
  126. let res = await new Promise<string | void>(async (resolve, reject) => {
  127. const res = await updates.checkForUpdateAsync();
  128. if (res.isAvailable) {
  129. Modal.alert("发现热更新", "需要立即下载", [
  130. {
  131. text: '确认',
  132. onPress: async () => {
  133. const l = Toast.loading("正在下载更新 ...");
  134. try {
  135. await updates.fetchUpdateAsync();
  136. Toast.remove(l);
  137. await updates.reloadAsync();
  138. // eslint-disable-next-line @typescript-eslint/no-unused-vars
  139. } catch (e) {
  140. Toast.remove(l);
  141. Modal.alert("提示", "更新遇到问题", [{
  142. text: '确认',
  143. onPress: resolve,
  144. }], () => { resolve(); return true });
  145. }
  146. }
  147. }
  148. ], () => false);
  149. } else {
  150. resolve('updateok');
  151. }
  152. });
  153. if (res === 'updateok') {
  154. break;
  155. }
  156. }
  157. setInitlizing(false);
  158. })();
  159. }, []);
  160. return (
  161. <ThemeProvider value={DefaultTheme}>
  162. <Provider theme={antdTheme}>
  163. {(!initializing && fontsLoaded) &&
  164. <AuthProvider>
  165. <AnimatedSplashOverlay />
  166. <Stack screenOptions={{
  167. headerShown: true,
  168. headerTransparent: true,
  169. headerBackButtonDisplayMode: 'minimal',
  170. headerBackground: () => (
  171. <BlurView intensity={80} tint="light" style={{ flex: 1 }} />
  172. ),
  173. // headerBackground: Platform.OS === 'android' ? () => <View style={{ flex: 1, backgroundColor: 'rgba(255, 255, 255, 0.9)' }} /> : undefined,
  174. // headerLeft: ({ canGoBack }) => canGoBack && <Icon name="arrow-left" size={24} />,
  175. animation: 'ios_from_right'
  176. }}>
  177. <Stack.Screen name="(tabs)" options={{ headerShown: false, }} />
  178. <Stack.Screen name="sign-in" options={{ headerShown: false, animation: 'fade_from_bottom' }} />
  179. <Stack.Screen name="sign-up" options={{ headerShown: false, animation: 'ios_from_right' }} />
  180. </Stack>
  181. </AuthProvider>}
  182. </Provider>
  183. {(initializing || !fontsLoaded) && <View className='absolute left-0 top-0 right-0 bottom-0 bg-[#f6f6f6] z-50'>
  184. <Image contentFit='contain' style={{ flex: 1 }} source={require('@/assets/images/uploading.jpg')} />
  185. </View>}
  186. </ThemeProvider>
  187. );
  188. }