iT邦幫忙

2022 iThome 鐵人賽

DAY 27
0

程式畫面預覽:

圖1

圖2

程式碼:

app.js

// source form React Native 教學 - 編寫一個圖書資訊 APP - Code Guy Studio
// https://codeguy.studio/react-native-tutorial/book-app/


import React, { useEffect, useState } from 'react';
import {
  ScrollView,
  View,
  StyleSheet,
  ActivityIndicator,
  Text,
  TextInput,
  Pressable,
} from 'react-native';
//
import Constants from 'expo-constants';
import { BarCodeScanner } from 'expo-barcode-scanner';

//
const RowItem = ({ title, content }) => (
  <View style={styles.rowItem}>
    <Text style={styles.title}>{title}</Text>
    <Text style={styles.content}>{content}</Text>
  </View>
);

export default function App() {

  const [isbn, setIsbn] = useState('');
  const [disabled, setDisabled] = useState(true);
  const [result, setResult] = useState(null);
  const [loading, setLoading] = useState(false);
  const [errorMsg, setErrorMsg] = useState('');
  const [hasPermission, setHasPermission] = useState(null);
  const [scanning, setScanning] = useState(false);

  //權限
  useEffect(() => {
    (async () => {
      const { status } = await BarCodeScanner.requestPermissionsAsync();
      setHasPermission(status === 'granted');
    })();
  }, []);

  const handleBarCodeScanned = ({ type, data }) => {
    if (type === BarCodeScanner.Constants.BarCodeType.ean13) {
      search(data);
      setErrorMsg('');
      setIsbn(data);
      setScanning(false);
    }
  };

  const onChangeText = (text) => {
    if (text.length === 10 || text.length === 13) {
      setDisabled(false);
    } else {
      setDisabled(true);
    }

    setErrorMsg('');
    setIsbn(text);
  };

  const search = async (q) => {
    const apiKey = '你的 Google Book API Key';
    const baseUrl = 'https://www.googleapis.com/books/v1/volumes?q=isbn:';
    const url = baseUrl + q + '&key=' + apiKey;

    setLoading(true);

    await fetch(url)
      .then((response) => {
        return response.json();
      })
      .then((json) => {
        if (json.totalItems > 0) {
          const info = json.items[0].volumeInfo;
          setResult(info);
          setErrorMsg('');
        } else {
          setResult(null);
          setErrorMsg('找不到結果');
        }
      })
      .catch((error) => {
        setErrorMsg('發生錯誤:' + error);
      });

    setLoading(false);
  };

  return (
    <ScrollView>
      <View style={styles.bar} />
      <View style={styles.container}>
        <Text style={styles.header}>搜尋書本</Text>
        <Text style={styles.subHeader}>
          請輸入10位或13位的ISBN條碼搜尋書本資料
        </Text>
        <TextInput
          style={styles.input}
          placeholder="ISBN"
          keyboardType="numeric"
          maxLength={13}
          onChangeText={onChangeText}
          placeholderTextColor="#666666"
          value={isbn}
        />
        {!loading ? (
          <>
            <Pressable
              style={({ pressed }) => [
                {
                  backgroundColor: pressed || disabled ? '#e5e5e5' : '#FF0266'
                },
                styles.btn
              ]}
              disabled={disabled}
              onPress={() => search(isbn)}
            >
              <Text style={styles.btnText}>搜尋</Text>
            </Pressable>
            <Pressable
              style={({ pressed }) => [
                {
                  backgroundColor: pressed ? '#e5e5e5' : '#FF0266'
                },
                styles.btn
              ]}
              onPress={() =>
                scanning ? setScanning(false) : setScanning(true)
              }
            >
              <Text style={styles.btnText}>
                {scanning ? '關閉相機' : '掃描條碼'}
              </Text>
            </Pressable>
            {hasPermission && scanning ? (
              <View style={styles.scanner}>
                <BarCodeScanner
                  onBarCodeScanned={handleBarCodeScanned}
                  style={StyleSheet.absoluteFillObject}
                />
              </View>
            ) : null}
          </>
        ) : (
          <ActivityIndicator size="large" color="#e5e5e5" />
        )}
        {result ? (
          <View style={styles.result}>
            {result.imageLinks ? (
              <Image
                style={styles.image}
                source={{ uri: result.imageLinks.thumbnail }}
                resizeMode="contain"
              />
            ) : null}
            {result.title ? (
              <RowItem title="標題" content={result.title} />
            ) : null}
            {result.subtitle ? (
              <RowItem title="副標題" content={result.subtitle} />
            ) : null}
            {result.authors ? (
              <RowItem title="作者" content={result.authors.toString()} />
            ) : null}
            {result.publisher ? (
              <RowItem title="出版商" content={result.publisher} />
            ) : null}
            {result.publishedDate ? (
              <RowItem title="出版日期" content={result.publishedDate} />
            ) : null}
            {result.description ? (
              <RowItem title="描述" content={result.description} />
            ) : null}
          </View>
        ) : null}
        {errorMsg ? (
          <View style={styles.result}>
            <Text style={styles.errorMsg}>{errorMsg}</Text>
          </View>
        ) : null}
      </View>
    </ScrollView>
  );
}

