RN code push自定義彈框

最近在弄react native的code push熱更新問題。開始是用的後臺默默更新配置。因爲微軟服務器速度問題,常常遇到用戶一直在下載中問題。而用戶也不知道代碼須要更新才能使用新功能,影響了正常業務流程。而目前公司也無力搭建本身的服務器和dns設置。因此比較快速的方案就是,前端自定義熱更新彈框,在須要更新代碼的狀況下禁止用戶向下操做。javascript

ok,廢話少說,直接上代碼:前端

這是構建一個彈框,強制文案提示和非強制文案提示彈框。java

/**
 * Created by susie on 2018/9/20.
 */
import React, { Component } from 'react';
import {View, Text, StyleSheet, Modal, TouchableOpacity, Image , Dimensions , Alert} from 'react-native'
import CodePush from "react-native-code-push"
import Progress from './CusProgressBar';
import color from '../../styles/theme';
import {showLoadingImg,hideLoadingImg,px2pt} from '../../utils/util';
import Global from "../../constants/global";


let SCREEN_WIDTH = Dimensions.get('window').width;//寬
let SCREEN_HEIGHT = Dimensions.get('window').height;//高

let codePushOptions = {
    checkFrequency : CodePush.CheckFrequency.ON_APP_START,
    installMode: CodePush.InstallMode.IMMEDIATE
}

 class CodePushModal extends Component {

    constructor(props) {
        super(props)
        this.currProgress = 0.0
        this.syncMessage = ''
        this.state = {
            modalVisible: false, //是否有更新
            isMandatory: false, //是否爲強制更新
            immediateUpdate: false, //是否在更新中
            updateInfo: {}
        }
    }

    codePushStatusDidChange(syncStatus) {
        if (this.state.immediateUpdate) {
            switch(syncStatus) {
                case CodePush.SyncStatus.CHECKING_FOR_UPDATE:
                    this.syncMessage = 'Checking for update'
                    break;
                case CodePush.SyncStatus.DOWNLOADING_PACKAGE:
                    this.syncMessage = 'Downloading package'
                    break;
                case CodePush.SyncStatus.AWAITING_USER_ACTION:
                    this.syncMessage = 'Awaiting user action'
                    break;
                case CodePush.SyncStatus.INSTALLING_UPDATE:
                    this.syncMessage = 'Installing update'
                    break;
                case CodePush.SyncStatus.UP_TO_DATE:
                    this.syncMessage = 'App up to date.'
                    break;
                case CodePush.SyncStatus.UPDATE_IGNORED:
                    this.syncMessage = 'Update cancelled by user'
                    break;
                case CodePush.SyncStatus.UPDATE_INSTALLED:
                    this.syncMessage = 'Update installed and will be applied on restart.'
                    break;
                case CodePush.SyncStatus.UNKNOWN_ERROR:
                    this.syncMessage = 'An unknown error occurred'
                    //Toast.showError('更新出錯,請重啓應用!')
                    this.setState({modalVisible: false})
                    CodePush.allowRestart();
                    break;
            }
        }
    }

    codePushDownloadDidProgress(progress) {
        var self = this;
        if(self.state.immediateUpdate){
            self.currProgress = parseFloat(progress.receivedBytes / progress.totalBytes).toFixed(2);
            if(self.currProgress >= 1) {
                self.setState({modalVisible: false})
            } else if(self.refs.progressBar) {
                self.refs.progressBar.progress = self.currProgress;
                self.refs.progressBar.buffer = self.currProgress;
            }
        }
    }

     syncImmediate() {
        CodePush.checkForUpdate().then((update) => {
            Global.isCheckCodePush = false;
            hideLoadingImg();
            if (!update) {
                CodePush.allowRestart();
            } else {
                this.setState({modalVisible: true, updateInfo: update, isMandatory: update.isMandatory})
            }
        }).catch(function () {
            Global.isCheckCodePush = false;
            CodePush.allowRestart();
        })
    }

    componentWillMount() {
        Global.isCheckCodePush = true;
        showLoadingImg();
        CodePush.disallowRestart()
        this.syncImmediate()
    }

    componentDidMount() {
        //CodePush.allowRestart()
    }

    _immediateUpdateNew() {
        this.setState({immediateUpdate: true});
        let self = this;
        var timer = setTimeout(function () {
            CodePush.sync(
                {
                    updateDialog: {},
                    installMode: CodePush.InstallMode.IMMEDIATE},
                self.codePushStatusDidChange.bind(self),
                self.codePushDownloadDidProgress.bind(self)
            )
            clearTimeout(timer);
            CodePush.allowRestart();
        },10);
    }

    render() {
        return (
            <View style={styles.container}>
            <Modal
                animationType={"none"}
                transparent={true}
                onRequestClose={() => {}}
                visible={this.state.modalVisible}
            >
                <View style={styles.modal}>
                    <View style={styles.modalContainer}>
                        {
                            !this.state.immediateUpdate ?
                                <View>
                                    <View style={styles.modalContent}>
                                        <View>
                                            <Text style={styles.modalTitle}>頁面升級</Text>
                                        </View>
                                        <View style={styles.updateDes}>
                                            <Text style={styles.updateDesText}>升級內容:</Text>
                                            <Text style={styles.updateDesText}>{this.state.updateInfo.description}</Text>
                                        </View>
                                        <View style={styles.updateTip}>
                                            <Text style={styles.updateTipText}>本升級非APP更新,wifi環境下30s內便可完成</Text>
                                        </View>
                                        {
                                            !this.state.isMandatory ?
                                                <View style={styles.updateBtns}>
                                                    <TouchableOpacity
                                                        onPress={() => this.setState({modalVisible: false})}>
                                                        <View style={[styles.btnRight,styles.btnLeft]}>
                                                            <Text style={{fontSize: 16, color: '#989898'}}>殘忍拒絕</Text>
                                                        </View>
                                                    </TouchableOpacity>
                                                    <TouchableOpacity
                                                        style={styles.btnRight}
                                                        onPress={() => this._immediateUpdateNew()}
                                                    >
                                                        <View style={styles.btnRightText}>
                                                            <Text style={{fontSize: 16, color: color.theme}}>當即升級</Text>
                                                        </View>
                                                    </TouchableOpacity>
                                                </View> :
                                                <View style={styles.updateBtns}>
                                                    <TouchableOpacity
                                                        style={[styles.btnRight,styles.onlyBtn]}
                                                        onPress={() => this._immediateUpdateNew()}
                                                    >
                                                        <View style={[styles.btnRightText,{marginHorizontal: 40}]}>
                                                            <Text style={{fontSize: 16, color: color.theme, letterSpacing:1}}>當即升級</Text>
                                                        </View>
                                                    </TouchableOpacity>
                                                </View>
                                        }
                                    </View>
                                </View> : <View style={styles.modalContent}>
                                        <View>
                                            <Text style={styles.modalTitle}>頁面升級中</Text>
                                        </View>
                                        <View style={{ paddingVertical: 20, alignItems: 'center'}}>
                                            <Progress
                                                ref="progressBar"
                                                progressColor={'#89C0FF'}
                                                style={{
                                                    marginTop: 20,
                                                    width: SCREEN_WIDTH - 80,
                                                    marginLeft:10,
                                                    marginRight:10
                                                }}
                                            />
                                            <View style={styles.updateTip}>
                                                <Text style={styles.updateTipText}>本升級非APP更新,wifi環境下30s內便可完成</Text>
                                            </View>
                                        </View>
                                </View>

                        }
                    </View>
                </View>
            </Modal>
            </View>
        );
    }
}
let modalW = SCREEN_WIDTH - 40;
const styles = StyleSheet.create({
    container: {
        flex: 1,
        justifyContent: 'center',
        alignItems: 'center'
    },
    modal: {
        height: SCREEN_HEIGHT,
        width: SCREEN_WIDTH,
        alignItems: 'center',
        justifyContent: 'center',
        backgroundColor: 'rgba(0,0,0,0.3)'
    },
    modalContainer: {
        marginHorizontal: px2pt(120),
        borderBottomLeftRadius: px2pt(20),
        borderBottomRightRadius: px2pt(20),
    },
    modalContent:{
        backgroundColor: '#FFF',
        borderRadius:5,
        width: modalW
    },
    modalTitle:{
        marginTop:px2pt(70),
        fontSize:px2pt(36),
        color:color.gray3,
        textAlign:'center',
        width:'100%'
    },
    modalTips:{
        fontSize:px2pt(28),
        color:color.darkOrange,
        textAlign:'center',
        marginBottom:px2pt(10)
    },
    updateDes:{
        marginLeft:px2pt(40),
        marginRight:px2pt(40),
        marginTop:px2pt(30)
    },
    updateDesText:{
        fontSize:px2pt(32),
        color: color.gray6,
        lineHeight:px2pt(44)
    },
    updateTip:{
        alignItems: 'center',
        marginTop: px2pt(40)
    },
    updateTipText:{
        fontSize: px2pt(28),
        color: color.gray6
    },
    updateBtns:{
        flexDirection: 'row',
        height: px2pt(100),
        alignItems: 'center',
        marginTop: px2pt(40),
        borderTopColor: '#E6E6E6',
        borderTopWidth: 1
    },
    btnLeft:{
        borderRightColor: '#E6E6E6',
        borderRightWidth: 1
    },
    btnRight:{
        flexDirection: 'row',
        alignItems: 'center',
        width: modalW / 2,
        height:px2pt(100),
        justifyContent: 'center'
    },
    btnRightText:{
        flex: 1,
        height:px2pt(80),
        alignItems: 'center',
        justifyContent: 'center'
    },
    onlyBtn:{
        width: modalW
    }
})

