diff --git a/README.md b/README.md index 4027bca..38558fa 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,15 @@ This is a mobile version of the [Image Generation Next.js](https://github.com/ke - Azure Functions for back-end hosting - Azure Storage for storing images +## Third-Party Libraries Used 📚 +- [Axios](https://axios-http.com/) +- [react-native-toast-message](https://github.com/calintamas/react-native-toast-message) +- [NativeWind](https://github.com/marklawlor/nativewind) +- [Tailwind CSS](https://tailwindcss.com/) +- [React Native Async Storage](https://github.com/react-native-async-storage/async-storage) +- [react-native-dotenv](https://github.com/goatandsheep/react-native-dotenv) +- [Recoil](https://recoiljs.org/) + ## Getting Started 🚀 1. Make sure you have the back-end set up, this includes the Azure Functions and Storages from the [main Next.js project](https://github.com/kelvinthh/Image-Generation-Next.js). diff --git a/ios/Podfile.lock b/ios/Podfile.lock index e35d570..b93d9e9 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -447,6 +447,8 @@ PODS: - React-jsi (= 0.71.6) - React-logger (= 0.71.6) - React-perflogger (= 0.71.6) + - RNCAsyncStorage (1.18.1): + - React-Core - Yoga (1.14.0) DEPENDENCIES: @@ -501,6 +503,7 @@ DEPENDENCIES: - React-RCTVibration (from `../node_modules/react-native/Libraries/Vibration`) - React-runtimeexecutor (from `../node_modules/react-native/ReactCommon/runtimeexecutor`) - ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`) + - "RNCAsyncStorage (from `../node_modules/@react-native-async-storage/async-storage`)" - Yoga (from `../node_modules/react-native/ReactCommon/yoga`) SPEC REPOS: @@ -607,6 +610,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/ReactCommon/runtimeexecutor" ReactCommon: :path: "../node_modules/react-native/ReactCommon" + RNCAsyncStorage: + :path: "../node_modules/@react-native-async-storage/async-storage" Yoga: :path: "../node_modules/react-native/ReactCommon/yoga" @@ -662,6 +667,7 @@ SPEC CHECKSUMS: React-RCTVibration: 73d201599a64ea14b4e0b8f91b64970979fd92e6 React-runtimeexecutor: 8692ac548bec648fa121980ccb4304afd136d584 ReactCommon: 0c43eaeaaee231d7d8dc24fc5a6e4cf2b75bf196 + RNCAsyncStorage: b90b71f45b8b97be43bc4284e71a6af48ac9f547 Yoga: ba09b6b11e6139e3df8229238aa794205ca6a02a PODFILE CHECKSUM: ebe432ad56513222ebfc09b56e491909e15bf652 diff --git a/package-lock.json b/package-lock.json index 321d6a9..52b3de5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "img-gen-rn", "version": "1.0.0", "dependencies": { + "@react-native-async-storage/async-storage": "^1.18.1", "@react-native-community/netinfo": "^9.3.9", "axios": "^1.3.5", "expo": "~48.0.10", @@ -3553,6 +3554,17 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/@react-native-async-storage/async-storage": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-1.18.1.tgz", + "integrity": "sha512-70aFW8fVCKl+oA1AKPFDpE6s4t9pulj2QeLX+MabEmzfT3urd/3cckv45WJvtocdoIH/oXA3Y+YcCRJCcNa8mA==", + "dependencies": { + "merge-options": "^3.0.4" + }, + "peerDependencies": { + "react-native": "^0.0.0-0 || 0.60 - 0.72 || 1000.0.0" + } + }, "node_modules/@react-native-community/cli": { "version": "10.2.2", "resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-10.2.2.tgz", @@ -8229,6 +8241,14 @@ "node": ">=8" } }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "engines": { + "node": ">=8" + } + }, "node_modules/is-plain-object": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", @@ -9460,6 +9480,17 @@ "resolved": "https://registry.npmjs.org/memory-cache/-/memory-cache-0.2.0.tgz", "integrity": "sha512-OcjA+jzjOYzKmKS6IQVALHLVz+rNTMPoJvCztFaZxwG14wtAW7VRZjwTQu06vKCYOxh4jVnik7ya0SXTB0W+xA==" }, + "node_modules/merge-options": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/merge-options/-/merge-options-3.0.4.tgz", + "integrity": "sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==", + "dependencies": { + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", diff --git a/package.json b/package.json index 0dfe3bc..f611ff6 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "web": "expo start --web" }, "dependencies": { + "@react-native-async-storage/async-storage": "^1.18.1", "@react-native-community/netinfo": "^9.3.9", "axios": "^1.3.5", "expo": "~48.0.10", diff --git a/src/components/Body.tsx b/src/components/Body.tsx index b2d0f7a..d96049b 100644 --- a/src/components/Body.tsx +++ b/src/components/Body.tsx @@ -18,6 +18,7 @@ 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"; +import AsyncStorage from "@react-native-async-storage/async-storage"; const Body = () => { const [images, setImages] = useRecoilState(imagesState); @@ -32,8 +33,10 @@ const Body = () => { const flatListRef = useRef>(null); const isFirstRender = useRef(true); - useEffect(() => { + // AsyncStorage key + const LAST_GENERATED_TIME_KEY = "lastGeneratedTime"; + useEffect(() => { // Prevent the toast pop up on the first render if (isFirstRender.current) { isFirstRender.current = false; @@ -50,7 +53,35 @@ const Body = () => { } }, [hasInternet]); + async function setLastGeneratedTime() { + const currentTime = new Date().getTime(); + await AsyncStorage.setItem( + LAST_GENERATED_TIME_KEY, + JSON.stringify(currentTime) + ); + } + + async function getLastGeneratedTime() { + const lastGeneratedTime = await AsyncStorage.getItem( + LAST_GENERATED_TIME_KEY + ); + return lastGeneratedTime ? JSON.parse(lastGeneratedTime) : null; + } + const handleSubmit = async (prompt: string) => { + const lastGeneratedTime = await getLastGeneratedTime(); + const currentTime = new Date().getTime(); + + if (lastGeneratedTime && currentTime - lastGeneratedTime < 10000) { + Toast.show({ + type: "info", + text1: "Please wait", + text2: "You can generate an image every 10 seconds.", + position: "bottom", + }); + return; + } + setGenerating(true); console.log("Submitting prompt: ", prompt); Toast.show({ @@ -79,6 +110,8 @@ const Body = () => { text2: "Your image has been generated!", position: "bottom", }); + + await setLastGeneratedTime(); await handleRefreshImage(); // Scroll the FlatList to the top @@ -178,7 +211,7 @@ const Body = () => { > Use suggestion! - {(!hasInternet && !isFirstRender.current) && ( + {!hasInternet && !isFirstRender.current && ( ⛔️ Error: No internet connection.