Files
minibits_wallet/src/screens/UpdateScreen.tsx
T

294 lines
10 KiB
TypeScript

import {observer} from 'mobx-react-lite'
import React, {FC, useEffect, useState} from 'react'
import {Linking, Platform, ScrollView, Switch, TextStyle, View, ViewStyle} from 'react-native'
import {
APP_ENV,
} from '@env'
// import codePush from "react-native-code-push"
import {colors, spacing, useThemeColor} from '../theme'
import {
Icon,
ListItem,
Screen,
Text,
Card,
Loading,
ErrorModal,
InfoModal,
Button,
BottomModal,
} from '../components'
import {useHeader} from '../utils/useHeader'
import AppError, { Err } from '../utils/AppError'
import { log } from '../services'
import {Env} from '../utils/envtypes'
import { CommonActions, StaticScreenProps, useNavigation } from '@react-navigation/native'
import { translate } from '../i18n'
import { HotUpdater, useHotUpdaterStore } from '@hot-updater/react-native'
import { ResultModalInfo } from './Wallet/ResultModalInfo'
type Props = StaticScreenProps<{
isNativeUpdateAvailable: boolean,
isUpdateAvailable: boolean,
updateDescription: string,
updateSize: string
prevScreen: 'Settings' | 'Wallet'
}>
export const UpdateScreen = observer(function UpdateScreen({ route }: Props) {
const navigation = useNavigation()
const {
isUpdateAvailable,
isNativeUpdateAvailable,
updateDescription,
updateSize,
prevScreen
} = route.params
// Select only the rounded percentage (0-100) instead of the raw progress float.
// The native download fires onProgress events very rapidly; subscribing to the raw
// value re-renders this screen (and its Portal-hosted modal) on every tick, faster
// than the JS thread can commit. useSyncExternalStore then forces nested sync
// re-renders to stay consistent and trips "Maximum update depth exceeded". Rounding
// here means a re-render only when the integer percent actually changes.
const progressPercent = useHotUpdaterStore(state => Math.round(state.progress * 100))
useHeader({
leftIcon: 'faArrowLeft',
onLeftPress: () => {
//@ts-ignore
navigation.setParams({isUpdateAvailable: undefined})
//@ts-ignore
navigation.setParams({isNativeUpdateAvailable: undefined})
//@ts-ignore
navigation.setParams({updateDescription: undefined})
//@ts-ignore
navigation.setParams({updateSize: undefined})
if(prevScreen === 'Settings') {
navigation.goBack()
} else {
navigation.dispatch(
CommonActions.reset({
index: 1,
routes: [{
name: 'WalletNavigator'
}]
})
)
}
},
})
const [info, setInfo] = useState('')
const [isLoading, setIsLoading] = useState(false)
const [isUpdateModalVisible, setIsUpdateModalVisible] = useState(false)
const [error, setError] = useState<AppError | undefined>()
const toggleUpdateModal = () => {
setIsUpdateModalVisible(previousState => !previousState)
}
const handleUpdate = async function () {
try {
setIsUpdateModalVisible(true)
const updateInfo = await HotUpdater.checkForUpdate({
updateStrategy: "fingerprint",
})
if (!updateInfo) {
throw new AppError(Err.NETWORK_ERROR, translate('updateScreen_couldNotRetrieveUpdate'))
}
const isDownloaded = await updateInfo.updateBundle()
if(isDownloaded) {
await HotUpdater.reload()
} else {
throw new AppError(Err.NETWORK_ERROR, 'Could not download update bundle')
}
} catch(e: any) {
handleError(e)
}
}
const gotoPlayStore = () => {
const packageName = 'com.minibits_wallet'; // Replace with your app's package name
// Determine the URL based on the user's platform (Android or iOS)
let url = ''
if (Platform.OS === 'android') {
url = `market://details?id=${packageName}`
} else if (Platform.OS === 'ios') {
url = `itms-apps://itunes.apple.com/app/${packageName}`
}
// Open the URL in the device's default app for handling URLs (e.g., Play Store)
Linking.openURL(url)
}
const gotoGithub = () => {
Linking.openURL('https://github.com/minibits-cash/minibits_wallet/releases')
}
const handleError = function (e: AppError): void {
setIsLoading(false)
setIsUpdateModalVisible(false)
setError(e)
}
const headerBg = useThemeColor('header')
const headerTitle = useThemeColor('headerTitle')
return (
<Screen contentContainerStyle={$screen} preset='fixed'>
<View style={[$headerContainer, {backgroundColor: headerBg}]}>
<Text preset="heading" tx="updateScreen_updateManagerTitle" style={{color: headerTitle}} />
</View>
<ScrollView style={$contentContainer}>
<Card
style={$card}
ContentComponent={
<>
{isUpdateAvailable && (
<>
<ListItem
tx='updateScreen_updateAvailable'
subTx='updateScreen_updateAvailableDesc'
leftIcon='faWandMagicSparkles'
leftIconColor={colors.palette.iconMagenta200}
leftIconInverse={true}
style={$item}
/>
<ListItem
tx='updateScreen_updateNew'
subText={updateDescription || translate("updateScreen_defaultUpdateDesc")}
leftIcon='faInfoCircle'
leftIconColor={colors.palette.neutral500}
topSeparator={true}
style={$item}
/>
{/*<ListItem
tx='updateScreen_updateSize'
subText={updateSize || '1.0MB'}
leftIcon='faDownload'
leftIconColor={colors.palette.neutral500}
topSeparator={true}
style={$item}
/>*/}
</>
)}
{isNativeUpdateAvailable && (
<ListItem
tx='updateScreen_updateAvailable'
subTx={'updateScreen_nativeUpdateAvailableDesc'}
leftIcon='faWandMagicSparkles'
leftIconColor={colors.palette.iconMagenta200}
leftIconInverse={true}
style={$item}
/>)}
{(!isUpdateAvailable && !isNativeUpdateAvailable) && (
<ListItem
tx='updateScreen_updateNotAvailable'
subTx={'updateScreen_updateNotAvailableDesc'}
leftIcon='faInfoCircle'
leftIconColor={colors.palette.neutral500}
style={$item}
/>
)}
</>
}
FooterComponent={
<>
{isUpdateAvailable && (
<Button
onPress={handleUpdate}
tx='updateScreen_updateNow'
style={{alignSelf: 'center', marginTop: spacing.medium}}
/>
)}
{isNativeUpdateAvailable && (
<View style={$buttonContainer}>
<Button
onPress={gotoPlayStore}
tx='updateScreen_gotoPlayStore'
style={{marginTop: spacing.medium, marginRight: spacing.small}}
/>
<Button
onPress={gotoGithub}
tx="updateScreen_apkOnGithub"
preset='secondary'
style={{marginTop: spacing.medium}}
/>
</View>
)}
</>
}
/>
{isLoading && <Loading />}
</ScrollView>
<BottomModal
isVisible={isUpdateModalVisible ? true : false}
style={{alignItems: 'stretch'}}
ContentComponent={
<ResultModalInfo
icon='faDownload'
iconColor={colors.palette.accent400}
title={`${progressPercent}%`}
message={translate('updateScreen_updateInProgress')}
/>
}
onBackButtonPress={toggleUpdateModal}
onBackdropPress={toggleUpdateModal}
/>
{error && <ErrorModal error={error} />}
{info && <InfoModal message={info} />}
</Screen>
)
})
const $screen: ViewStyle = {}
const $headerContainer: TextStyle = {
alignItems: 'center',
paddingBottom: spacing.medium,
height: spacing.screenHeight * 0.15,
}
const $buttonContainer: ViewStyle = {
flexDirection: 'row',
alignSelf: 'center',
marginTop: spacing.medium,
}
const $contentContainer: TextStyle = {
flex: 1,
marginTop: -spacing.extraLarge * 2,
padding: spacing.extraSmall,
}
const $card: ViewStyle = {
marginBottom: 0,
}
const $bottomModal: ViewStyle = {
flex: 1,
alignItems: 'center',
paddingVertical: spacing.large,
paddingHorizontal: spacing.small,
}
const $item: ViewStyle = {
paddingHorizontal: spacing.small,
paddingLeft: 0,
}
const $rightContainer: ViewStyle = {
// padding: spacing.extraSmall,
alignSelf: 'center',
//marginLeft: spacing.small,
}