From 2c85d93c660214dac3718f6ae7902e294197c313 Mon Sep 17 00:00:00 2001 From: Kelvin Tam <3705865+kelvinthh@users.noreply.github.com> Date: Wed, 19 Apr 2023 22:06:57 -0400 Subject: [PATCH 1/2] Add NetInfo the check network status --- App.tsx | 26 ++++++++++++++++++++++---- ios/Podfile.lock | 8 +++++++- package-lock.json | 9 +++++++++ package.json | 7 ++++--- src/components/Body.tsx | 24 +++++++++++++++++++----- src/components/Header.tsx | 6 +++++- src/components/ImageItem.tsx | 2 +- src/state/imageState.ts | 6 +++--- src/state/internetState.ts | 8 ++++++++ 9 files changed, 78 insertions(+), 18 deletions(-) create mode 100644 src/state/internetState.ts diff --git a/App.tsx b/App.tsx index cae1f9b..f35765c 100644 --- a/App.tsx +++ b/App.tsx @@ -1,22 +1,40 @@ import React, { useEffect } from "react"; import { SafeAreaView } from "react-native"; -import { RecoilRoot, useSetRecoilState } from "recoil"; +import { RecoilRoot, useRecoilState, useSetRecoilState } from "recoil"; import Header from "./src/components/Header"; import imageState from "./src/state/imageState"; import Body from "./src/components/Body"; import suggestionState from "./src/state/suggestionState"; import { fetchImages, fetchSuggestion } from "./src/fetchData"; -import { Toast } from 'react-native-toast-message/lib/src/Toast'; +import { Toast } from "react-native-toast-message/lib/src/Toast"; +import internetState from "./src/state/internetState"; +import NetInfo from "@react-native-community/netinfo"; const AppContent = () => { const setImages = useSetRecoilState(imageState); const setSuggestion = useSetRecoilState(suggestionState); + const [hasInternet, setHasInternet] = useRecoilState(internetState); + + useEffect(() => { + const unsubscribe = NetInfo.addEventListener((state) => { + if (state.isInternetReachable !== hasInternet) { + console.log( + `Internet state: ${state.type} ${state.isInternetReachable}` + ); + setHasInternet(state.isInternetReachable); + } + }); + + return () => { + unsubscribe(); + }; + }, []); useEffect(() => { (async () => { const images = await fetchImages(); const suggestion = await fetchSuggestion(); - + setImages(images ?? []); // Provide an empty array as the default value setSuggestion(suggestion ?? ""); // Provide an empty string as the default value })(); @@ -34,7 +52,7 @@ const App = () => { return ( - + ); }; diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 933b5a8..e35d570 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -361,6 +361,8 @@ PODS: - React-jsinspector (0.71.6) - React-logger (0.71.6): - glog + - react-native-netinfo (9.3.9): + - React-Core - React-perflogger (0.71.6) - React-RCTActionSheet (0.71.6): - React-Core/RCTActionSheetHeaders (= 0.71.6) @@ -485,6 +487,7 @@ DEPENDENCIES: - React-jsiexecutor (from `../node_modules/react-native/ReactCommon/jsiexecutor`) - React-jsinspector (from `../node_modules/react-native/ReactCommon/jsinspector`) - React-logger (from `../node_modules/react-native/ReactCommon/logger`) + - "react-native-netinfo (from `../node_modules/@react-native-community/netinfo`)" - React-perflogger (from `../node_modules/react-native/ReactCommon/reactperflogger`) - React-RCTActionSheet (from `../node_modules/react-native/Libraries/ActionSheetIOS`) - React-RCTAnimation (from `../node_modules/react-native/Libraries/NativeAnimation`) @@ -576,6 +579,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/ReactCommon/jsinspector" React-logger: :path: "../node_modules/react-native/ReactCommon/logger" + react-native-netinfo: + :path: "../node_modules/@react-native-community/netinfo" React-perflogger: :path: "../node_modules/react-native/ReactCommon/reactperflogger" React-RCTActionSheet: @@ -643,6 +648,7 @@ SPEC CHECKSUMS: React-jsiexecutor: 7894956638ff3e00819dd3f9f6f4a84da38f2409 React-jsinspector: d5ce2ef3eb8fd30c28389d0bc577918c70821bd6 React-logger: 9332c3e7b4ef007a0211c0a9868253aac3e1da82 + react-native-netinfo: 22c082970cbd99071a4e5aa7a612ac20d66b08f0 React-perflogger: 43392072a5b867a504e2b4857606f8fc5a403d7f React-RCTActionSheet: c7b67c125bebeda9fb19fc7b200d85cb9d6899c4 React-RCTAnimation: c2de79906f607986633a7114bee44854e4c7e2f5 @@ -660,4 +666,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: ebe432ad56513222ebfc09b56e491909e15bf652 -COCOAPODS: 1.12.0 +COCOAPODS: 1.12.1 diff --git a/package-lock.json b/package-lock.json index b18be02..321d6a9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "img-gen-rn", "version": "1.0.0", "dependencies": { + "@react-native-community/netinfo": "^9.3.9", "axios": "^1.3.5", "expo": "~48.0.10", "expo-dev-client": "~2.1.6", @@ -4695,6 +4696,14 @@ "node": ">=8" } }, + "node_modules/@react-native-community/netinfo": { + "version": "9.3.9", + "resolved": "https://registry.npmjs.org/@react-native-community/netinfo/-/netinfo-9.3.9.tgz", + "integrity": "sha512-L9f8OjX5Fwh5CdP4ygDPa6iQCJJ4tAtXiFuQp6EG4/sdSXDqOXaehAhJrZAN8P8Lztnf8YN8p836GmZuBCrY+A==", + "peerDependencies": { + "react-native": ">=0.59" + } + }, "node_modules/@react-native/assets": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@react-native/assets/-/assets-1.0.0.tgz", diff --git a/package.json b/package.json index d5b51e8..0dfe3bc 100644 --- a/package.json +++ b/package.json @@ -8,8 +8,11 @@ "web": "expo start --web" }, "dependencies": { + "@react-native-community/netinfo": "^9.3.9", "axios": "^1.3.5", "expo": "~48.0.10", + "expo-dev-client": "~2.1.6", + "expo-splash-screen": "~0.18.1", "expo-status-bar": "~1.4.4", "nativewind": "^2.0.11", "react": "18.2.0", @@ -17,9 +20,7 @@ "react-native-dotenv": "^3.4.8", "react-native-toast-message": "^2.1.6", "recoil": "^0.7.7", - "tailwindcss": "^3.3.1", - "expo-dev-client": "~2.1.6", - "expo-splash-screen": "~0.18.1" + "tailwindcss": "^3.3.1" }, "devDependencies": { "@babel/core": "^7.20.0", diff --git a/src/components/Body.tsx b/src/components/Body.tsx index d52e59c..1455de0 100644 --- a/src/components/Body.tsx +++ b/src/components/Body.tsx @@ -10,13 +10,14 @@ import { Keyboard, RefreshControl, } from "react-native"; -import { useRecoilState } from "recoil"; +import { useRecoilState, useRecoilValue } from "recoil"; import imagesState from "../state/imageState"; import suggestionState from "../state/suggestionState"; import { ImageUrl } from "../types/imageUrl"; import { fetchImages, fetchSuggestion, generateImage } from "../fetchData"; import ImageItem from "../components/ImageItem"; import { Toast } from "react-native-toast-message/lib/src/Toast"; +import internetState from "../state/internetState"; const Body = () => { const [images, setImages] = useRecoilState(imagesState); @@ -25,6 +26,7 @@ const Body = () => { const [inputValue, setInputValue] = useState(""); const [refreshing, setRefreshing] = useState(false); const [generating, setGenerating] = useState(false); + const hasInternet = useRecoilValue(internetState); // Reference for the FlatList const flatListRef = useRef>(null); @@ -124,22 +126,23 @@ const Body = () => { !inputValue || generating ? " bg-fuchsia-200" : "bg-fuchsia-600" }`} onPress={!generating ? () => handleSubmit(inputValue) : undefined} - disabled={!inputValue || generating} + disabled={!inputValue || generating || !hasInternet} > {generating ? "Loading..." : "Submit"} - {inputValue && ( + {inputValue && (suggestion || hasInternet) && ( - Suggestion💡 + Suggestion💡 {suggestion} )} Gimme a new suggestion! @@ -149,8 +152,18 @@ const Body = () => { > Use suggestion! + {!hasInternet && ( + + ⛔️ Error: No internet... + + )} {/* */} + {!hasInternet && !images ? ( + + ⛔️ Unable to connect to the internet... + + ) : ( { refreshControl={ } - className='mx-4' + className="mx-4" /> + )} ); }; diff --git a/src/components/Header.tsx b/src/components/Header.tsx index bbd9bb9..44b1352 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -14,7 +14,11 @@ const Header = () => { Image Generator by Kelvin Tam Powered by OpenAI & Azure - openURL("https://github.com/kelvinthh/Image-Generation-RN")}> + + openURL("https://github.com/kelvinthh/Image-Generation-RN") + } + > diff --git a/src/components/ImageItem.tsx b/src/components/ImageItem.tsx index 2058e3f..8b68619 100644 --- a/src/components/ImageItem.tsx +++ b/src/components/ImageItem.tsx @@ -55,7 +55,7 @@ const ImageItem: React.FC = ({ item }) => { {loading && ( - Loading... + Loading... )} ({ - key: 'imagesState', + key: "imagesState", default: [], }); diff --git a/src/state/internetState.ts b/src/state/internetState.ts new file mode 100644 index 0000000..a258816 --- /dev/null +++ b/src/state/internetState.ts @@ -0,0 +1,8 @@ +import { atom } from "recoil"; + +const internetState = atom({ + key: "internetState", + default: null, +}); + +export default internetState; From 260c9b6392ac1c2507e5cff5deeaa75330e2c1be Mon Sep 17 00:00:00 2001 From: Kelvin Tam <3705865+kelvinthh@users.noreply.github.com> Date: Thu, 20 Apr 2023 05:20:54 -0400 Subject: [PATCH 2/2] Major update - Fix status bar text - Optimize network checking, add toast pop-up - When no internet, submit & get new suggestion buttons will be greyed out - Suggestion no longer displays when it's in the input prompt - Update README.md --- App.tsx | 11 +-- README.md | 5 +- ios/imggenrn/Info.plist | 158 ++++++++++++++++++++-------------------- src/components/Body.tsx | 75 ++++++++++++------- 4 files changed, 133 insertions(+), 116 deletions(-) diff --git a/App.tsx b/App.tsx index f35765c..46846bb 100644 --- a/App.tsx +++ b/App.tsx @@ -1,5 +1,5 @@ import React, { useEffect } from "react"; -import { SafeAreaView } from "react-native"; +import { SafeAreaView, StatusBar } from "react-native"; import { RecoilRoot, useRecoilState, useSetRecoilState } from "recoil"; import Header from "./src/components/Header"; import imageState from "./src/state/imageState"; @@ -17,12 +17,8 @@ const AppContent = () => { useEffect(() => { const unsubscribe = NetInfo.addEventListener((state) => { - if (state.isInternetReachable !== hasInternet) { - console.log( - `Internet state: ${state.type} ${state.isInternetReachable}` - ); - setHasInternet(state.isInternetReachable); - } + console.log(`Internet state: ${state.type} ${state.isInternetReachable}`); + setHasInternet(state.isInternetReachable); }); return () => { @@ -44,6 +40,7 @@ const AppContent = () => {
+ ); }; diff --git a/README.md b/README.md index f0e1cfc..4027bca 100644 --- a/README.md +++ b/README.md @@ -36,10 +36,11 @@ API_GET_IMAGES= // getImages API endpoint, e.g. /api/getImages API_GET_SUGGESTIONS= // getChatGPTSuggestion API endpoint API_GENERATE_IMAGE= // generateImage API endpoint ``` +5. Download **Expo Go** app from Google Play/App Store, run `npx expo start` in the terminal to start the development server, then scan the QR Code within Expo Go app or your phone's camera app. -5. Run the app on your preferred platform (iOS or Android) using `npx expo run:ios` or `npx expo run:android`. +6. Or to build the app, run the app on your preferred platform (iOS or Android) using `npx expo run:ios` or `npx expo run:android`. -6. Enjoy generating and exploring images based on your text prompts! 🌈 +7. Enjoy generating and exploring images based on your text prompts! 🌈 ## Additional Information ℹ️ diff --git a/ios/imggenrn/Info.plist b/ios/imggenrn/Info.plist index 3c70324..0ce0d60 100644 --- a/ios/imggenrn/Info.plist +++ b/ios/imggenrn/Info.plist @@ -1,82 +1,82 @@ - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleDisplayName - AI Image Gen - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - $(PRODUCT_BUNDLE_PACKAGE_TYPE) - CFBundleShortVersionString - 1.0.0 - CFBundleSignature - ???? - CFBundleURLTypes - - - CFBundleURLSchemes - - com.hhtam.imggenrn - - - - CFBundleURLSchemes - - exp+img-gen-rn - - - - CFBundleVersion - 1 - LSRequiresIPhoneOS - - NSAppTransportSecurity - - NSAllowsArbitraryLoads - - NSExceptionDomains - - localhost - - NSExceptionAllowsInsecureHTTPLoads - - - - - UILaunchStoryboardName - SplashScreen - UIRequiredDeviceCapabilities - - armv7 - - UIRequiresFullScreen - - UIStatusBarStyle - UIStatusBarStyleDefault - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - - UIUserInterfaceStyle - Light - UIViewControllerBasedStatusBarAppearance - - - \ No newline at end of file + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + AI Image Gen + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0.0 + CFBundleSignature + ???? + CFBundleURLTypes + + + CFBundleURLSchemes + + com.hhtam.imggenrn + + + + CFBundleURLSchemes + + exp+img-gen-rn + + + + CFBundleVersion + 1 + LSRequiresIPhoneOS + + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + NSExceptionDomains + + localhost + + NSExceptionAllowsInsecureHTTPLoads + + + + + UILaunchStoryboardName + SplashScreen + UIRequiredDeviceCapabilities + + armv7 + + UIRequiresFullScreen + + UIStatusBarStyle + UIStatusBarStyleDefault + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIUserInterfaceStyle + Light + UIViewControllerBasedStatusBarAppearance + + + diff --git a/src/components/Body.tsx b/src/components/Body.tsx index 1455de0..b2d0f7a 100644 --- a/src/components/Body.tsx +++ b/src/components/Body.tsx @@ -1,4 +1,4 @@ -import React, { useState, useRef } from "react"; +import React, { useState, useRef, useEffect } from "react"; import { View, Text, @@ -30,6 +30,25 @@ const Body = () => { // Reference for the FlatList const flatListRef = useRef>(null); + const isFirstRender = useRef(true); + + useEffect(() => { + + // Prevent the toast pop up on the first render + if (isFirstRender.current) { + isFirstRender.current = false; + return; + } + + if (!hasInternet) { + Toast.show({ + type: "error", + text1: "Error", + text2: "No internet connection.", + position: "bottom", + }); + } + }, [hasInternet]); const handleSubmit = async (prompt: string) => { setGenerating(true); @@ -99,6 +118,7 @@ const Body = () => { }; const handleRefreshImage = async () => { + if (!hasInternet) return; const newImages = await fetchImages(); setImages(newImages ?? []); }; @@ -123,7 +143,9 @@ const Body = () => { handleSubmit(inputValue) : undefined} disabled={!inputValue || generating || !hasInternet} @@ -133,14 +155,18 @@ const Body = () => { - {inputValue && (suggestion || hasInternet) && ( - - Suggestion💡 - {suggestion} - - )} + {inputValue && + !inputValue.includes(suggestion) && + (suggestion || hasInternet) && ( + + Suggestion💡 + {suggestion} + + )} @@ -152,29 +178,22 @@ const Body = () => { > Use suggestion! - {!hasInternet && ( + {(!hasInternet && !isFirstRender.current) && ( - ⛔️ Error: No internet... + ⛔️ Error: No internet connection. )} - {/* */} - {!hasInternet && !images ? ( - - ⛔️ Unable to connect to the internet... - - ) : ( - item.name} - refreshControl={ - - } - className="mx-4" - /> - )} + item.name} + refreshControl={ + + } + className="mx-4" + /> ); };