感受這個是頗有才華的博主,畢竟是能夠在npm 包裏面留後門的程序員 博主的gihtub關於這個項目的地址是:https://github.com/ikimiler/react-native-video-project 運行出來了項目我十分興奮,由於項目很完整,先不去想複雜不復雜,可是看到這樣的項目會很感恩開源的程序員 先看效果圖 react
項目的界面大概是如上面的樣子 佈局不少相似,可是看到項目就會很開心 接下來咱們分析項目android
//index.js定義了入口爲App.js,而後有數據處理這些 import React from 'react' import { AppRegistry, StatusBar, View } from 'react-native'; import {} from './src/utils/ScreenUtils' import { Provider } from 'react-redux' import Store from './src/utils/ConfigRedux' import CodePush from 'react-native-code-push' import App from './App' class Root extends React.Component { render() { return ( <Provider store={Store}> <App /> </Provider> ); } } // var wrapper = CodePush({ // checkFrequency: CodePush.CheckFrequency.ON_APP_RESUME, // installMode: CodePush.InstallMode.ON_NEXT_RESTART // })(Root); AppRegistry.registerComponent('colavideoapp', () => Root);
//src/utils/ScreenUtils.js //判斷手機型號,縮放比例 import {Dimensions,PixelRatio,StatusBar,Platform} from 'react-native' // 設備的像素密度,例如: // PixelRatio.get() === 1 mdpi Android 設備 (160 dpi) // PixelRatio.get() === 1.5 hdpi Android 設備 (240 dpi) // PixelRatio.get() === 2 iPhone 4, 4S,iPhone 5, 5c, 5s,iPhone 6,xhdpi Android 設備 (320 dpi) // PixelRatio.get() === 3 iPhone 6 plus , xxhdpi Android 設備 (480 dpi) export const window = Dimensions.get("window") export const screen= Dimensions.get("screen") const defaultWidth = 1080,defaultHeight = 1920,defaultRatio = 3; //px to dp const w2 = defaultWidth / defaultRatio; const h2 = defaultHeight / defaultRatio; //獲取縮放比例 const scale = Math.min(window.height / h2, window.width / w2); function dp(number){ let size = Math.round(number * scale + 0.5) / defaultRatio; return size } // iPhoneX Xs const X_WIDTH = 375; const X_HEIGHT = 812; // iPhoneXR XsMax const XR_WIDTH = 414; const XR_HEIGHT = 896; // screen const SCREEN_WIDTH = Dimensions.get('window').width; const SCREEN_HEIGHT = Dimensions.get('window').height; //判斷是否爲iphoneX或Xs function isIphoneX() { return ( Platform.OS === 'ios' && ((SCREEN_HEIGHT === X_HEIGHT && SCREEN_WIDTH === X_WIDTH) || (SCREEN_HEIGHT === X_WIDTH && SCREEN_WIDTH === X_HEIGHT)) ) } //判斷是否爲iphoneXR或XsMAX function isIphoneXR() { return ( Platform.OS === 'ios' && ((SCREEN_HEIGHT === XR_HEIGHT && SCREEN_WIDTH === XR_WIDTH) || (SCREEN_HEIGHT === XR_WIDTH && SCREEN_WIDTH === XR_HEIGHT)) ) } global.dp = dp; global.DEVICE = { width:window.width, height:window.height, screenWidth: Platform.OS == 'ios'? window.width : screen.width, screenHeight:Platform.OS == 'ios'? window.height : screen.height, StatusBarHeight: StatusBar.currentHeight, android:Platform.OS === 'android', ios:Platform.OS == 'ios', isIphoneX:isIphoneX() | isIphoneXR(), }
//src/utils/ConfigRedux.js //定義了redux的公共入口 import {createStore,combineReducers,applyMiddleware} from 'redux' import promiseMiddleware from 'redux-promise-middleware' function reducer(state ={},action){ return {} } const reducers = combineReducers({ index:reducer }) const store = createStore(reducers,applyMiddleware(promiseMiddleware)) export default store;
//src/views/MainTabNavigatorHeader.js //設置的公共的搜索頭部 import React from 'react' import { Text, View, Image, TouchableOpacity } from 'react-native' import Header, { HeaderItem } from '../components/Header' export default class MainTabNavigatorHeader extends React.Component { _enterSearchPage = () => { this.props.navigation.navigate('SearchPage') } render() { return ( <Header> <HeaderItem onClick={() => this.props.navigation.navigate('PersonCenterPage')}> <Image source={require('../../source/image/main_my.png')}></Image> </HeaderItem> <TouchableOpacity onPress={this._enterSearchPage} activeOpacity={1} style={[{ flex: 1, height: 35, borderRadius: 5, backgroundColor: 'rgba(0,0,0,0.1)', justifyContent: 'center', alignItems: 'center' },this.props.centerStyle]}> <Text>搜一搜,全都有</Text> </TouchableOpacity> {this.props.rightIcon && <HeaderItem onClick={() => this.props.onRightClick()}> <Image source={this.props.rightIcon}></Image> </HeaderItem>} </Header> ); } }
//src/pages/OfflineVideoPlayer.js //點擊進去的視頻頁面 import React from 'react' import { View, StyleSheet, ScrollView, Text, TouchableOpacity, Image, Share, ListView, NativeModules } from 'react-native' import BaseComponent from '../components/BaseComponent' import VideoWrapper from '../views/VideoWrapper' import { writeHistoryVideo, queryCollectVideo, deleteCollectVideo, writeCollectVideo } from '../utils/DButils' import Colors from '../utils/Colors' import Toast from 'react-native-root-toast' import DownloadManager from '../utils/DownloadManager' import Loadding from '../components/Loadding' export default class OfflineVideoPlayer extends BaseComponent { state = { data: null, } initData(){ let item = this.props.navigation.state.params.data; this.setState({data:item},() => this.update(this.LOAD_SUCCESS)) } _renderHeader() { let item = this.state.data; if(!item) return null; return ( <VideoWrapper item={item} navigation={this.props.navigation} onProgress={options => this.progressOption = options} onLoad={data => { }} onEnd={() => { }} /> ) } renderComponent() { let data = this.state.data; let playCount = parseInt(data.playCount) if (playCount > 10000) { playCount = (playCount / 10000).toFixed(1) + '萬' } let title = data.title + (data.index > 0 ? ` 第${data.index}集` : "") return ( <View style={{ flex: 1, backgroundColor: 'white' }}> <ScrollView style={{ flex: 1 }} contentContainerStyle={{ padding: 10 }}> <Text style={[styles.itemStyle, { fontSize: 18, color: 'black' }]}>{title}</Text> <View style={styles.itemBetweenStyle}> <Text>播放: {playCount}次</Text> <Text style={[styles.buttonStyle, { backgroundColor: Colors.mainColor }]}>豆瓣: {data.imdbScore > 0 ? data.imdbScore : '6.0'}</Text> </View> <Text style={styles.itemStyle}>分類: {data.classifyTypeListValue}</Text> <Text style={styles.itemStyle}>導演: {data.director}</Text> <Text style={styles.itemStyle}>演員: {data.staring}</Text> <Text style={styles.itemStyle}>簡介: </Text> <Text style={[styles.itemStyle, { marginTop: 10 }]}> {data.intro}</Text> </ScrollView> </View> ); } } var styles = StyleSheet.create({ itemBetweenStyle: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', minHeight: 40, }, itemStyle: { flexDirection: 'row', alignItems: 'center', minHeight: 30, textAlignVertical: 'center' }, buttonStyle: { backgroundColor: '#EFF0EB', borderRadius: 5, paddingHorizontal: 10, paddingVertical: 5, textAlign: 'center', textAlignVertical: 'center', color: 'white' }, bottomStyle: { flexDirection: 'row', height: 45, alignItems: 'center' }, bottomImageStyle: { width: 20, height: 20 } })
//src/utils/DownloadManager.js //點擊下載的功能 import fs from 'react-native-fs' import { queryDownloadVideoAll, writeDownloadVideo,deleteDownloadVideo } from '../utils/DButils' import Toast from 'react-native-root-toast' const baseFile = DEVICE.android ? fs.ExternalStorageDirectoryPath + "/ColaApp" : fs.LibraryDirectoryPath + "/ColaApp"; fs.exists(baseFile).then(exists => { if (!exists) { fs.mkdir(baseFile) } }) class DownloadManager { /** * 刪除文件 * @param {*} data */ deleteCacheVideo(data) { return new Promise(async function(resolve,reject) { try { let file = data.file; let exitis = await fs.exists(file) if (exitis) { let lastIndex = file.lastIndexOf("/") let dir = file.substring(0, lastIndex) await fs.unlink(dir) //同時刪除數據庫的記錄 await deleteDownloadVideo(data) resolve(true) } else { reject(false) } } catch (error) { console.log('netlog-', error) reject(false) } }) } checkSafeUrl(data) { let url = data.url; if (!url.startsWith("http://") && !url.startsWith("https://")) { Toast.show("非法的下載連接") return false; } else if (url.indexOf('.m3u8') > -1) { if (url.indexOf('?') > -1) { data.url = url.substring(0, url.indexOf('?')) } return true; } Toast.show("暫不支持此格式視頻") return false; } /** * 正在進行中的任務 * { * maxProgress: 100, progress: 0, status:0, // 0 運行中 -1 失敗 2成功 toFile:toFile, * } */ allRunningTask = new Map() listeners = new Set(); addListener(listener = () => { }) { this.listeners.add(listener) } removeListener(listener = () => { }) { if (this.listeners.has(listener)) { this.listeners.delete(listener) } } /** * 觀察者模式,對外發送通知 */ _updatelisteners() { for (let listener of this.listeners) { listener && listener(); } } downLoad(data) { let result = this.checkSafeUrl(data); if (result) { this.startDownloadM3U8(data) } } resetDownLoad(data) { let result = this.checkSafeUrl(data); if (result) { if (this.allRunningTask.has(data.id)) { this.allRunningTask.delete(data.id) } this.startDownloadM3U8(data) } } /** * 開始下載m3u8文件 * @param {*} url */ async startDownloadM3U8(data) { console.log('netlog-download', data.id) //已經存在了,直接return if (this.allRunningTask.has(data.id)) { if (this.allRunningTask.get(data.id).status == 0) { Toast.show("任務已經在下載隊列中了,請不要重複下載") return; } else if (this.allRunningTask.get(data.id).status == 2) { Toast.show("您已經下載過該視頻,請不要重複下載") return; } } //查詢本地已經下載成功的視頻 let videos = await queryDownloadVideoAll(); let keys = Object.keys(videos) let localFile; for (let i = 0; i < keys.length; i++) { let obj = videos[keys[i]] if (obj && obj.id == data.id) { localFile = obj.file; } } if (localFile) { let flag = await fs.exists(localFile) if (flag) { Toast.show("您已經下載過該視頻,請不要重複下載") return; } } Toast.show("開始下載...") //根據url獲取到對應的本地目錄 let url = data.url; let urlSplits = url.split("/"); let scheme = urlSplits[0]; let baseUrl = urlSplits[2]; let path = urlSplits.slice(3, urlSplits.length - 1).join("/") let fileName = urlSplits[urlSplits.length - 1] let toDirPath = baseFile + "/" + path; await fs.mkdir(toDirPath) let toFile = toDirPath + "/" + fileName; data.maxProgress = 100; data.progress = 0; data.status = 0; data.toFile = toFile; data.file = toFile; //添加m3u8下載任務到緩存 this.allRunningTask.set(data.id, data) //開始下載m3u8文件 let task = fs.downloadFile({ fromUrl: url, toFile: toFile, connectionTimeout: 1000 * 60, readTimeout: 1000 * 60, begin: function (res) { }, progress: function (res) { }, }); let result = await task.promise if (result.statusCode == 200) { console.log('netlog-m3u8下載成功', toFile, url, result) try { //m3u8下載成功,開始逐步下載ts文件 await this.readM3U8File(data, url, toFile, toDirPath) console.log('netlog-', '全部ts文件都下載成功了') //標記下載成功 this.allRunningTask.get(data.id).status = 2; //寫入本地數據庫 await writeDownloadVideo(data) console.log('netlog-', '插入本地數據庫成功了') //刪除內存中緩存 this.allRunningTask.delete(data.id) Toast.show(data.title + "下載成功了,請到下載中心查看") //通知出去 this._updatelisteners() } catch (error) { Toast.show("哎喲,下載出現了異常", error) console.log('netlog-', '哎喲,下載出現了異常', error) this.allRunningTask.get(data.id).status = -1; //通知出去 this._updatelisteners() } } else { console.log('哎喲,下載出現了異常') Toast.show("哎喲,下載出現了異常") this.allRunningTask.get(data.id).status = -1; //通知出去 this._updatelisteners() } } /** * 讀取m3u8對應的內容,獲取到對應的ts文件地址 * @param {*} m3u8Url * @param {*} m3u8File * @param {*} m3u8Dir */ async readM3U8File(data, m3u8Url, m3u8File, m3u8Dir) { let result = await fs.readFile(m3u8File) let lines = result.split('\n'); let tsUrls = []; for (let line of lines) { if (line.endsWith('.ts') || line.indexOf("ts") > -1) { tsUrls.push(line) } } //設置最大進度,默認爲ts文件數爲單位 this.allRunningTask.get(data.id).maxProgress = tsUrls.length; //開始下載ts文件 await this.startDownloadTS(data, m3u8Url, tsUrls, 0, m3u8Dir); } /** * 開始下載ts文件 * @param {*} m3u8Url * @param {*} tsUrls * @param {*} index * @param {*} m3u8Dir */ async startDownloadTS(data, m3u8Url, tsUrls, index, m3u8Dir) { if (index >= tsUrls.length) { return; }; // if (index >= 10) { // return; // }; let url = tsUrls[index]; //若是ts文件中包含路徑,當文件夾形式處理 if (url.lastIndexOf("/") > -1) { let targetDir = m3u8Dir + "/" + url.substring(0, url.lastIndexOf('/')) let exists = await fs.exists(targetDir) if (!exists) { await fs.mkdir(targetDir) } } let downloadUrl = m3u8Url.substring(0, m3u8Url.lastIndexOf("/") + 1) + url; let toFile = m3u8Dir + "/" + url; let result = await this.createDownloadTSPromise(downloadUrl, toFile) console.log('netlog-ts下載成功了', toFile, downloadUrl, result) //刷新進度 this.allRunningTask.get(data.id).progress = index + 1; //通知出去 this._updatelisteners() await this.startDownloadTS(data, m3u8Url, tsUrls, index + 1, m3u8Dir) } /** * 建立ts下載任務 * @param {*} url * @param {*} file */ createDownloadTSPromise(url, file) { let task = fs.downloadFile({ fromUrl: url, toFile: file, connectionTimeout: 1000 * 60, readTimeout: 1000 * 60, begin: function (res) { }, progress: function (res) { }, }); return task.promise } } const DownloadManagerInstance = new DownloadManager() export default DownloadManagerInstance;
//src/pages/QueryMoreVideoPage.js //點擊進入查看更多頁面 import React from 'react' import { View, Text, Image, StyleSheet, ScrollView, TouchableOpacity } from 'react-native' import BaseFlatListComponent from '../components/BaseFlatListComponent' import Colors from '../utils/Colors' import data from '../../data.json' import config from '../../config.json' const itemWidth = Math.floor((DEVICE.width - 10) / 3); const itemHeight = Math.floor(itemWidth * 1.3) const finalStyle = { width: itemWidth, height: itemHeight } //this.props.id 0推薦 1電影 2電視劇 3動漫 4綜藝 export default class QueryMoreVideoPage extends BaseFlatListComponent { pageSize = 18 numColumns = 3; paramsArray = []; contentContainerStyle = { flexDirection: 'row', flexWrap: 'wrap' } static navigationOptions = options => { return { title: options.navigation.state.params.title } } _initListState() { return { classData: null, } } componentDidMount() { // let url = `/api/app/video/ver2/video/queryClassifyList/2/7?videoType=${this.props.navigation.state.params.id}` // axios.get(url).then(res => { // for (let i = 0; i < res.data.data.length; i++) { // let childList = res.data.data[i].childList; // this.paramsArray.push(""); //默認爲所有 // childList = childList.splice(0, 0, { classifyName: res.data.data[i].classifyName, id: "", selected: true }) // } // //flag 1 最多播放 2最近更新 3最多喜歡 5最高評分 // res.data.data.push({ // childList: [ // { classifyName: '最多播放', id: 1 ,selected:true}, // { classifyName: '最近更新', id: 2 ,selected:false}, // { classifyName: '最多喜歡', id: 3 ,selected:false}, // { classifyName: '最高評分', id: 5 ,selected:false}, // ] // }) // this.paramsArray.push(1); // this.setState({ classData: res.data.data }, () => super.componentDidMount()) // }).catch(error => { // console.log('netlog-', error) // super.componentDidMount() // }) setTimeout(() => { let res = {data:data.ClassTypes} for (let i = 0; i < res.data.data.length; i++) { let childList = res.data.data[i].childList; this.paramsArray.push(""); //默認爲所有 childList = childList.splice(0, 0, { classifyName: res.data.data[i].classifyName, id: "", selected: true }) } //flag 1 最多播放 2最近更新 3最多喜歡 5最高評分 res.data.data.push({ childList: [ { classifyName: '最多播放', id: 1 ,selected:true}, { classifyName: '最近更新', id: 2 ,selected:false}, { classifyName: '最多喜歡', id: 3 ,selected:false}, { classifyName: '最高評分', id: 5 ,selected:false}, ] }) this.paramsArray.push(1); this.setState({ classData: res.data.data }, () => super.componentDidMount()) }, config.delayed); } getRequestAction(pageIndex, pageSize) { return new Promise((resolve,reject) => { setTimeout(() => { resolve({data:data.ClassMoreData}) }, config.delayed); }) } enterDetialPage = data => { data.videoInfoId = data.id; this.props.navigation.navigate("VideoInfoPage", { data }) } getTagName(obj) { return obj.tagName == '無標籤' ? null : obj.tagName } _getTagBackgroundColor = tag => { if (tag == "搶鮮") { return "#573D1B" } else if (tag == "1080P") { return "#C47F14"; } else { return "red" } } renderHeaderItem(item, index) { return item.childList.map((child) => { let bgColor = child.selected ? Colors.mainColor : 'white' let tvColor = child.selected ? 'white' : 'black' return ( <TouchableOpacity style={{ padding: 5, borderRadius: 5, justifyContent: 'center', alignItems: 'center', marginRight: 10, backgroundColor: bgColor }} onPress={() => { this.paramsArray[index] = child.id; let data = [...this.state.classData] for (let i = 0; i < data[index].childList.length; i++) { let z = data[index].childList[i] z.selected = z == child; } this.setState({ classData: data }, () => { this.onRefresh() }) }} activeOpacity={0.7}> <Text style={{ color: tvColor }}>{child.classifyName}</Text> </TouchableOpacity> ); }) } renderFlatViewHeader = () => { if (!this.state.classData) return null; let views = this.state.classData.map((item, index) => { return ( <ScrollView showsHorizontalScrollIndicator={false} horizontal={true} contentContainerStyle={{ alignItems: 'center' }} style={{marginTop:5, paddingLeft: 10 }}> {this.renderHeaderItem(item, index)} </ScrollView> ) }) return ( <View> {views} </View> ) } renderRow = (rowData, sectionID, rowID, highlightRow) => { let obj = rowData; let index = rowID + 1; let style = index % 3 == 2 ? { marginHorizontal: 5, width: itemWidth, alignItems: 'center', marginTop: 10, } : { width: itemWidth, alignItems: 'center', marginTop: 10, } let tagName = obj.tagName == '無標籤' ? null : obj.tagName let tagBackgroundColor = this._getTagBackgroundColor(tagName) let complete = obj.episodeState == 1; let updateTag; if (complete) { if (obj.episodeUploadCount > 1) { updateTag = "已完結"; } } else { updateTag = obj.episodeUploadCount > 1 ? obj.type != 4 ? `更新至${obj.episodeUploadCount}集` : `更新至${obj.episodeUploadCount}期` : null; } let image = obj.coverUrl ? { uri: obj.coverUrl } : require('../../source/image/nor.png') let playCount = parseInt(obj.playCount) if (playCount > 10000) { playCount = (playCount / 10000).toFixed(1) + '萬' } return ( <TouchableOpacity activeOpacity={0.7} onPress={() => this.enterDetialPage(obj)} style={style}> <View style={finalStyle}> <Image style={finalStyle} resizeMode="cover" source={image}></Image> {tagName ? ( <View style={{ position: 'absolute', borderRadius: 2, backgroundColor: tagBackgroundColor, top: 5, right: 5, paddingHorizontal: 5 }}> <Text style={{ color: 'white', fontSize: 12 }}>{tagName}</Text> </View> ) : null} {updateTag ? ( <View style={{ position: 'absolute', width: '100%', bottom: 0, paddingVertical: 5, backgroundColor: 'rgba(0,0,0,0.3)', alignItems: 'center', justifyContent: 'center' }}> <Text style={{ color: 'white', fontSize: 12 }}>{updateTag}</Text> </View> ) : null} </View> <View style={{ paddingVertical: 5 }}> <Text numberOfLines={1} style={{ textAlign: 'center' }}>{obj.title}</Text> <Text numberOfLines={1} style={{ textAlign: 'center' }}>{playCount} 播放</Text> </View> </TouchableOpacity> ) } } ```js //src/pages/VideoListPage.js import React from 'react' import { View, Text, Image, StyleSheet, TouchableOpacity } from 'react-native' import BaseFlatListComponent from '../components/BaseFlatListComponent' import data from '../../data.json' import config from '../../config.json' const itemWidth = Math.floor((DEVICE.width - 10) / 3); const itemHeight = Math.floor(itemWidth * 1.3) const finalStyle = { width: itemWidth, height: itemHeight } export default class VideoListPage extends BaseFlatListComponent { pageSize = 18 numColumns = 3; enbaleRefresh = false; contentContainerStyle = { flexDirection: 'row', flexWrap: 'wrap' } static navigationOptions = options => { return { title: options.navigation.state.params.title } } getRequestAction(pageIndex, pageSize) { return new Promise((resolve,reject) => { setTimeout(() => { resolve({data:data.MoreData}) }, config.delayed); }) } enterDetialPage = data => { this.props.navigation.navigate("VideoInfoPage", { data}) } getTagName(obj) { return obj.tagName == '無標籤' ? null : obj.tagName } _getTagBackgroundColor = tag => { if(tag == "搶鮮"){ return "#573D1B" }else if(tag == "1080P"){ return "#C47F14"; }else{ return "red" } } renderRow = (rowData, sectionID, rowID, highlightRow) => { let obj = rowData; let index = rowID + 1; let style = index % 3 == 2 ? { marginHorizontal: 5, width: itemWidth, alignItems: 'center', marginTop: 10, } : { width: itemWidth, alignItems: 'center', marginTop: 10, } let tagName = obj.tagName == '無標籤' ? null : obj.tagName let tagBackgroundColor = this._getTagBackgroundColor(tagName) let complete = obj.episodeState == 1; let updateTag ; if(complete){ if(obj.episodeUploadCount > 1){ updateTag = "已完結"; } }else{ updateTag = obj.episodeUploadCount > 1 ? obj.type != 4 ? `更新至${obj.episodeUploadCount}集` : `更新至${obj.episodeUploadCount}期` : null; } let image = obj.coverUrl ? {uri : obj.coverUrl} : require('../../source/image/nor.png') let playCount = parseInt(obj.playCount) if(playCount > 10000){ playCount = (playCount / 10000).toFixed(1) + '萬' } return ( <TouchableOpacity activeOpacity={0.7} onPress={() => this.enterDetialPage(obj)} style={style}> <View style={finalStyle}> <Image style={finalStyle} resizeMode="cover" source={image}></Image> {tagName ? ( <View style={{ position: 'absolute', borderRadius: 2, backgroundColor: tagBackgroundColor, top: 5, right: 5, paddingHorizontal: 5 }}> <Text style={{ color: 'white', fontSize: 12 }}>{tagName}</Text> </View> ) : null} {updateTag ? ( <View style={{ position: 'absolute', width: '100%', bottom: 0, paddingVertical: 5, backgroundColor: 'rgba(0,0,0,0.3)', alignItems: 'center', justifyContent: 'center' }}> <Text style={{ color: 'white', fontSize: 12 }}>{updateTag}</Text> </View> ) : null} </View> <View style={{ paddingVertical: 5 }}> <Text numberOfLines={1} style={{ textAlign: 'center' }}>{obj.title}</Text> <Text numberOfLines={1} style={{ textAlign: 'center' }}>{playCount} 播放</Text> </View> </TouchableOpacity> ) } }
//視頻詳情頁 //src/pages/VideoInfoPage.js import React from 'react' import { View, StyleSheet, ScrollView, Text, TouchableOpacity, Image, Share, ListView ,NativeModules,BackHandler} from 'react-native' import BaseComponent from '../components/BaseComponent' import VideoWrapper from '../views/VideoWrapper' import { writeHistoryVideo, queryCollectVideo, deleteCollectVideo, writeCollectVideo } from '../utils/DButils' import Colors from '../utils/Colors' import Toast from 'react-native-root-toast' import DownloadManager from '../utils/DownloadManager' import Loadding from '../components/Loadding' import data from '../../data.json' import config from '../../config.json' export default class VideoInfo extends BaseComponent { videoItemIndex = 0; params = {}; state = { data: {}, totalVideoList: [], isCollect: false, downloadComponentShow: false, } componentWillMount(){ this.subscription = BackHandler.addEventListener("hardwareBackPress",this.onBack) } onBack = () => { if(this.state.downloadComponentShow){ this.setState({downloadComponentShow:false}) return true; }else{ return false; } } async componentWillUnmount() { this.subscription && this.subscription.remove() this.hideLoadding() //事件回調 this.scrollTask && clearTimeout(this.scrollTask) //存儲播放記錄 let data = this.state.data; if (!data.title || !data.id || !data.coverUrl || !this.progressOption) return; let id = data.id; let name = data.title; let level = this.videoItemIndex; let progress = (this.progressOption.currentTime / this.progressOption.seekableDuration) * 100 let coverUrl = data.coverUrl; let obj = { id, name, coverUrl, progress, level } await writeHistoryVideo(obj) this.props.navigation.state.params.onBack && this.props.navigation.state.params.onBack(); } initData() { this.queryVideoCollect(); setTimeout(() => { this.queryTotalVideoList(data.VideoInfoData.data); }, config.delayed); } queryVideoCollect() { let data = this.props.navigation.state.params.data queryCollectVideo(data).then(res => { if (Object.keys(res).length) { this.setState({ isCollect: true }) } else { this.setState({ isCollect: false }) } }) } addVideoCollect() { let data = this.props.navigation.state.params.data writeCollectVideo(data).then(res => { this.queryVideoCollect(); }) } deleteVideoCollect() { let data = this.props.navigation.state.params.data deleteCollectVideo(data).then(res => { this.queryVideoCollect(); }) } queryTotalVideoList(data) { this.setState({ data, totalVideoList: data.videoList }, () => this.update(this.LOAD_SUCCESS, () => { if (this.params.history) { this.scrollTask = setTimeout(() => { let videoItemIndex = this.params.level; this.listview && this.listview.scrollTo(0, 55 * videoItemIndex) }, this.params.level * 10); } })) } _renderHeader() { let item = null //從播放歷史進入 if (this.params.history) { this.videoItemIndex = this.params.level; } if (this.state.totalVideoList.length) { item = this.state.totalVideoList[this.videoItemIndex] } return ( <VideoWrapper ref={ref => this.videoWrapper = ref} item={item} navigation={this.props.navigation} seek={this.params.progress} onProgress={options => this.progressOption = options} onLoad={data => { if (this.params.history) { this.params.history = false; this.params.progress = 0; } }} onEnd={() => { let data = this.state.data; if (data.videoList[this.videoItemIndex + 1]) { this.videoItemIndex += 1; this.forceUpdate(() => this.videoWrapper.startPlayVideo()); } }} /> ) } dataSource = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2 }) itemWidth = (DEVICE.width - 70) / 5; _renderVideoItems = () => { let data = this.state.totalVideoList; let dataSource = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2 }) if (data.length > 1) { return ( <View> <Text style={{ color: 'black', fontSize: 16 }}>選集</Text> <ListView removeClippedSubviews={DEVICE.android ? true : false} horizontal={true} showsHorizontalScrollIndicator={false} ref={ref => this.listview = ref} contentContainerStyle={{ paddingVertical: 10 }} initialListSize={this.params.history ? this.params.level + 10 : 10} dataSource={dataSource.cloneWithRows(data)} renderRow={(rowData, sectionID, rowID, highlightRow) => { let index = parseInt(rowID) let i = index + 1; let color = this.videoItemIndex == index ? "#C47F14" : '#666666' let text = this.state.data.type == 4 ? `第${i}期` : i; return ( <TouchableOpacity activeOpacity={0.7} onPress={() => { if (this.videoItemIndex !== index) { this.videoItemIndex = index; this.forceUpdate() } }} style={[styles.buttonStyle, { marginRight: 10, padding: 0, minWidth: 45, height: 45, justifyContent: 'center', alignItems: 'center' }]}> <Text style={{ color, fontSize: 15, fontWeight: 'bold' }}>{text}</Text> </TouchableOpacity> ); }}> </ListView> </View> ); } else { return null; } } renderComponent() { let data = this.state.data; let playCount = parseInt(data.playCount) if (playCount > 10000) { playCount = (playCount / 10000).toFixed(1) + '萬' } return ( <View style={{ flex: 1, backgroundColor: 'white' }}> <ScrollView style={{ flex: 1 }} contentContainerStyle={{ padding: 10 }}> {this._renderVideoItems()} <Text style={[styles.itemStyle, { fontSize: 18, color: 'black' }]}>{data.title}</Text> <View style={styles.itemBetweenStyle}> <Text>播放: {playCount}次</Text> <Text style={[styles.buttonStyle, { backgroundColor: Colors.mainColor }]}>豆瓣: {data.imdbScore > 0 ? data.imdbScore : '6.0'}</Text> </View> <Text style={styles.itemStyle}>分類: {data.classifyTypeList.join('/')}</Text> <Text style={styles.itemStyle}>導演: {data.director}</Text> <Text style={styles.itemStyle}>演員: {data.staring}</Text> <Text style={styles.itemStyle}>簡介: </Text> <Text style={[styles.itemStyle, { marginTop: 10 }]}> {data.intro}</Text> </ScrollView> </View> ); } /** * 下載選集對應得component */ _renderOther2() { if (!this.state.downloadComponentShow) return null; let data = this.state.totalVideoList; let dataSource = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2 }) if (data.length > 1) { return ( <View style={{ position: 'absolute', left: 0, right: 0, bottom: 0, top: 0, backgroundColor: 'white' }}> <View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', height: 45,paddingHorizontal:10 }}> <Text style={{ color: 'black', fontSize: 16 }}>選集</Text> <Text style={{ color: 'black', fontSize: 16 }} onPress={() => this.setState({downloadComponentShow:false})} >關閉</Text> </View> <ListView showsVerticalScrollIndicator={false} initialListSize={data.length} ref={ref => this.listview = ref} contentContainerStyle={{ flexDirection: 'row', justifyContent: 'flex-start', flexWrap: 'wrap',paddingLeft:10 }} dataSource={dataSource.cloneWithRows(data)} renderRow={(rowData, sectionID, rowID, highlightRow) => { let index = parseInt(rowID) let i = index + 1; let color = '#666666' let text = this.state.data.type == 4 ? `第${i}期` : i; return ( <TouchableOpacity activeOpacity={0.7} onPress={() => { this.downloadVideo(rowData,i) }} style={[styles.buttonStyle, { width: Math.floor((DEVICE.width - 50) / 4), height: 45, marginRight:10, justifyContent: 'center', alignItems: 'center', marginBottom:10, }]}> <Text style={{ color, fontSize: 15, fontWeight: 'bold' }}>{text}</Text> </TouchableOpacity> ); }}> </ListView> </View> ); } else { return null; } } _renderOther() { let collectImg = this.state.isCollect ? require('../../source/image/shoucang.png') : require('../../source/image/icon_shoucang.png') let collectText = this.state.isCollect ? "取消收藏" : " 收藏 " return ( <View style={styles.bottomStyle}> <TouchableOpacity activeOpacity={0.7} onPress={() => { Share.share({ title: '來嘻哈影視,看免費高清大片', message: '最新,最全,無廣告,請上嘻哈影視 https://github.com/andmizi', url: '最新,最全,無廣告,請上嘻哈影視https://github.com/andmizi' }) }} style={{ flex: 1, alignItems: 'center' }}> <Image style={styles.bottomImageStyle} resizeMode='contain' source={require('../../source/image/icon_share.png')}></Image> <Text style={{ color: 'black' }}>分享</Text> </TouchableOpacity> <TouchableOpacity activeOpacity={0.7} onPress={() => { if (this.state.isCollect) { this.deleteVideoCollect(); } else { this.addVideoCollect() } }} style={{ flex: 1, alignItems: 'center' }}> <Image style={styles.bottomImageStyle} resizeMode='contain' source={collectImg}></Image> <Text style={{ color: 'black' }}>{collectText}</Text> </TouchableOpacity> <TouchableOpacity activeOpacity={0.7} onPress={() => { let data = this.state.totalVideoList if(data.length == 0) return; if (data.length > 1) { this.setState({ downloadComponentShow: true }) } else { this.downloadVideo(data[0],-1) } } } style={{ flex: 1, alignItems: 'center' }}> <Image style={styles.bottomImageStyle} resizeMode='contain' source={require('../../source/image/icon_down.png')}></Image> <Text style={{ color: 'black' }}>緩存</Text> </TouchableOpacity> </View> ); } showLoadding() { this.hideLoadding() this.loadding = Loadding.show(); } hideLoadding() { this.loadding && Loadding.hide(this.loadding) } /** * 開始下載 * @param {*} data */ startDownloadVideo(data,index){ // let qualityMap = new Map(); // if (data.m3u8Format['1080P']) { // qualityMap.set('1080P', data.m3u8Format['1080P']) // } // if (data.m3u8Format['720P']) { // qualityMap.set('720P', data.m3u8Format['720P']) // } // if (data.m3u8Format['480P']) { // qualityMap.set('480P', data.m3u8Format['480P']) // } // if (data.m3u8Format['360P']) { // qualityMap.set('360P', data.m3u8Format['360P']) // } // if (data.m3u8Format['free'] && data.freeShow) { // qualityMap.set('free', data.m3u8Format['free']) // } // let playUrl = data.m3u8PlayUrl; // //默認取第一個 // let arr = Array.from(qualityMap.keys()); // let videoQuality = arr[0] // let url = playUrl + qualityMap.get(videoQuality); // console.log('netlog-',data.id,url) this.hideLoadding() // properties: { // id:"int", // url: 'string', //以url爲準 // title:'string', //視頻名稱 // index:'int', //集數 // coverUrl:'string', //視頻封面 // file:'string', //本地存儲路徑,m3u8文件 // playCount:'string',//播放次數 // imdbScore:'int',//豆瓣評分 // director:'string',//導演 // staring:'string',//演員 // intro:'string', //簡介 // type:'int',//類型 電影 電視劇 動漫 綜藝 // } let params = Object.assign({},this.state.data) params.url = data; params.index = index; params.id = data.id; params.classifyTypeListValue = params.classifyTypeList.join('/') DownloadManager.downLoad(params) } /** * 開始下載視頻 * @param {*} data */ downloadVideo(data,index) { this.showLoadding() setTimeout(() => { this.startDownloadVideo(config.videoUrl,index) }, config.delayed); } } var styles = StyleSheet.create({ itemBetweenStyle: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', minHeight: 40, }, itemStyle: { flexDirection: 'row', alignItems: 'center', minHeight: 30, textAlignVertical: 'center' }, buttonStyle: { backgroundColor: '#EFF0EB', borderRadius: 5, paddingHorizontal: 10, paddingVertical: 5, textAlign: 'center', textAlignVertical: 'center', color: 'white' }, bottomStyle: { flexDirection: 'row', height: 45, alignItems: 'center' }, bottomImageStyle: { width: 20, height: 20 } })
//src/pages/VIPPage.js //vip頁面 import React from 'react' import { Text } from 'react-native' import BaseComponent from '../components/BaseComponent'; import ScrollableTabView, { DefaultTabBar } from 'react-native-scrollable-tab-view' import Colors from '../utils/Colors' import VIPTabListPage from './VIPTabListPage' export default class VIPPage extends BaseComponent { state = { data: [] } initData(pageIndex, pageSize) { let url = "/api/app/video/ver2/video/queryColumnDataSmall/2/7?modelName=4" axios.get(url).then(res => { let data = res.data if (data.success) { if (data.data && data.data.length) { this.setState({ data: data.data }, () => this.update(this.LOAD_SUCCESS)) } else { this.update(this.LOAD_EMPTY) } } else { this.update(this.LOAD_FAILED) } }).catch(error => { console.log('netlog-', error) this.update(this.LOAD_FAILED) }) } _renderPages = () => { return this.state.data.map(item => { return ( <VIPTabListPage navigation={this.props.navigation} tabLabel={item.title} id={item.columnId}> </VIPTabListPage> ); }) } renderComponent() { return ( <ScrollableTabView renderTabBar={() => <DefaultTabBar tabStyle={{ backgroundColor: 'white', justifyContent: 'center', alignItems: 'center' }} underlineStyle={{ backgroundColor: 'transparent', height: 0 }} /> } locked={true} tabBarPosition='top' tabBarTextStyle={{ fontSize: DEVICE.ios_OS ? 17 : 20, fontWeight: DEVICE.ios_OS ? '600' : '500', }} tabBarActiveTextColor={Colors.mainColor} ref={(tabView) => { this.tabView = tabView }}> {this._renderPages()} </ScrollableTabView> ); } }
//src/pages/AboutPage.js import React from 'react' import { Text, View, Image, ScrollView } from 'react-native' export default class AboutPage extends React.Component { render() { return ( <View style={{ flex: 1, backgroundColor: 'white', justifyContent: 'center', alignItems: 'center', padding: 10 }}> <Text>影視愛好者,爲廣大網友提供免費的,高質量的影視做品</Text> <Text style={{ marginTop: 5 }}>若有侵權,請聯繫告知~</Text> </View> ) } }
//src/pages/HelpPage.js import React from 'react' import { Text, View, Image, ScrollView } from 'react-native' export default class HelpPage extends React.Component{ render(){ return ( <ScrollView contentContainerStyle={{flex:1,padding:10,backgroundColor:'white'}} showsVerticalScrollIndicator={false}> <View style={{height:40,justifyContent:'center'}}> <Text style={{color:'red'}}>1.沒法播放視頻</Text> </View> <Text>若是出現某些視頻沒法播放,包含一直緩衝,閃退,網絡異常,請嘗試多打開幾回,若是仍是沒法播放,請聯繫咱們的客服進行反饋。</Text> <View style={{height:40,justifyContent:'center',marginTop:10}}> <Text style={{color:'red'}}>2.搜索不到想看的視頻</Text> </View> <Text>因爲版權的緣由,某些視頻暫時沒法提供,請聯繫咱們的客服進行反饋。</Text> <View style={{height:40,justifyContent:'center',marginTop:10}}> <Text style={{color:'red'}}>3.界面展現異常</Text> </View> <Text>界面展現異常,不美觀或適配遇到問題,請聯繫咱們的客服進行反饋。</Text> <View style={{height:40,justifyContent:'center',marginTop:10}}> <Text style={{color:'red'}}>4.圖標加載不出來</Text> </View> <Text>Android:若是遇到啓動頁圖片,返回按鍵圖標加載不出來,請到設置-應用程序-嘻哈影視-存儲-清空數據。</Text> </ScrollView> ) } }
//src/pages/PersonCenterPage.js import React from 'react' import { Text, View, Image, TouchableOpacity, StyleSheet, ScrollView, Share, ImageBackground, StatusBar, Alert } from 'react-native' import BaseComponent from '../components/BaseComponent' import SetingItem from '../views/SettingItem' import { queryAllHistoryVideo, clearAllHistoryVideo } from '../utils/DButils' import { HeaderItem, appBarPaddingTop } from '../components/Header' import Toast from 'react-native-root-toast' import Colors from '../utils/Colors' const itemWidth = Math.floor((DEVICE.width - 40) / 4); const itemHeight = Math.floor(itemWidth * 1.1) const finalStyle = { width: itemWidth, height: itemHeight } export default class PersonCenterPage extends BaseComponent { state = { historyVideo: [], } initData(){ queryAllHistoryVideo().then(res => { let result = []; for(let key in res){ result.push(res[key]) } this.setState({historyVideo:result},() => this.update(this.LOAD_SUCCESS)) }) } _onBack = () => { this.initData(); } enterDetialPage = data => { data.videoInfoId = data.id; data.title = data.name; data.history = true; let params = Object.assign({},data) this.props.navigation.navigate("VideoInfoPage", { data:params, onBack: this._onBack }) } _clearAllHistoryVideo = () => { clearAllHistoryVideo().then(res => { this.setState({historyVideo:[]}) }) } renderComponent() { console.log('netlog-item',this.state.historyVideo.length) let historyVideoViews = [] for (let i = this.state.historyVideo.length - 1; i >= 0; i--) { if(historyVideoViews.length >= 30) break; let obj = this.state.historyVideo[i + ""]; console.log('netlog-item',obj) let item = ( <TouchableOpacity key={'history_' + i} activeOpacity={0.7} style={{ marginRight: 10 }} onPress={() => this.enterDetialPage(obj)}> <Image style={[finalStyle]} resizeMode="cover" source={{ uri: obj.coverUrl }}></Image> <Text style={{ width: finalStyle.width, paddingVertical: 5, textAlign: 'center' }} numberOfLines={1}>{obj.name}</Text> <Text style={{ width: finalStyle.width, textAlign: 'center' }} numberOfLines={1}>觀看至%{obj.progress}</Text> </TouchableOpacity> ); historyVideoViews.push(item) } let imageheight = DEVICE.width / 1.7; return ( <ScrollView contentContainerStyle={{ paddingBottom: 50 }} style={{ backgroundColor: "#F1F1F1" }}> <ImageBackground source={require('../../source/image/profile_bg.png')} resizeMode='cover' style={{ justifyContent: 'center', width: '100%', height: imageheight, alignItems: 'center', backgroundColor: 'white' }}> {/* <Image source={require('../../source/image/profile_icon.png')}></Image> */} <HeaderItem onClick={() => this.props.navigation.goBack()} style={{ position: 'absolute', left: 0, top: appBarPaddingTop }}> <Image resizeMode='contain' style={{ width: 25, height: 25 }} source={require('../../source/image/player_return.png')}></Image> </HeaderItem> </ImageBackground> <View style={{ flexDirection: 'row', paddingVertical: 10, backgroundColor: 'white' }}> <TouchableOpacity activeOpacity={0.7} onPress={() => { Toast.show('正在努力開發中...') }} style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}> <Image style={{ width: 25, height: 25 }} resizeMode='contain' source={require('../../source/image/icon_mine_vip.png')}></Image> <Text style={{ marginTop: 5, color: 'black' }}>神祕大片</Text> </TouchableOpacity> <TouchableOpacity activeOpacity={0.7} onPress={() => this.props.navigation.navigate("DownloadPage")} style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}> <Image style={{ width: 25, height: 25 }} resizeMode='contain' source={require('../../source/image/down.png')}></Image> <Text style={{ marginTop: 5, color: 'black' }}>下載中心</Text> </TouchableOpacity> <TouchableOpacity activeOpacity={0.7} onPress={() => this.props.navigation.navigate('MyCollectPage') } style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}> <Image style={{ width: 25, height: 25 }} resizeMode='contain' source={require('../../source/image/shoucang.png')}></Image> <Text style={{ marginTop: 5, color: 'black' }}>個人收藏</Text> </TouchableOpacity> <TouchableOpacity activeOpacity={0.7} onPress={() => { Toast.show('正在努力開發中...') }} style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}> <Image style={{ width: 25, height: 25 }} resizeMode='contain' source={require('../../source/image/more.png')}></Image> <Text style={{ marginTop: 5, color: 'black' }}>更多功能</Text> </TouchableOpacity> </View> {historyVideoViews && historyVideoViews.length ? ( <View style={{ backgroundColor: 'white', marginTop: 10 }}> <View style={{ flexDirection: 'row', alignItems: 'center', marginVertical: 10, paddingHorizontal: 10, justifyContent: 'space-between' }}> <View style={{ flexDirection: 'row', alignItems: 'center' }}> <View style={{ width: 3, height: 15, backgroundColor: "black" }}></View> <Text style={{ color: 'black', fontSize: 15, marginLeft: 5 }}>播放記錄</Text> </View> <Text onPress={this._clearAllHistoryVideo}>清空記錄</Text> </View> <ScrollView showsHorizontalScrollIndicator={false} horizontal={true} contentContainerStyle={{ paddingLeft: 10, paddingBottom: 20 }}> {historyVideoViews} </ScrollView> </View> ) : null} {/* 新手幫助頁面 */} <SetingItem style={{ marginTop: 10 }} onClick={() => { this.props.navigation.navigate('HelpPage') }} options={{ key: '新手幫助', value: '', hasArrow: true }}></SetingItem> <SetingItem onClick={() => { Share.share({ title: '來嘻哈影視,看免費高清大片', message: '最新,最全,無廣告,請上嘻哈影視 https://github.com/andmizi', url: '最新,最全,無廣告,請上嘻哈影視https://github.com/andmizi' }) }} options={{ key: '分享給好友', value: '', hasArrow: true }}></SetingItem> </ScrollView> ); } } const styles = StyleSheet.create({ })
//src/pages/SearchInfoPage.js //看不出來是作的什麼 import React from 'react' import { Text, View, Image, TouchableOpacity, StyleSheet } from 'react-native' import BaseFlatListComponent from '../components/BaseFlatListComponent' import Colors from '../utils/Colors' import data from '../../data.json' import config from '../../config.json' export default class SearchInfoPage extends BaseFlatListComponent { enbaleRefresh = false; static navigationOptions = options => { return { title: options.navigation.state.params.key } } filterResponse(result) { return result.data.data.map(item => { item.title = item.title.replace(/{/g, "").replace(/}/g, "").replace(/,/g, ""); return item; }) } getRequestAction(pageIndex, pageSize) { return new Promise((resolve,reject) => { setTimeout(() => { resolve({data:data.ClassMoreData}) }, config.delayed); }) } enterDetialPage = data => { data.videoInfoId = data.id; this.props.navigation.navigate("VideoInfoPage", { data }) } _getTagBackgroundColor = tag => { if(tag == "搶鮮"){ return "#573D1B" }else if(tag == "1080P"){ return "#C47F14"; }else{ return "red" } } renderRow = rowdata => { let tagName = rowdata.tagName == '無標籤' ? null : rowdata.tagName let tagBackgroundColor = this._getTagBackgroundColor(tagName) let complete = rowdata.episodeState == 1; let updateTag; if(complete){ if(rowdata.episodeUploadCount > 1){ updateTag = "已完結"; } }else{ updateTag = rowdata.episodeUploadCount > 1 ? rowdata.type != 4 ? `更新至${rowdata.episodeUploadCount}集` : `更新至${rowdata.episodeUploadCount}期` : null; } let image = rowdata.coverUrl ? {uri : rowdata.coverUrl} : require('../../source/image/nor.png') let playCount = parseInt(rowdata.playCount) if(playCount > 10000){ playCount = (playCount / 10000).toFixed(1) + '萬' } return ( <TouchableOpacity activeOpacity={0.7} onPress={() => this.enterDetialPage(rowdata)} style={styles.itemStyle}> <View style={{ width: 120, height: 80 }}> <Image style={{width: 120, height: 80 }} resizeMode="cover" source={image}></Image> {tagName ? ( <View style={{ position: 'absolute', borderRadius: 2, backgroundColor: tagBackgroundColor, top: 5, right: 5, paddingHorizontal: 5 }}> <Text style={{ color: 'white', fontSize: 12 }}>{tagName}</Text> </View> ) : null} {updateTag ? ( <View style={{ position: 'absolute', width: '100%', bottom: 0, paddingVertical: 5, backgroundColor: 'rgba(0,0,0,0.3)', alignItems: 'center', justifyContent: 'center' }}> <Text style={{ color: 'white', fontSize: 12 }}>{updateTag}</Text> </View> ) : null} </View> <View style={{ flex: 1, height: 80, justifyContent: 'space-between', marginLeft: 10 }}> <Text numberOfLines={1}>{rowdata.title}</Text> <View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' }}> <Text>播放{playCount}次</Text> <Text style={styles.buttonStyle}>豆瓣: {rowdata.doubanScore > 0 ? rowdata.doubanScore : '6.0'}</Text> </View> </View> </TouchableOpacity> ); } } const styles = StyleSheet.create({ itemStyle: { flexDirection: 'row', alignItems: 'center', padding: 10, height: 100, }, buttonStyle: { backgroundColor: Colors.mainColor, borderRadius: 5, paddingHorizontal: 10, paddingVertical: 5, textAlign: 'center', textAlignVertical: 'center', color: 'white' } })
//src/pages/SearchPage.js import React from 'react' import { Text, View, Image, TouchableOpacity, TextInput, StyleSheet, ScrollView } from 'react-native' import Header, { HeaderItem } from '../components/Header' import BaseComponent from '../components/BaseComponent' import { writeHistorySearchContent, queryAllHistorySearchContent, clearAllHistorySearchConten } from '../utils/DButils' import Colors from '../utils/Colors' import Toast from 'react-native-root-toast' import data from '../../data.json' import config from '../../config.json' const backIcon = require('../../source/icons/back_icon.png') const itemWidth = (DEVICE.width - 20) / 2; export default class SearchPage extends BaseComponent { state = { datas: data.HotSearchData.data, historyContents: {}, content: '', LOAD_STATE:this.LOAD_SUCCESS } initData() { this.queryHistoryVideo() } /** * 查詢搜索歷史記錄 */ queryHistoryVideo(){ queryAllHistorySearchContent().then(res => { this.setState({historyContents:res}) }) } enterSearchInfo(key, flag) { if (key) { if (flag) { writeHistorySearchContent(key).then(res => { this.queryHistoryVideo(); }).catch(error => { console.log("netlog-",error) }) } this.props.navigation.navigate('SearchInfoPage', { key }) } } _clearAllHistorySearchContens = () => { clearAllHistorySearchConten().then(res => { this.setState({historyContents:{}}) }) } _renderHeader() { return ( <Header> <HeaderItem onClick={() => this.props.navigation.goBack()}> <Image source={backIcon}></Image> </HeaderItem> <TextInput autoFocus={true} numberOfLines={1} onChangeText={text => this.setState({ content: text })} maxLength={20} placeholder="搜一搜,全都有" returnKeyType="search" onSubmitEditing={e => this.enterSearchInfo(e.nativeEvent.text,true)} underlineColorAndroid='transparent' style={{ flex: 1, height: 35, padding: 0, borderRadius: 5, backgroundColor: 'rgba(0,0,0,0.1)', justifyContent: 'center', alignItems: 'center', textAlign: 'center' }}> </TextInput> <HeaderItem onClick={() => this.enterSearchInfo(this.state.content, true)}> <Text style={{ fontSize: 15, color: 'black', fontWeight: 'bold' }}>搜索 </Text> </HeaderItem> </Header> ); } renderComponent() { let keys = [] keys = Object.keys(this.state.historyContents).reverse(); keys.splice(10, keys.length); let showHistoryContents = keys.length > 0 return ( <ScrollView contentContainerStyle={{ padding: 10 }}> { showHistoryContents ? ( <View style={{ marginBottom: 15 }}> <View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', marginVertical: 10 }}> <View style={{ flexDirection: 'row', alignItems: 'center' }}> <View style={{ width: 3, height: 15, backgroundColor: 'black' }}></View> <Text style={{ color: 'black', fontSize: 15, marginLeft: 5 }}>歷史搜索</Text> </View> <Text onPress={this._clearAllHistorySearchContens}>清空記錄</Text> </View> <View style={{ flexDirection: 'row', flexWrap: 'wrap', alignItems: 'center', }}> { keys.map((key, index) => { let item = this.state.historyContents[key] return ( <Text key={'search_children_' + index} onPress={() => this.enterSearchInfo(item.name, true)} numberOfLines={1} style={{ fontSize: 15, margin: 5, padding: 5, color: 'white', backgroundColor: Colors.mainColor, borderRadius: 4 }}>{item.name} </Text> ); }) } </View> </View> ) : null } <View style={{ flexDirection: 'row', alignItems: 'center', marginVertical: 10 }}> <View style={{ width: 3, height: 15, backgroundColor: 'black' }}></View> <Text style={{ color: 'black', fontSize: 15, marginLeft: 5 }}>熱門搜索</Text> </View> <View style={styles.container}> { this.state.datas.map((item) => { return ( <TouchableOpacity key={'search_' + item.id} style={{ width: itemWidth, marginVertical: 5, flexDirection: 'row', alignItems: 'center' }} onPress={() => this.enterSearchInfo(item.keyword, false)} activeOpacity={0.7}> <Text style={{ fontSize: 15 }}>{item.orderNum} </Text> <Text numberOfLines={1} style={{ fontSize: 15, marginLeft: 5 }}>{item.keyword}</Text> </TouchableOpacity> ); }) } </View> </ScrollView> ); } } const styles = StyleSheet.create({ container: { flexDirection: 'row', flexWrap: 'wrap', justifyContent: 'space-between' } })
//src/pages/VarietyPage.js import React from 'react' import { DeviceEventEmitter } from 'react-native' import BaseFlatListComponent from '../components/BaseFlatListComponent' import Banner from '../views/Banner' import ListItem from '../views/ListItem' import MainTabNavigatorHeader from '../views/MainTabNavigatorHeader' import data from '../../data.json' import config from '../../config.json' export default class VarietyPage extends BaseFlatListComponent { pageSize = 4; _renderHeader() { return <MainTabNavigatorHeader onRightClick={() => { this.props.navigation.navigate('QueryMoreVideoPage', { id: 4, title: '綜藝' }) }} rightIcon={require('../../source/image/sx_icon.png')} navigation={this.props.navigation} /> } getRequestAction(pageIndex, pageSize) { return new Promise((resolve,reject) => { setTimeout(() => { resolve({data:data.VarietyPageData}) }, config.delayed); }) } filterResponse(result) { return result.data.data; } renderFlatViewHeader = () => { return <Banner id={4} navigation={this.props.navigation}></Banner> } renderRow = rowData => { return ( <ListItem navigation={this.props.navigation} data={rowData}></ListItem> ); } }
//初始首頁 import React from 'react' import { ScrollView, View, Text, Image, Alert, BackHandler, DeviceEventEmitter } from 'react-native' import { StackNavigator, TabNavigator, NavigationActions, DrawerNavigator, DrawerItems } from 'react-navigation' // import { HeaderItem } from './src/components/Header' import Toast from 'react-native-root-toast' import SplashPage from './src/pages/SplashPage' //推薦頁面 import RecommendPage from './src/pages/RecommendPage' //電影頁面 import MoviePage from './src/pages/MoviePage' //迷惑了不知道是作什麼的了 import TVPage from './src/pages/TVPage' //這個竟然仍是相似的組件,說明裏面有優化空間 import CartoonPage from './src/pages/CartoonPage' //封裝的組件頁面 import VarietyPage from './src/pages/VarietyPage' //搜索頁面 import SearchPage from './src/pages/SearchPage' //不知道幹啥的 import SearchInfoPage from './src/pages/SearchInfoPage' //我的中心頁面 import PersonCenterPage from './src/pages/PersonCenterPage' //help頁面 import HelpPage from './src/pages/HelpPage' //關於頁面 import AboutPage from './src/pages/AboutPage' //vip頁面 import VIPPage from './src/pages/VIPPage' //視頻詳情頁 import VideoInfoPage from './src/pages/VideoInfoPage' // import VideoListPage from './src/pages/VideoListPage' // import MyCollectPage from './src/pages/MyCollectPage' //帶你進入查詢更多視頻頁面 import QueryMoreVideoPage from './src/pages/QueryMoreVideoPage' //下載的方法 import DownloadPage from './src/pages/DownloadPage' //進入的是單個的視頻頁面 import OfflineVideoPlayer from './src/pages/OfflineVideoPlayer' //定義了根搜索 import MainTabNavigatorHeader from './src/views/MainTabNavigatorHeader' import Colors from './src/utils/Colors' const TabNav = TabNavigator({ Recommend: { screen: RecommendPage, navigationOptions: { tabBarLabel: options => { return <Text style={{ color: options.tintColor }}>推薦</Text> }, tabBarIcon: options => { let img = options.focused ? require('./source/image/main_choice_click.png') : require('./source/image/main_choice.png') return <Image source={img}></Image> }, tabBarOnPress: obj => { DeviceEventEmitter.emit("Recommend"); obj.jumpToIndex(obj.scene.index) }, } }, Movie: { screen: MoviePage, navigationOptions: { tabBarLabel: options => { return <Text style={{ color: options.tintColor }}>電影</Text> }, tabBarIcon: options => { let img = options.focused ? require('./source/image/main_movie_click.png') : require('./source/image/main_movie.png') return <Image source={img} ></Image> }, tabBarOnPress: obj => { DeviceEventEmitter.emit("Movie"); obj.jumpToIndex(obj.scene.index) }, } }, TV: { screen: TVPage, navigationOptions: { tabBarLabel: options => { return <Text style={{ color: options.tintColor }}>電視劇</Text> }, tabBarIcon: options => { let img = options.focused ? require('./source/image/main_tv_click.png') : require('./source/image/main_tv.png') return <Image source={img} ></Image> }, tabBarOnPress: obj => { DeviceEventEmitter.emit("TV"); obj.jumpToIndex(obj.scene.index) }, } }, Cartoon: { screen: CartoonPage, navigationOptions: { tabBarLabel: options => { return <Text style={{ color: options.tintColor }}>動漫</Text> }, tabBarIcon: options => { let img = options.focused ? require('./source/image/icon_cartoon_nor_click.png') : require('./source/image/icon_cartoon_nor.png') return <Image source={img} ></Image> }, tabBarOnPress: obj => { DeviceEventEmitter.emit("Cartoon"); obj.jumpToIndex(obj.scene.index) }, } }, Variety: { screen: VarietyPage, navigationOptions: { tabBarLabel: options => { return <Text style={{ color: options.tintColor }}>綜藝</Text> }, tabBarIcon: options => { let img = options.focused ? require('./source/image/icon_variety_nor_click.png') : require('./source/image/icon_variety_nor.png') return <Image source={img} ></Image> }, tabBarOnPress: obj => { DeviceEventEmitter.emit("Variety"); obj.jumpToIndex(obj.scene.index) }, } }, // VIP: { // screen: VIPPage, // navigationOptions: { // tabBarLabel: options => { // return <Text style={{ color: options.tintColor }}>神祕大片</Text> // }, // tabBarIcon: options => { // let img = options.focused ? require('./source/image/main_tv_click.png') : require('./source/image/main_tv.png') // return <Image style={{ width: dp(55), height: dp(55) }} source={img} resizeMode="cover"></Image> // }, // } // } }, { tabBarPosition: 'bottom', lazy: true, swipeEnabled: false, animationEnabled: false, initialRouteName: "Recommend", removeClippedSubviews: DEVICE.android ? true : false, tabBarOptions: { activeTintColor: Colors.mainColor, inactiveTintColor: Colors.mainColor, showIcon: true, showLabel: true, style: { backgroundColor: 'white', elevation: 5, }, indicatorStyle: { height: 0 } } }); const RootNav = StackNavigator({ Splash: { screen: SplashPage, navigationOptions: { header: null } }, Root: { screen: TabNav, navigationOptions: function (options) { return { header: null, headerLeft: null } } }, VideoInfoPage: { screen: VideoInfoPage, navigationOptions: { header: null } }, VideoListPage: { screen: VideoListPage, }, SearchPage: { screen: SearchPage, navigationOptions: { header: null } }, SearchInfoPage: { screen: SearchInfoPage, }, PersonCenterPage: { screen: PersonCenterPage, navigationOptions: { header: null } }, HelpPage: { screen: HelpPage, navigationOptions: { title: "新手幫助" } }, AboutPage: { screen: AboutPage, navigationOptions: { title: "關於咱們" } }, MyCollectPage: { screen: MyCollectPage, navigationOptions: { title: "個人收藏" } }, QueryMoreVideoPage: { screen: QueryMoreVideoPage, }, DownloadPage:{ screen:DownloadPage, navigationOptions : { title: "下載中心" } }, OfflineVideoPlayer:{ screen:OfflineVideoPlayer, navigationOptions : { header: null } }, }, { initialRouteName: "Splash", cardStyle: { }, navigationOptions: function (options) { return { headerLeft: <HeaderItem onClick={() => options.navigation.goBack()}><Image source={require('./source/icons/back_icon.png')}></Image></HeaderItem> } } }); const defaultStateAction = RootNav.router.getStateForAction; RootNav.router.getStateForAction = (action, state) => { if (DEVICE.android && state && action.type === NavigationActions.BACK && state.routes.length === 1) { Alert.alert('提示', '肯定要退出嗎?', [{ text: '取消', onPress: () => { } }, { text: '退出', onPress: () => { BackHandler.exitApp(); } }]); const routes = [...state.routes]; return { ...state, ...state.routes, index: routes.length - 1, }; } else { return defaultStateAction(action, state); } }; import { TestPage } from './src/TestPage' export default RootNav;
//src/views/Banner.js import React from 'react' import { Image, View, Text, TouchableOpacity, ScrollView } from 'react-native' import BaseComponent from '../components/BaseComponent' import Swiper from 'react-native-swiper' import Colors from '../utils/Colors' import data from '../../data.json' import config from '../../config.json' const finalStyle = { width: DEVICE.width, height: DEVICE.width * 0.5 }; //this.props.id 0推薦 1電影 2電視劇 3動漫 4綜藝 export default class Banner extends BaseComponent { containerStyle = finalStyle; state = { datas: [], classDatas: [] } filterData(data) { //過濾掉廣告輪播 return data.filter(item => { return item.targetType == 2 && item.videoInfoId != 0; }) } initData() { setTimeout(() => { let result = this.props.id == 0 ? data.RecommendBannerData : ( this.props.id == 1 ? data.MovieBannerData : ( this.props.id == 2 ? data.TVBannerData : ( this.props.id == 3 ? data.CartoonBannerData : data.VarietyBannerData ) ) ) this.setState({ datas: this.filterData(result.data) }, () => this.update(this.LOAD_SUCCESS)) }, config.delayed); } _enterVideoInfo = data => { data.coverUrl = data.thumbnailUrl; this.props.navigation.navigate("VideoInfoPage", { data }) } renderComponent() { let items = []; for (let i = 0; i < this.state.datas.length; i++) { let obj = this.state.datas[i]; let image = obj.thumbnailUrl ? { uri: obj.thumbnailUrl } : require('../../source/image/nor.png') let item = ( <TouchableOpacity key={'banner' + i} onPress={() => this._enterVideoInfo(obj)} activeOpacity={1}> <Image style={finalStyle} source={image} resizeMode="cover"></Image> <View style={{ position: 'absolute', bottom: 0, paddingBottom: 25, paddingTop: 5, paddingLeft: 5, width: '100%', backgroundColor: 'rgba(0,0,0,0.3)' }}> <Text numberOfLines={1} style={{ color: 'white', fontWeight: '400', fontSize: 15 }}>{obj.title}</Text> </View> </TouchableOpacity> ); items.push(item) } return ( <Swiper removeClippedSubviews={DEVICE.android ? true : false} paginationStyle={{ bottom: 10, justifyContent: 'flex-end', paddingRight: 5 }} style={finalStyle} width={finalStyle.width} height={finalStyle.height} loop={true} activeDotColor={Colors.mainColor} dotColor="white" autoplay={true} showsPagination={true}> {items} </Swiper> ); } }
代碼感受很複雜啊,又不是很複雜,可是很難沉下心一句一句弄懂,只能似是而非,實際上是不懂,下一篇嘍~ios