export default CodePush(codePushOptions)(CodePushModal)

  其中,注意installMode 的設置問題,在初始化CodePush參數時,就要設置。不然在非強制更新下會出現 不當即刷新的問題。node

而後是進度條展現處理:react

/**
 * Created by susie on 2018/9/20.
 */
import React, {Component}from 'react'
import {View, StyleSheet, Animated, Easing,Text}from 'react-native'
import LinearGradient from 'react-native-linear-gradient'

import PropTypes from 'prop-types'

export default class CusProgressBar extends Component {

    static propTypes = {
        ...View.propTypes,
        // 當前進度
        progress: PropTypes.number,
        // second progress進度
        buffer: PropTypes.number,
        // 進度條顏色
        progressColor: PropTypes.string,
        // 進度動畫時長
        progressAniDuration: PropTypes.number,
        // buffer動畫時長
        bufferAniDuration: PropTypes.number
    }

    static defaultProps = {
        // 進度條顏色
        progressColor: 'white',
        // 進度條動畫時長
        progressAniDuration: 100,
        // buffer進度條動畫時長
        bufferAniDuration: 100
    }

    constructor(props) {
        super(props)
        this._progressAni = new Animated.Value(0)
        this._bufferAni = new Animated.Value(0)
    }

    componentWillReceiveProps(nextProps) {
        this._progress = nextProps.progress
        this._buffer = nextProps.buffer
    }

