diff --git a/03_source/mobile/src/pages/DemoPullstateTutorial/components/Person.tsx b/03_source/mobile/src/pages/DemoPullstateTutorial/components/Person.tsx
new file mode 100644
index 0000000..2edc34d
--- /dev/null
+++ b/03_source/mobile/src/pages/DemoPullstateTutorial/components/Person.tsx
@@ -0,0 +1,24 @@
+import { IonAvatar, IonButton, IonItem, IonLabel } from '@ionic/react';
+import { toggleFollowing } from '../store/PeopleStore';
+
+export const Person = ({ person }): React.JSX.Element => {
+ return (
+
+
+
+
+
+ {person.name}
+ {person.title}
+
+
+ toggleFollowing(person.id)}
+ >
+ {person.following ? 'Following' : 'Follow'}
+
+
+ );
+};
diff --git a/03_source/mobile/src/pages/DemoPullstateTutorial/data/index.js b/03_source/mobile/src/pages/DemoPullstateTutorial/data/index.js
new file mode 100644
index 0000000..9e1fff8
--- /dev/null
+++ b/03_source/mobile/src/pages/DemoPullstateTutorial/data/index.js
@@ -0,0 +1,45 @@
+export const people = [
+
+ {
+ id: 1,
+ name: "Alan Montgomery",
+ title: "Mobile Team Lead",
+ avatar: "https://pbs.twimg.com/profile_images/1420489989163524096/GwHdYSky_400x400.jpg",
+ following: false
+ },
+ {
+ id: 2,
+ name: "Max Lynch",
+ title: "CEO | Co Founder",
+ avatar: "https://pbs.twimg.com/profile_images/1318970727173885953/bln98FNj_400x400.jpg",
+ following: false
+ },
+ {
+ id: 3,
+ name: "Mike Hartington",
+ title: "Senior Dev Rel",
+ avatar: "https://pbs.twimg.com/profile_images/1084993841898446849/DJ8XtR6L_400x400.jpg",
+ following: false
+ },
+ {
+ id: 4,
+ name: "Matt Netkow",
+ title: "Head of Product Marketing",
+ avatar: "https://pbs.twimg.com/profile_images/1323383930150621187/GKc0nVzi_400x400.jpg",
+ following: false
+ },
+ {
+ id: 5,
+ name: "Ben Sperry",
+ title: "CDO | Co Founder",
+ avatar: "https://pbs.twimg.com/profile_images/1407747959345795072/McJb-RvC_400x400.jpg",
+ following: false
+ },
+ {
+ id: 6,
+ name: "Liam DeBeasi",
+ title: "Software Engineer",
+ avatar: "https://pbs.twimg.com/profile_images/1105953692669366273/ZNK4lRAJ_400x400.jpg",
+ following: false
+ }
+];
\ No newline at end of file
diff --git a/03_source/mobile/src/pages/DemoPullstateTutorial/index.tsx b/03_source/mobile/src/pages/DemoPullstateTutorial/index.tsx
index 0b8f21b..f5313e3 100644
--- a/03_source/mobile/src/pages/DemoPullstateTutorial/index.tsx
+++ b/03_source/mobile/src/pages/DemoPullstateTutorial/index.tsx
@@ -1,16 +1,18 @@
import { IonIcon, IonLabel, IonRouterOutlet, IonTabBar, IonTabButton, IonTabs } from '@ionic/react';
-import { cloudOutline, searchOutline } from 'ionicons/icons';
+import { list, people } from 'ionicons/icons';
import { Route, Redirect } from 'react-router';
-import Tab1 from './AppPages/Tab1';
-import Tab2 from './AppPages/Tab2';
+// import Tab1 from './AppPages/Tab1';
+// import Tab2 from './AppPages/Tab2';
import './style.scss';
+import Tab1 from './pages/Tab1';
+import Tab2 from './pages/Tab2';
function DemoPullstateTutorial() {
return (
-
+
@@ -25,12 +27,12 @@ function DemoPullstateTutorial() {
{/* */}
-
- Dashboard
+
+ List
-
- Search
+
+ Following
diff --git a/03_source/mobile/src/pages/DemoPullstateTutorial/pages/Tab1.css b/03_source/mobile/src/pages/DemoPullstateTutorial/pages/Tab1.css
new file mode 100644
index 0000000..e69de29
diff --git a/03_source/mobile/src/pages/DemoPullstateTutorial/pages/Tab1.tsx b/03_source/mobile/src/pages/DemoPullstateTutorial/pages/Tab1.tsx
new file mode 100644
index 0000000..98513db
--- /dev/null
+++ b/03_source/mobile/src/pages/DemoPullstateTutorial/pages/Tab1.tsx
@@ -0,0 +1,63 @@
+import {
+ IonButton,
+ IonButtons,
+ IonContent,
+ IonHeader,
+ IonIcon,
+ IonPage,
+ IonTitle,
+ IonToolbar,
+ useIonRouter,
+} from '@ionic/react';
+import { PeopleStore } from '../store';
+import { Person } from '../components/Person';
+import './Tab1.css';
+import { useStoreState } from 'pullstate';
+import { getPeople } from '../store/Selectors';
+import { chevronBackOutline } from 'ionicons/icons';
+
+const Tab1 = (): React.JSX.Element => {
+ const people = useStoreState(PeopleStore, getPeople);
+
+ console.log(people);
+
+ const router = useIonRouter();
+ function handleBackClick() {
+ router.goBack();
+ }
+
+ return (
+
+
+
+ List of People
+
+
+ handleBackClick()}>
+
+
+
+
+
+
+
+
+ List of People
+
+
+ handleBackClick()}>
+
+
+
+
+
+
+ {people.map((person, index) => {
+ return ;
+ })}
+
+
+ );
+};
+
+export default Tab1;
diff --git a/03_source/mobile/src/pages/DemoPullstateTutorial/pages/Tab2.css b/03_source/mobile/src/pages/DemoPullstateTutorial/pages/Tab2.css
new file mode 100644
index 0000000..e69de29
diff --git a/03_source/mobile/src/pages/DemoPullstateTutorial/pages/Tab2.tsx b/03_source/mobile/src/pages/DemoPullstateTutorial/pages/Tab2.tsx
new file mode 100644
index 0000000..7a95a49
--- /dev/null
+++ b/03_source/mobile/src/pages/DemoPullstateTutorial/pages/Tab2.tsx
@@ -0,0 +1,34 @@
+import { IonContent, IonHeader, IonPage, IonTitle, IonToolbar } from '@ionic/react';
+import { useStoreState } from 'pullstate';
+import { Person } from '../components/Person';
+import { PeopleStore } from '../store';
+import { getFollowing } from '../store/Selectors';
+import './Tab2.css';
+import React from 'react';
+
+const Tab2 = (): React.JSX.Element => {
+ const people = useStoreState(PeopleStore, getFollowing);
+
+ return (
+
+
+
+ Following
+
+
+
+
+
+ Following
+
+
+
+ {people.map((person, index) => {
+ return ;
+ })}
+
+
+ );
+};
+
+export default Tab2;
diff --git a/03_source/mobile/src/pages/DemoPullstateTutorial/store/PeopleStore.js b/03_source/mobile/src/pages/DemoPullstateTutorial/store/PeopleStore.js
new file mode 100644
index 0000000..e80c158
--- /dev/null
+++ b/03_source/mobile/src/pages/DemoPullstateTutorial/store/PeopleStore.js
@@ -0,0 +1,19 @@
+import { Store } from "pullstate";
+
+import { people } from "../data";
+
+const PeopleStore = new Store({
+
+ people: people
+});
+
+export const toggleFollowing = personId => {
+
+ PeopleStore.update(s => {
+
+ const personIndex = s.people.findIndex(person => person.id === personId);
+ s.people[personIndex].following = !s.people[personIndex].following;
+ });
+}
+
+export default PeopleStore;
\ No newline at end of file
diff --git a/03_source/mobile/src/pages/DemoPullstateTutorial/store/Selectors.js b/03_source/mobile/src/pages/DemoPullstateTutorial/store/Selectors.js
new file mode 100644
index 0000000..1c3bdc6
--- /dev/null
+++ b/03_source/mobile/src/pages/DemoPullstateTutorial/store/Selectors.js
@@ -0,0 +1,7 @@
+import { createSelector } from "reselect";
+
+const getState = state => state;
+
+// Gets
+export const getPeople = createSelector(getState, state => state.people);
+export const getFollowing = createSelector(getState, state => state.people.filter(person => person.following));
\ No newline at end of file
diff --git a/03_source/mobile/src/pages/DemoPullstateTutorial/store/index.js b/03_source/mobile/src/pages/DemoPullstateTutorial/store/index.js
new file mode 100644
index 0000000..9beff78
--- /dev/null
+++ b/03_source/mobile/src/pages/DemoPullstateTutorial/store/index.js
@@ -0,0 +1 @@
+export { default as PeopleStore } from "./PeopleStore";
\ No newline at end of file
diff --git a/03_source/mobile/src/pages/DemoPullstateTutorial/theme/variables.scss b/03_source/mobile/src/pages/DemoPullstateTutorial/theme/variables.scss
new file mode 100644
index 0000000..8606e9f
--- /dev/null
+++ b/03_source/mobile/src/pages/DemoPullstateTutorial/theme/variables.scss
@@ -0,0 +1,79 @@
+.demo-pullstate-tutorial {
+ /* Ionic Variables and Theming. For more info, please see:
+http://ionicframework.com/docs/theming/ */
+
+ /** Ionic CSS Variables **/
+ :root {
+ /** primary **/
+ --ion-color-primary: #3880ff;
+ --ion-color-primary-rgb: 56, 128, 255;
+ --ion-color-primary-contrast: #ffffff;
+ --ion-color-primary-contrast-rgb: 255, 255, 255;
+ --ion-color-primary-shade: #3171e0;
+ --ion-color-primary-tint: #4c8dff;
+
+ /** secondary **/
+ --ion-color-secondary: #3dc2ff;
+ --ion-color-secondary-rgb: 61, 194, 255;
+ --ion-color-secondary-contrast: #ffffff;
+ --ion-color-secondary-contrast-rgb: 255, 255, 255;
+ --ion-color-secondary-shade: #36abe0;
+ --ion-color-secondary-tint: #50c8ff;
+
+ /** tertiary **/
+ --ion-color-tertiary: #5260ff;
+ --ion-color-tertiary-rgb: 82, 96, 255;
+ --ion-color-tertiary-contrast: #ffffff;
+ --ion-color-tertiary-contrast-rgb: 255, 255, 255;
+ --ion-color-tertiary-shade: #4854e0;
+ --ion-color-tertiary-tint: #6370ff;
+
+ /** success **/
+ --ion-color-success: #2dd36f;
+ --ion-color-success-rgb: 45, 211, 111;
+ --ion-color-success-contrast: #ffffff;
+ --ion-color-success-contrast-rgb: 255, 255, 255;
+ --ion-color-success-shade: #28ba62;
+ --ion-color-success-tint: #42d77d;
+
+ /** warning **/
+ --ion-color-warning: #ffc409;
+ --ion-color-warning-rgb: 255, 196, 9;
+ --ion-color-warning-contrast: #000000;
+ --ion-color-warning-contrast-rgb: 0, 0, 0;
+ --ion-color-warning-shade: #e0ac08;
+ --ion-color-warning-tint: #ffca22;
+
+ /** danger **/
+ --ion-color-danger: #eb445a;
+ --ion-color-danger-rgb: 235, 68, 90;
+ --ion-color-danger-contrast: #ffffff;
+ --ion-color-danger-contrast-rgb: 255, 255, 255;
+ --ion-color-danger-shade: #cf3c4f;
+ --ion-color-danger-tint: #ed576b;
+
+ /** dark **/
+ --ion-color-dark: #222428;
+ --ion-color-dark-rgb: 34, 36, 40;
+ --ion-color-dark-contrast: #ffffff;
+ --ion-color-dark-contrast-rgb: 255, 255, 255;
+ --ion-color-dark-shade: #1e2023;
+ --ion-color-dark-tint: #383a3e;
+
+ /** medium **/
+ --ion-color-medium: #92949c;
+ --ion-color-medium-rgb: 146, 148, 156;
+ --ion-color-medium-contrast: #ffffff;
+ --ion-color-medium-contrast-rgb: 255, 255, 255;
+ --ion-color-medium-shade: #808289;
+ --ion-color-medium-tint: #9d9fa6;
+
+ /** light **/
+ --ion-color-light: #f4f5f8;
+ --ion-color-light-rgb: 244, 245, 248;
+ --ion-color-light-contrast: #000000;
+ --ion-color-light-contrast-rgb: 0, 0, 0;
+ --ion-color-light-shade: #d7d8da;
+ --ion-color-light-tint: #f5f6f9;
+ }
+}