(
+
+
+ {state => (
+
+ {children}
+
+ )}
+
+
+);
+
+export default PageStack;
diff --git a/components/ui/TabBar.jsx b/components/ui/TabBar.jsx
index 667f04f..2954c6f 100644
--- a/components/ui/TabBar.jsx
+++ b/components/ui/TabBar.jsx
@@ -1,12 +1,13 @@
const TabBar = ({ children }) => (
-)
+);
-export default TabBar;
\ No newline at end of file
+export default TabBar;
diff --git a/components/ui/Toggle.jsx b/components/ui/Toggle.jsx
new file mode 100644
index 0000000..cef2b36
--- /dev/null
+++ b/components/ui/Toggle.jsx
@@ -0,0 +1,5 @@
+import ReactToggle from 'react-toggle';
+
+const Toggle = props =>
;
+
+export default Toggle;
diff --git a/components/ui/VirtualScroll.jsx b/components/ui/VirtualScroll.jsx
new file mode 100644
index 0000000..01ded4e
--- /dev/null
+++ b/components/ui/VirtualScroll.jsx
@@ -0,0 +1,5 @@
+import { Virtuoso } from 'react-virtuoso';
+
+const VirtualScroll = props =>
;
+
+export default VirtualScroll;
diff --git a/data/index.js b/data/index.js
deleted file mode 100644
index aa465bc..0000000
--- a/data/index.js
+++ /dev/null
@@ -1,47 +0,0 @@
-// Some fake filler data
-
-export const images = [
- 'https://images.unsplash.com/photo-1608091526083-86ae8489ae5c?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=2100&q=80',
- 'https://images.unsplash.com/photo-1608050072262-7b26ba63fb46?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=2100&q=80',
- 'https://images.unsplash.com/photo-1607975218223-94f82613e833?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=934&q=80',
- 'https://images.unsplash.com/photo-1608108707326-215150457c9f?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=2100&q=80',
- 'https://images.unsplash.com/photo-1608057681073-9399f209e773?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=934&q=80',
-];
-
-export const homeItems = [
- {
- title: 'Welcome',
- type: 'Blog',
- text: 'Welcome to the app!',
- author: 'Max',
- image: images[0],
- },
- {
- title: 'How to get started',
- type: 'Article',
- text: 'Getting started with the app is easy! Just follow these 100 steps',
- author: 'Max',
- image: images[1],
- },
- {
- title: 'Need help?',
- type: 'Support',
- text: "We're here to help. Available between the hours of 3am and 3:01am every day",
- author: 'Max',
- image: images[2],
- },
- {
- title: 'Welcome',
- type: 'Blog',
- text: 'Welcome to the app!',
- author: 'Max',
- image: images[3],
- },
- {
- title: 'Welcome',
- type: 'Blog',
- text: 'Welcome to the app!',
- author: 'Max',
- image: images[4],
- },
-];
diff --git a/ios/App/App/capacitor.config.json b/ios/App/App/capacitor.config.json
index 05e195d..3ecb51b 100644
--- a/ios/App/App/capacitor.config.json
+++ b/ios/App/App/capacitor.config.json
@@ -9,8 +9,5 @@
"launchShowDuration": 0
}
},
- "server": {
- "url": "http://192.168.86.26:3000"
- },
"cordova": {}
}
diff --git a/package-lock.json b/package-lock.json
index e98917f..0d62412 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1582,6 +1582,12 @@
}
}
},
+ "csstype": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.5.tgz",
+ "integrity": "sha512-uVDi8LpBUKQj6sdxNaTetL6FpeCqTjOvAQuQUa/qAqq8oOd4ivkbhgnqayl0dnPal8Tb/yB1tF+gOvCBiicaiQ==",
+ "dev": true
+ },
"cyclist": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz",
@@ -1737,6 +1743,16 @@
}
}
},
+ "dom-helpers": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.0.tgz",
+ "integrity": "sha512-Ru5o9+V8CpunKnz5LGgWXkmrH/20cGKwcHwS4m73zIvs54CN9epEmT/HLqFJW3kXpakAFkEdzgy1hzlJe3E4OQ==",
+ "dev": true,
+ "requires": {
+ "@babel/runtime": "^7.8.7",
+ "csstype": "^3.0.2"
+ }
+ },
"dom-serializer": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.0.1.tgz",
@@ -4411,6 +4427,27 @@
"prop-types": "^15.5.8"
}
},
+ "react-toggle": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/react-toggle/-/react-toggle-4.1.1.tgz",
+ "integrity": "sha512-+wXlMcSpg8SmnIXauMaZiKpR+r2wp2gMUteroejp2UTSqGTVvZLN+m9EhMzFARBKEw7KpQOwzCyfzeHeAndQGw==",
+ "dev": true,
+ "requires": {
+ "classnames": "^2.2.5"
+ }
+ },
+ "react-transition-group": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.1.tgz",
+ "integrity": "sha512-Djqr7OQ2aPUiYurhPalTrVy9ddmFCCzwhqQmtN+J3+3DzLO209Fdr70QrN8Z3DsglWql6iY1lDWAfpFiBtuKGw==",
+ "dev": true,
+ "requires": {
+ "@babel/runtime": "^7.5.5",
+ "dom-helpers": "^5.0.1",
+ "loose-envify": "^1.4.0",
+ "prop-types": "^15.6.2"
+ }
+ },
"react-use-gesture": {
"version": "9.0.0-beta.11",
"resolved": "https://registry.npmjs.org/react-use-gesture/-/react-use-gesture-9.0.0-beta.11.tgz",
@@ -4497,6 +4534,12 @@
"resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz",
"integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc="
},
+ "reselect": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.0.0.tgz",
+ "integrity": "sha512-qUgANli03jjAyGlnbYVAV5vvnOmJnODyABz51RdBN7M4WaVu8mecZWgyQNkG8Yqe3KRGRt0l4K4B3XVEULC4CA==",
+ "dev": true
+ },
"resize-observer-polyfill": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz",
diff --git a/package.json b/package.json
index 421b1b1..a27a96b 100644
--- a/package.json
+++ b/package.json
@@ -26,6 +26,9 @@
"prettier": "^2.2.1",
"pullstate": "^1.20.5",
"react-spring": "^8.0.27",
- "react-use-gesture": "^9.0.0-beta.11"
+ "react-toggle": "^4.1.1",
+ "react-transition-group": "^4.4.1",
+ "react-use-gesture": "^9.0.0-beta.11",
+ "reselect": "^4.0.0"
}
}
diff --git a/pages/index.js b/pages/index.js
index e763f2a..ef09cd1 100644
--- a/pages/index.js
+++ b/pages/index.js
@@ -1,147 +1,59 @@
-import { useState } from 'react';
-import { Virtuoso } from 'react-virtuoso';
-import { flash, flashOutline, cog, cogOutline, home, homeOutline } from 'ionicons/icons';
+import { useDrag } from 'react-use-gesture';
import Store from '../store';
+import * as actions from '../store/actions';
+import * as selectors from '../store/selectors';
import App from '../components/ui/App';
import Backdrop from '../components/ui/Backdrop';
import Menu from '../components/ui/Menu';
import Modal from '../components/ui/Modal';
import Nav from '../components/ui/Nav';
+import PageStack from '../components/ui/PageStack';
import Tab from '../components/ui/Tab';
import TabBar from '../components/ui/TabBar';
-import List from '../components/ui/List';
-import ListItem from '../components/ui/ListItem';
-import Button from '../components/ui/Button';
import { SafeAreaProvider } from '../components/ui/SafeArea';
-
-import Home from '../components/pages/Home';
-import Feed from '../components/pages/Feed';
-import Settings from '../components/pages/Settings';
-import { useDrag } from 'react-use-gesture';
-
-const pages = [
- { id: 'home', title: 'Home', icon: homeOutline, selectedIcon: home, component: Home },
- {
- id: 'feed',
- title: 'Feed',
- icon: flashOutline,
- selectedIcon: flash,
- component: Feed,
- },
- {
- id: 'settings',
- title: 'Settings',
- icon: cogOutline,
- selectedIcon: cog,
- component: Settings,
- },
-];
+import Notifications from '../components/Notifications';
+import MenuContent from '../components/MenuContent';
+import { useEffect, useState } from 'react';
const CurrentPage = ({ page }) => {
+ const pages = Store.useState(selectors.getPages);
+ const currentPage = Store.useState(selectors.getCurrentPage);
+
+ const Page = currentPage.component;
+
return (
-
- {pages.map(p => {
+
+ {/*pages.map(p => {
const Page = p.component;
return ;
- })}
-
+ })*/}
+
+
);
};
-const MenuItem = ({ children }) => (
-
-
- {children}
-
-
-);
-const MenuContent = () => (
- <>
-
-
Menu
-
-
-
-
-
-
- >
-);
-
-const FakeNotification = ({ i }) => (
-
-
-
- You have a new friend request
-
-
-
-
-
-
-);
-
-const NotificationsContent = () => (
-
-
-
Notifications
-
-
- }
- />
-
-
-);
-
export default function Index() {
- const [page, setPage] = useState(pages[0]);
+ const showMenu = Store.useState(selectors.getMenuOpen);
+ const showNotifications = Store.useState(selectors.getNotificationsOpen);
+ const currentPage = Store.useState(selectors.getCurrentPage);
+ const tabs = Store.useState(selectors.getTabs);
- const showMenu = Store.useState(s => s.showMenu);
- const showNotifications = Store.useState(s => s.showNotifications);
-
- const closeMenu = () => {
- Store.update(s => {
- s.showMenu = false;
- });
- };
+ const closeMenu = () => actions.setMenuOpen(false);
const backdropClose = () => {
- Store.update(s => {
- s.showMenu = false;
- s.showNotifications = false;
- });
+ actions.setMenuOpen(false);
+ actions.setNotificationsOpen(false);
};
- const closeNotifications = () => {
- Store.update(s => {
- s.showNotifications = false;
- });
- };
+ const closeNotifications = () => actions.setNotificationsOpen(false);
// To enable edge drag detection to open the side menu
const bind = useDrag(
({ down, movement: [mx], xy: [x, y], cancel }) => {
if (mx > 5 && x < 50 && down) {
- Store.update(s => {
- s.showMenu = true;
- });
+ actions.setMenuOpen(true);
cancel();
}
},
@@ -163,16 +75,21 @@ export default function Index() {
-
-
+
+
- {pages.map(p => (
- setPage(p)} selected={p.id === page.id} />
+ {tabs.map(p => (
+ actions.setPage(p)}
+ selected={p.id === currentPage.id}
+ />
))}
-
+
diff --git a/store/actions.js b/store/actions.js
new file mode 100644
index 0000000..5aa67dc
--- /dev/null
+++ b/store/actions.js
@@ -0,0 +1,50 @@
+import Store from '.';
+
+export const setPageById = id => {
+ Store.update((s, o) => {
+ s.currentPage = o.pages.find(p => p.id === id);
+ });
+};
+
+export const setPage = page => {
+ Store.update((s, o) => {
+ s.currentPage = page;
+ });
+};
+
+export const setMenuOpen = open => {
+ Store.update(s => {
+ s.menuOpen = open;
+ });
+};
+
+export const setNotificationsOpen = open => {
+ Store.update(s => {
+ s.notificationsOpen = open;
+ });
+};
+
+export const setSettings = settings => {
+ Store.update(s => {
+ s.settings = settings;
+ });
+};
+
+// App-specific actions
+
+export const setDone = (list, item, done) => {
+ Store.update((s, o) => {
+ const listIndex = o.lists.findIndex(l => l === list);
+ const itemIndex = o.lists[listIndex].items.findIndex(i => i === item);
+ s.lists[listIndex].items[itemIndex].done = done;
+ if (list === o.selectedList) {
+ s.selectedList = s.lists[listIndex];
+ }
+ });
+};
+
+export const setSelectedList = list => {
+ Store.update(s => {
+ s.selectedList = list;
+ });
+};
diff --git a/store/index.js b/store/index.js
index 07236a9..eb3efcf 100644
--- a/store/index.js
+++ b/store/index.js
@@ -1,10 +1,110 @@
import { Store as PullStateStore } from 'pullstate';
+import { list, listOutline, cog, cogOutline, home, homeOutline } from 'ionicons/icons';
+
+import Home from '../components/pages/Home';
+import Lists from '../components/pages/Lists';
+import Settings from '../components/pages/Settings';
+import ListDetail from '../components/pages/ListDetail';
+
+// The available pages here
+const pages = [
+ { id: 'home', title: 'Home', icon: homeOutline, selectedIcon: home, component: Home },
+ {
+ id: 'lists',
+ title: 'Lists',
+ icon: listOutline,
+ selectedIcon: list,
+ component: Lists,
+ },
+ {
+ id: 'list-detail',
+ title: () => Store.getRawState().selectedList?.name,
+ icon: listOutline,
+ selectedIcon: list,
+ component: ListDetail,
+ },
+ {
+ id: 'settings',
+ title: 'Settings',
+ icon: cogOutline,
+ selectedIcon: cog,
+ component: Settings,
+ },
+];
+
+export const images = [
+ 'https://images.unsplash.com/photo-1608091526083-86ae8489ae5c?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=2100&q=80',
+ 'https://images.unsplash.com/photo-1608050072262-7b26ba63fb46?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=2100&q=80',
+ 'https://images.unsplash.com/photo-1607975218223-94f82613e833?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=934&q=80',
+ 'https://images.unsplash.com/photo-1608108707326-215150457c9f?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=2100&q=80',
+ 'https://images.unsplash.com/photo-1608057681073-9399f209e773?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=934&q=80',
+];
+
+export const homeItems = [
+ {
+ title: 'Welcome',
+ type: 'Blog',
+ text: 'Welcome to the app!',
+ author: 'Max',
+ image: images[0],
+ },
+ {
+ title: 'How to get started',
+ type: 'Article',
+ text: 'Getting started with the app is easy! Just follow these 100 steps',
+ author: 'Max',
+ image: images[1],
+ },
+ {
+ title: 'Need help?',
+ type: 'Support',
+ text: "We're here to help. Available between the hours of 3am and 3:01am every day",
+ author: 'Max',
+ image: images[2],
+ },
+];
+
+// Some fake lists
+const lists = [
+ {
+ name: 'Groceries',
+ id: 'groceries',
+ items: [{ name: 'Apples' }, { name: 'Bananas' }, { name: 'Milk' }, { name: 'Ice Cream' }],
+ },
+ {
+ name: 'Hardware Store',
+ id: 'hardware',
+ items: [
+ { name: 'Circular Saw' },
+ { name: 'Tack Cloth' },
+ { name: 'Drywall' },
+ { name: 'Router' },
+ ],
+ },
+ { name: 'Work', id: 'work', items: [{ name: 'TPS Report' }, { name: 'Set up email' }] },
+ { name: 'Reminders', id: 'reminders' },
+];
+
const Store = new PullStateStore({
safeAreaTop: 0,
safeAreaBottom: 0,
- showMenu: false,
- showNotifications: false,
+ menuOpen: false,
+ notificationsOpen: false,
+ currentPage: pages[0],
+ pages,
+
+ // The pages that are linked to tabs
+ tabs: pages.filter(p => ['home', 'lists', 'settings'].indexOf(p.id) >= 0),
+ // The pages that are linked to the menu
+ menuLinks: pages.filter(p => ['home', 'lists', 'settings'].indexOf(p.id) >= 0),
+
+ homeItems,
+ lists,
+ selectedList: null,
+ settings: {
+ enableNotifications: true,
+ },
});
export default Store;
diff --git a/store/selectors.js b/store/selectors.js
new file mode 100644
index 0000000..31a5b14
--- /dev/null
+++ b/store/selectors.js
@@ -0,0 +1,16 @@
+import { createSelector } from 'reselect';
+
+const getState = state => state;
+
+export const getMenuOpen = createSelector(getState, state => state.menuOpen);
+export const getNotificationsOpen = createSelector(getState, state => state.notificationsOpen);
+export const getCurrentPage = createSelector(getState, state => state.currentPage);
+export const getTabs = createSelector(getState, state => state.tabs);
+export const getMenuLinks = createSelector(getState, state => state.menuLinks);
+export const getPages = createSelector(getState, state => state.pages);
+
+// App specific selectors
+export const getHomeItems = createSelector(getState, state => state.homeItems);
+export const getLists = createSelector(getState, state => state.lists);
+export const getSelectedList = createSelector(getState, state => state.selectedList);
+export const getSettings = createSelector(getState, state => state.settings);
diff --git a/styles/global.css b/styles/global.css
index fbe6f15..592f028 100644
--- a/styles/global.css
+++ b/styles/global.css
@@ -52,3 +52,147 @@ body {
height: 100%;
width: 100%;
}
+
+/** Styles for React Toggle */
+
+.react-toggle {
+ touch-action: pan-x;
+
+ display: inline-block;
+ position: relative;
+ cursor: pointer;
+ background-color: transparent;
+ border: 0;
+ padding: 0;
+
+ -webkit-touch-callout: none;
+ -webkit-user-select: none;
+ -khtml-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+
+ -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
+ -webkit-tap-highlight-color: transparent;
+}
+
+.react-toggle-screenreader-only {
+ border: 0;
+ clip: rect(0 0 0 0);
+ height: 1px;
+ margin: -1px;
+ overflow: hidden;
+ padding: 0;
+ position: absolute;
+ width: 1px;
+}
+
+.react-toggle--disabled {
+ cursor: not-allowed;
+ opacity: 0.5;
+ -webkit-transition: opacity 0.25s;
+ transition: opacity 0.25s;
+}
+
+.react-toggle-track {
+ width: 50px;
+ height: 24px;
+ padding: 0;
+ border-radius: 30px;
+ background-color: #4d4d4d;
+ -webkit-transition: all 0.2s ease;
+ -moz-transition: all 0.2s ease;
+ transition: all 0.2s ease;
+}
+
+.react-toggle:hover:not(.react-toggle--disabled) .react-toggle-track {
+ background-color: #000000;
+}
+
+.react-toggle--checked .react-toggle-track {
+ background-color: #19ab27;
+}
+
+.react-toggle--checked:hover:not(.react-toggle--disabled) .react-toggle-track {
+ background-color: #128d15;
+}
+
+.react-toggle-track-check {
+ position: absolute;
+ width: 14px;
+ height: 10px;
+ top: 0px;
+ bottom: 0px;
+ margin-top: auto;
+ margin-bottom: auto;
+ line-height: 0;
+ left: 8px;
+ opacity: 0;
+ -webkit-transition: opacity 0.25s ease;
+ -moz-transition: opacity 0.25s ease;
+ transition: opacity 0.25s ease;
+}
+
+.react-toggle--checked .react-toggle-track-check {
+ opacity: 1;
+ -webkit-transition: opacity 0.25s ease;
+ -moz-transition: opacity 0.25s ease;
+ transition: opacity 0.25s ease;
+}
+
+.react-toggle-track-x {
+ position: absolute;
+ width: 10px;
+ height: 10px;
+ top: 0px;
+ bottom: 0px;
+ margin-top: auto;
+ margin-bottom: auto;
+ line-height: 0;
+ right: 10px;
+ opacity: 1;
+ -webkit-transition: opacity 0.25s ease;
+ -moz-transition: opacity 0.25s ease;
+ transition: opacity 0.25s ease;
+}
+
+.react-toggle--checked .react-toggle-track-x {
+ opacity: 0;
+}
+
+.react-toggle-thumb {
+ transition: all 0.5s cubic-bezier(0.23, 1, 0.32, 1) 0ms;
+ position: absolute;
+ top: 1px;
+ left: 1px;
+ width: 22px;
+ height: 22px;
+ border: 1px solid #4d4d4d;
+ border-radius: 50%;
+ background-color: #fafafa;
+
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+
+ -webkit-transition: all 0.25s ease;
+ -moz-transition: all 0.25s ease;
+ transition: all 0.25s ease;
+}
+
+.react-toggle--checked .react-toggle-thumb {
+ left: 27px;
+ border-color: #19ab27;
+}
+
+.react-toggle--focus .react-toggle-thumb {
+ -webkit-box-shadow: 0px 0px 3px 2px #0099e0;
+ -moz-box-shadow: 0px 0px 3px 2px #0099e0;
+ box-shadow: 0px 0px 2px 3px #0099e0;
+}
+
+.react-toggle:active:not(.react-toggle--disabled) .react-toggle-thumb {
+ -webkit-box-shadow: 0px 0px 5px 5px #0099e0;
+ -moz-box-shadow: 0px 0px 5px 5px #0099e0;
+ box-shadow: 0px 0px 5px 5px #0099e0;
+}