    componentWillMount() {
        this._progress = this.props.progress
        this._buffer = this.props.buffer
    }

    render() {
        return (
            <View
                style={[styles.container,this.props.style]}
                onLayout={this._onLayout.bind(this)}>
                <LinearGradient colors={['#daddff', '#d3eeff']} start={{ x: 0, y: 0 }} end={{ x: 1, y: 0 }} style={{position:'absolute',borderRadius:10,width:'100%',height:'100%'}}></LinearGradient>
                <Animated.View
                    ref="progress"
                    style={{
                        position:'absolute',
                        width: this._progressAni,
                        borderRadius:10
                    }}>
                    <LinearGradient colors={['#4669ff', '#3eefff']} start={{ x: 0, y: 0 }} end={{ x: 1, y: 0 }} style={{borderRadius:10,width:'100%',height:'100%'}}></LinearGradient>
                </Animated.View>
                <Animated.Image
                    ref="buffer"
                    style={{
                        position:'absolute',
                        left: this._bufferAni,
                        marginLeft:0,
                        top : -3,
                        width:35
                    }}  source={require('../../styles/images/huojian.png')}  />
            </View>
        )
    }

    _onLayout({nativeEvent: {layout:{width, height}}}) {
        // 防止屢次調用,當第一次獲取後,後面就再也不去獲取了
        if (width > 0 && this.totalWidth !== width) {
            // 獲取progress控件引用
            let progress = this._getProgress()
            // 獲取buffer控件引用
            let buffer = this._getBuffer()
            // 獲取父佈局寬度
            this.totalWidth = width
            //給progress控件設置高度
            progress.setNativeProps({
                style: {
                    height: height
                }
            })

            // 給buffer控件設置高度
            buffer.setNativeProps({
                style: {
                    height: height+6
                }
            })
        }
    }

    _startAniProgress(progress) {
        if (this._progress >= 0 && this.totalWidth !== 0) {
            Animated.timing(this._progressAni, {
                toValue: progress * this.totalWidth,
                duration: this.props.progressAniDuration,
                easing: Easing.linear
            }).start()
        }
    }

    _startAniBuffer(buffer) {
        if (this._buffer >= 0 && this.totalWidth !== 0) {
            Animated.timing(this._bufferAni, {
                toValue: buffer * this.totalWidth,
                duration: this.props.bufferAniDuration,
            }).start()
        }
    }

    _getProgress() {
        if (typeof this.refs.progress.refs.node !== 'undefined') {
            return this.refs.progress.refs.node
        }
        return this.refs.progress._component
    }

    _getBuffer() {
        if (typeof this.refs.buffer.refs.node !== 'undefined') {
            return this.refs.buffer.refs.node;
        }
        return this.refs.buffer._component;
    }
}

Object.defineProperty(CusProgressBar.prototype, 'progress', {
    set(value){
        if (value >= 0 && this._progress !== value) {
            this._progress = value;
            this._startAniProgress(value);
        }
    },
    get() {
        return this._progress;
    },
    enumerable: true,
})

Object.defineProperty(CusProgressBar.prototype, 'buffer', {
    set(value){
        if (value >= 0 && this._buffer !== value) {
            this._buffer = value;
            this._startAniBuffer(value);
        }
    },
    get() {
        return this._buffer;
    },
    enumerable: true,
})

const styles = StyleSheet.create({
    container: {
        height: 12
    }
})

 以上就完成了自定義 code push彈框的自定義展現。react-native

相關文章
相關標籤/搜索