diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 0000000..2399e7e
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,6 @@
+{
+ "bracketSpacing": false,
+ "singleQuote": true,
+ "trailingComma": "all",
+ "arrowParens": "avoid"
+}
diff --git a/.prettierrc.js b/.prettierrc.js
deleted file mode 100644
index c26961c..0000000
--- a/.prettierrc.js
+++ /dev/null
@@ -1,6 +0,0 @@
-module.exports = {
- bracketSpacing: false,
- singleQuote: true,
- trailingComma: 'all',
- arrowParens: 'avoid',
-};
diff --git a/README.md b/README.md
index da06a7c..780474d 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,7 @@
# SociQuote
-A open source quotes app made possible by [Airtable](https://airtable.com/) and [React Native](https://reactnative.dev/).
+A open source quotes app made possible by [React Native](https://reactnative.dev/).
-![Airtable](https://img.shields.io/badge/Airtable-18BFFF?style=for-the-badge&logo=Airtable&logoColor=white)
![React Native](https://img.shields.io/badge/React_Native-20232A?style=for-the-badge&logo=react&logoColor=61DAFB)
## Status
@@ -14,7 +13,12 @@ A open source quotes app made possible by [Airtable](https://airtable.com/) and
Head over to [releases](https://github.com/siddsarkar/SociQuote/releases) to grab the latest apk from assets.
-## Design
+## Features
+
+- [x] Pull to refresh
+- [x] Longpress for sharing
+
+## Design
![cover image](resources/cover.jpeg)
diff --git a/package.json b/package.json
index af2dcaf..dd29fd9 100644
--- a/package.json
+++ b/package.json
@@ -12,7 +12,6 @@
"lint": "eslint . --fix"
},
"dependencies": {
- "@react-native-clipboard/clipboard": "^1.8.4",
"appcenter": "^4.3.0",
"appcenter-analytics": "^4.3.0",
"appcenter-crashes": "^4.3.0",
diff --git a/src/api/index.js b/src/api/index.js
index 08574f8..d9f36c1 100644
--- a/src/api/index.js
+++ b/src/api/index.js
@@ -1,3 +1,3 @@
-import airtable from './providers/airtable.js';
+import quotable from './providers/quotable';
-export default {airtable};
+export default {quotable};
diff --git a/src/api/providers/airtable.js b/src/api/providers/airtable.js
deleted file mode 100644
index f0fc073..0000000
--- a/src/api/providers/airtable.js
+++ /dev/null
@@ -1,116 +0,0 @@
-import requesthandler from '../requestHandler';
-
-const AIRTABLE_BASE = 'appikp0oubt89KWGW'; // quoted app base
-const AIRTABLE_BASE_URL = `https://api.airtable.com/v0/${AIRTABLE_BASE}`;
-const AIRTABLE_API_KEY = 'keyZXKPvGAfYaTQKY';
-
-/**
- * ?AIRTABLE API INTERFACE
- */
-export default {
- /**
- * Get a table within base
- * @param {{airtable_api_key: string, table: string}} options api key and record data
- * @returns table records
- */
- getTable: async function ({
- airtable_api_key = AIRTABLE_API_KEY,
- table,
- params,
- }) {
- let queryStr = '';
- if (params) {
- queryStr = Object.keys(params)
- .map((key, idx) => {
- if (idx === 0) {
- return `?${key}=${params[key]}`;
- }
- return `&${key}=${params[key]}`;
- })
- .join('');
- }
- const url = `${AIRTABLE_BASE_URL}/${table}${queryStr}`;
- return requesthandler(url, {
- method: 'GET',
- headers: {Authorization: `Bearer ${airtable_api_key}`},
- });
- },
-
- /**
- * Creates a record on specified base and table
- * @param {{airtable_api_key: string, table: string, record: object}} options apikey, name of table, record
- * @returns saved record
- */
- createRecord: async function ({
- airtable_api_key = AIRTABLE_API_KEY,
- table,
- record,
- }) {
- const url = `${AIRTABLE_BASE_URL}/${table}`;
- return requesthandler(url, {
- method: 'POST',
- headers: {
- Authorization: `Bearer ${airtable_api_key}`,
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify(record),
- });
- },
-
- /**
- * Updates a record
- * @param {{airtable_api_key: string, table: string, record: object}} options apikey, table name and updated record object
- * @returns updated record
- */
- updateRecord: async function ({
- airtable_api_key = AIRTABLE_API_KEY,
- table,
- record,
- }) {
- const url = `${AIRTABLE_BASE_URL}/${table}`;
- return requesthandler(url, {
- method: 'PATCH',
- headers: {
- Authorization: `Bearer ${airtable_api_key}`,
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({records: [record]}),
- });
- },
-
- /**
- * Retrive user record if any
- * @param {{airtable_api_key: string, user: object}} options apikey and firebase user object
- * @returns users with the firebase user id
- */
- getUser: async function ({airtable_api_key = AIRTABLE_API_KEY, user}) {
- const url = `${AIRTABLE_BASE_URL}/users?filterByFormula=id=${user.id}`;
- return requesthandler(url, {
- method: 'GET',
- headers: {
- Authorization: `Bearer ${airtable_api_key}`,
- 'Content-Type': 'application/json',
- },
- });
- },
-
- /**
- * Sign Up a User
- * @param {{airtable_api_key: string, user: object}} options api key and user object
- * @returns user from airtable
- */
- signupUser: async function ({airtable_api_key = AIRTABLE_API_KEY, user}) {
- const url = `${AIRTABLE_BASE_URL}/users`;
- const body = user.photo
- ? JSON.stringify({fields: {...user, photo: [{url: user.photo}]}})
- : JSON.stringify({fields: {...user}});
- return requesthandler(url, {
- method: 'POST',
- headers: {
- Authorization: `Bearer ${airtable_api_key}`,
- 'Content-Type': 'application/json',
- },
- body,
- });
- },
-};
diff --git a/src/api/providers/quotable.js b/src/api/providers/quotable.js
new file mode 100644
index 0000000..43e18fe
--- /dev/null
+++ b/src/api/providers/quotable.js
@@ -0,0 +1,22 @@
+import {generateQueryStrFromObject, processFetchRequest} from '../../utils';
+
+const QUOTABLE_BASE_URL = 'https://quotable.io';
+
+/**
+ * Get list of quotes
+ * @param {object=} params query params as object
+ * @returns json response
+ */
+const getQuotes = async function (params) {
+ let url = `${QUOTABLE_BASE_URL}/quotes`;
+ if (params) {
+ url += generateQueryStrFromObject(params);
+ }
+
+ return processFetchRequest(url);
+};
+
+export default {
+ getQuotes,
+ // other methods here
+};
diff --git a/src/libs/Caraousel.js b/src/components/common/Caraousel/Caraousel.js
similarity index 77%
rename from src/libs/Caraousel.js
rename to src/components/common/Caraousel/Caraousel.js
index eaec888..23b4da7 100644
--- a/src/libs/Caraousel.js
+++ b/src/components/common/Caraousel/Caraousel.js
@@ -1,13 +1,14 @@
/**
- * @source https://lloyds-digital.com/blog/lets-create-a-carousel-in-react-native
- * @modified
+ * This caraousel was made by following the below article link
+ * https://lloyds-digital.com/blog/lets-create-a-carousel-in-react-native
*/
+import Analytics from 'appcenter-analytics';
import React, {memo, useCallback, useEffect, useRef, useState} from 'react';
import {
- Clipboard,
Dimensions,
FlatList,
+ Share,
StyleSheet,
Text,
ToastAndroid,
@@ -17,19 +18,35 @@ import {
const {width: windowWidth, height: windowHeight} = Dimensions.get('window');
const Slide = memo(function Slide({data}) {
- const copyToClipboard = () => {
- Clipboard.setString(data.fields.content + ' — ' + data.fields.author);
- ToastAndroid.show('Copied', ToastAndroid.SHORT);
+ const onShare = async () => {
+ try {
+ const result = await Share.share({
+ message: data.content + '\n — ' + data.author,
+ });
+ if (result.action === Share.sharedAction) {
+ if (result.activityType) {
+ // shared with activity type of result.activityType
+ } else {
+ // shared
+ Analytics.trackEvent('Quote Shared', {id: data._id});
+ }
+ } else if (result.action === Share.dismissedAction) {
+ // dismissed
+ }
+ } catch (error) {
+ ToastAndroid.show(error.message, ToastAndroid.SHORT);
+ }
};
return (
〃
-
- {data.fields.content}
+
+
+ {data.content}
-
- — {data.fields.author}
+
+ — {data.author}
);
@@ -69,7 +86,7 @@ const Caraousel = ({slideList = []}) => {
removeClippedSubviews: true,
scrollEventThrottle: 16,
windowSize: 2,
- keyExtractor: useCallback(s => String(s.id), []),
+ keyExtractor: useCallback(s => s._id, []),
getItemLayout: useCallback(
(_, idx) => ({
index: idx,
diff --git a/src/components/common/index.js b/src/components/common/index.js
new file mode 100644
index 0000000..68a9139
--- /dev/null
+++ b/src/components/common/index.js
@@ -0,0 +1,5 @@
+/**
+ * Components that can be used anywhere in the project
+ */
+
+export {default as Caraousel} from './Caraousel/Caraousel';
diff --git a/src/components/ui/index.js b/src/components/ui/index.js
new file mode 100644
index 0000000..fffa08a
--- /dev/null
+++ b/src/components/ui/index.js
@@ -0,0 +1,3 @@
+/**
+ * UI Elements goes here
+ */
diff --git a/src/utils/generateQueryStrFromObject.js b/src/utils/generateQueryStrFromObject.js
new file mode 100644
index 0000000..d596523
--- /dev/null
+++ b/src/utils/generateQueryStrFromObject.js
@@ -0,0 +1,16 @@
+/**
+ * Returns query string from object
+ * @param {{ [param: string]: string|number|boolean }} obj query params as object
+ * @example generateQueryStrFromObject({"q":"any","page":1}) returns "?q=any&page=1"
+ */
+const generateQueryStrFromObject = obj =>
+ Object.keys(obj)
+ .map((key, idx) => {
+ if (idx === 0) {
+ return `?${key}=${obj[key]}`;
+ }
+ return `&${key}=${obj[key]}`;
+ })
+ .join('');
+
+export default generateQueryStrFromObject;
diff --git a/src/utils/index.js b/src/utils/index.js
new file mode 100644
index 0000000..6dafb5e
--- /dev/null
+++ b/src/utils/index.js
@@ -0,0 +1,6 @@
+/**
+ * Utlities to be used anywhere in the project
+ */
+
+export {default as generateQueryStrFromObject} from './generateQueryStrFromObject';
+export {default as processFetchRequest} from './processFetchRequest';
diff --git a/src/api/requestHandler.js b/src/utils/processFetchRequest.js
similarity index 70%
rename from src/api/requestHandler.js
rename to src/utils/processFetchRequest.js
index bb15718..2d8439e 100644
--- a/src/api/requestHandler.js
+++ b/src/utils/processFetchRequest.js
@@ -1,10 +1,10 @@
/**
* Global network request handler
- * @param {string} url url to fetch
- * @param {Request} options fetch options
+ * @param {RequestInfo} url url to fetch
+ * @param {RequestInit=} options fetch options
* @returns json response
*/
-async function requestHandler(url, options) {
+const processFetchRequest = async (url, options) => {
const ts = Date.now();
const method = options?.method || 'GET';
const endpoint = url.match(
@@ -18,6 +18,6 @@ async function requestHandler(url, options) {
return response.json();
}
throw response.json();
-}
+};
-export default requestHandler;
+export default processFetchRequest;
diff --git a/src/views/Home/HomeScreen.js b/src/views/Home/HomeScreen.js
index 1818b46..7c31c81 100644
--- a/src/views/Home/HomeScreen.js
+++ b/src/views/Home/HomeScreen.js
@@ -7,24 +7,24 @@ import {
View,
} from 'react-native';
import api from '../../api';
-import Caraousel from '../../libs/Caraousel';
+import {Caraousel} from '../../components/common';
const HomeScreen = () => {
const [data, setData] = useState([]);
- const [pageOffset, setPageOffset] = useState('');
+ const [page, setPage] = useState(1);
const [isLoading, setIsLoading] = useState(true);
const [refreshing, setRefreshing] = useState(false);
const onRefresh = () => {
setRefreshing(true);
async function fetchData() {
- const response = await api.airtable.getTable({
- table: 'quotes',
- params: {limit: 10, offset: pageOffset},
+ const response = await api.quotable.getQuotes({
+ limit: 10,
+ page,
});
- setPageOffset(response.offset);
- setData(response.records);
+ setPage(response.page + 1);
+ setData(response.results);
setRefreshing(false);
}
@@ -33,12 +33,10 @@ const HomeScreen = () => {
useEffect(() => {
async function fetchData() {
- const response = await api.airtable.getTable({
- table: 'quotes',
- params: {limit: 10},
- });
- setPageOffset(response.offset);
- setData(response.records);
+ const response = await api.quotable.getQuotes({limit: 10});
+ setPage(response.page + 1);
+ setData(response.results);
+
setIsLoading(false);
}
diff --git a/src/views/index.js b/src/views/index.js
index 4eec777..7108b55 100644
--- a/src/views/index.js
+++ b/src/views/index.js
@@ -1 +1,5 @@
+/**
+ * All App Screens are Exported here
+ */
+
export {default as HomeScreen} from './Home/HomeScreen';
diff --git a/yarn.lock b/yarn.lock
index c44af88..9db9353 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -979,11 +979,6 @@
"@types/yargs" "^16.0.0"
chalk "^4.0.0"
-"@react-native-clipboard/clipboard@^1.8.4":
- version "1.8.4"
- resolved "https://registry.yarnpkg.com/@react-native-clipboard/clipboard/-/clipboard-1.8.4.tgz#4bc1fb00643688e489d8220cd635844ab5c066f9"
- integrity sha512-poFq3RvXzkbXcqoQNssbZ+aNbCRzBFAWkR9QL7u9xNMgsyWZtk7d16JQoaBo8D2E+kKi+/9JOiVQzA5w+9N67w==
-
"@react-native-community/cli-debugger-ui@^6.0.0-rc.0":
version "6.0.0-rc.0"
resolved "https://registry.yarnpkg.com/@react-native-community/cli-debugger-ui/-/cli-debugger-ui-6.0.0-rc.0.tgz#774378626e4b70f5e1e2e54910472dcbaffa1536"