const styles = StyleSheet.create({
  rowItem: {
    paddingVertical: 10
  },
  title: {
    fontWeight: 'bold',
    fontSize: 18,
    paddingBottom: 10
  },
  content: {
    fontSize: 18,
    color: '#666666'
  },
  bar: {
    height: Constants.statusBarHeight + 30,
    backgroundColor: '#FF0266',
    shadowColor: '#000',
    shadowOffset: {
      width: 0,
      height: 3
    },
    shadowOpacity: 0.25,
    shadowRadius: 4,
    elevation: 5
  },
  header: {
    alignSelf: 'center',
    fontSize: 40,
    marginBottom: 25
  },
  subHeader: {
    fontSize: 18,
    marginBottom: 25,
    color: '#666666'
  },
  input: {
    borderWidth: 1,
    borderRadius: 5,
    paddingVertical: 18,
    paddingHorizontal: 15,
    fontSize: 18,
    marginBottom: 25
  },
  btn: {
    shadowColor: '#000',
    shadowOffset: {
      width: 0,
      height: 2
    },
    shadowOpacity: 0.25,
    shadowRadius: 4,
    elevation: 5,
    paddingVertical: 12,
    paddingHorizontal: 18,
    borderRadius: 5,
    marginBottom: 25
  },
  btnText: {
    color: '#fff',
    fontSize: 20,
    textAlign: 'center'
  },
  result: {
    borderTopWidth: 1,
    marginTop: 20,
    paddingTop: 20,
    paddingBottom: 20
  },
  image: {
    width: '100%',
    aspectRatio: 1 / 1,
    marginBottom: 10
  },
  container: {
    backgroundColor: '#fff',
    marginTop: 50,
    paddingHorizontal: 30
  },
  errorMsg: {
    color: '#B00020',
    fontSize: 20,
    alignSelf: 'center'
  },
  scanner: {
    width: '100%',
    aspectRatio: 1 / 1
  }
});

範例 Source Code:

git clone https://smilehsu@bitbucket.org/smilehsu/barcode_booksearch0120.git

參考資料:

React Native 教學 – 編寫一個圖書資訊 APP – Code Guy Studio (連結已經失效)
https://codeguy.studio/react-native-tutorial/book-app/
這個範例是我在google上找來的,網站主機是一間學校,可惜範例已經被移除...


上一篇
DAY26 - 把React網站部屬到Vercel
下一篇
DAY28 - 那些我買過的線上課程(1)
系列文
總是學不來的中年大叔,孤獨的自學之旅第二年30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言