phodal/growth

View on GitHub
src/containers/practises/leetcode/LeetCodeView.js

Summary

Maintainability
D
1 day
Test Coverage
import React, { Component } from 'react';
import {
  Text,
  View,
  ScrollView,
  StyleSheet,
  Dimensions,
} from 'react-native';
import Carousel from 'react-native-looped-carousel';
import Modal from 'react-native-modalbox';
import RNFetchBlob from 'react-native-fetch-blob';
import Toast from 'react-native-simple-toast';
import * as Progress from 'react-native-progress';
import { unzip } from 'react-native-zip-archive';
import RNFS from 'react-native-fs';
import HTMLView from 'react-native-htmlview';

import AsyncStorageHelper from '../../../utils/AsyncStorageHelper';
import MarkdownHelper from '../../../utils/MarkdownHelper';

const DIR = RNFetchBlob.fs.dirs;
const LEETCODE_PATH = '/growth-leetcode-api-master';
const LeetCodeUrl = 'https://github.com/phodal/growth-leetcode-api/archive/master.zip';
const { width, height } = Dimensions.get('window');
const styles = StyleSheet.create({
  wrapper: {
    paddingTop: 50,
    flex: 1,
  },
  text: {
    paddingBottom: 50,
  },
  modal: {
    justifyContent: 'center',
    alignItems: 'center',
    height: 300,
    width: 300,
  },
});

export default class LeetCodeView extends Component {
  static componentName = 'LeetCodeView';
  static modal = null;

  constructor(props) {
    super(props);

    this.state = {
      progress: 0.001,
      questions: null,
      question: null,
      nextQuestion: null,
      size: { width, height },
      hasDownloaded: false,
    };
  }

  componentWillMount() {
    AsyncStorageHelper.get('leetcode.index', (err, result) => {
      let index = 0;
      if (result !== null) {
        index = parseInt(result, 10);
      }

      this.setState({
        index,
      });
    });

    AsyncStorageHelper.get('leetcode.downloaded', (err, result) => {
      if (result === 'true') {
        this.setState({
          hasDownloaded: true,
        });
      }
    });
  }

  componentDidMount() {
    const that = this;
    AsyncStorageHelper.get('leetcode.downloaded', (err, result) => {
      if (result !== 'true') {
        this.modal.open();
        this.fetchFile(LeetCodeUrl, 'leetcode');
      } else {
        AsyncStorageHelper.get('leetcode.questions', (error, questions) => {
          if (questions) {
            that.setState({
              questions: JSON.parse(questions),
            });
            this.loadQuestion();
          } else {
            that.loadQuestionsToDB();
          }
        });
      }
    });
  }

  onLayoutDidChange = (e) => {
    const layout = e.nativeEvent.layout;
    this.setState({ size: { width: layout.width, height: layout.height } });
  };

  loadQuestionsToDB() {
    const leetCodeDir = DIR.DocumentDir.concat(`${LEETCODE_PATH}/api.json`);
    console.log(leetCodeDir);

    RNFS.readFile(leetCodeDir, 'utf8')
    .then((json) => {
      this.setState({
        questions: JSON.parse(json),
      });

      AsyncStorageHelper.set('leetcode.questions', json);
    }).catch((error) => {
      Toast.show(error);
      AsyncStorageHelper.set('leetcode.downloaded', 'false');
    });
  }

  fetchFile(url, fileName) {
    RNFetchBlob
    .config({
      path: `${DIR.DocumentDir}/${fileName}.zip`,
    })
    .fetch('GET', url, {})
    .progress((received, total) => {
      const progressWithoutRound = received / total;

      let progress = Math.round(progressWithoutRound * 1e2) / 1e2;
      if (progress > 1) {
        progress = 1;
      }
      this.setState({ progress });
    })
    .then((res) => {
      const now = new Date();
      console.log(`${now} 下载成功`);
      console.log(res.path());

      this.unzipFiles(res);

      AsyncStorageHelper.set('leetcode.downloaded', 'true');
      this.setState({ hasDownloaded: true });
    })
    .catch((err) => {
      Toast.show(`下载失败 ${err}`);
    });
  }

  unzipFiles(res) {
    unzip(res.path(), `${DIR.DocumentDir}`).then(() => {
      this.loadQuestionsToDB();
      AsyncStorageHelper.set('leetcode.unzip', 'true');
    }).catch((err) => {
      console.log(`解压失败 ${err}`);
      Toast.show(`解压失败 ${err}`);
    });
  }

  loadQuestion() {
    const path = this.state.questions[this.state.index].path;
    const questionPath = DIR.DocumentDir.concat(`${LEETCODE_PATH}/${path}`);
    RNFS.readFile(questionPath, 'utf8')
      .then((question) => {
        this.setState({
          question: MarkdownHelper.convert(question),
        });
      }).catch((error) => {
        Toast.show(error);
      });

    const question2Index = this.state.index + 1;
    const path2 = this.state.questions[question2Index].path;
    const NextQuestionPath = DIR.DocumentDir.concat(`${LEETCODE_PATH}/${path2}`);
    RNFS.readFile(NextQuestionPath, 'utf8')
      .then((nextQuestion) => {
        this.setState({
          nextQuestion: MarkdownHelper.convert(nextQuestion),
        });
      }).catch((error) => {
        Toast.show(error);
      });
  }

  reloadQuestion() {
    const index = this.state.index;
    this.setState({
      index: index + 1,
      question: this.state.nextQuestion,
    });

    const path2 = this.state.questions[this.state.index + 1].path;
    const NextQuestionPath = DIR.DocumentDir.concat(`${LEETCODE_PATH}/${path2}`);
    RNFS.readFile(NextQuestionPath, 'utf8')
      .then((nextQuestion) => {
        this.setState({
          nextQuestion: MarkdownHelper.convert(nextQuestion),
        });
      }).catch((error) => {
        Toast.show(error);
      });
  }

  render() {
    const hasDownloaded = this.state.hasDownloaded;
    const questions = this.state.questions;

    if (!hasDownloaded || !questions) {
      return (
        <Modal
          style={[styles.modal]}
          position={'center'}
          ref={(modal) => { this.modal = modal; }}
        >
          <Text style={styles.text}>正在为您下载</Text>
          <Progress.Circle
            textStyle={{ color: '#03a9f4' }}
            progress={this.state.progress}
            indeterminate
            showsText
            size={80}
          />
        </Modal>
      );
    }

    return (
      <View style={{ flex: 1 }} onLayout={this.onLayoutDidChange}>
        <Carousel
          delay={2000}
          style={this.state.size}
          autoplay={false}
          pageInfo={false}
          onAnimateNextPage={() => (this.reloadQuestion())}
        >
          <ScrollView style={{ width }}>
            <HTMLView
              value={this.state.question}
              addLineBreaks={false}
              style={{ padding: 10, borderBottomWidth: 1, backgroundColor: '#fff', borderBottomColor: '#ddd' }}
            />
          </ScrollView>
          <ScrollView style={{ width }}>
            <HTMLView
              value={this.state.question}
              addLineBreaks={false}
              style={{ padding: 10, borderBottomWidth: 1, backgroundColor: '#fff', borderBottomColor: '#ddd' }}
            />
          </ScrollView>
        </Carousel>
      </View>
    );
  }
}