diff --git a/03_source/mobile/src/App.tsx b/03_source/mobile/src/App.tsx index 52fe519..79cc364 100644 --- a/03_source/mobile/src/App.tsx +++ b/03_source/mobile/src/App.tsx @@ -75,6 +75,8 @@ import DemoQrScanner from './pages/DemoQrScanner'; import DemoShopAppUi from './pages/DemoShopAppUi'; // DemoDictionaryApp import DemoDictionaryApp from './pages/DemoDictionaryApp'; +// demo-recipe-app +import DemoRecipeApp from './pages/DemoRecipeApp'; setupIonicReact(); @@ -140,6 +142,7 @@ const IonicApp: React.FC = ({ } /> } /> } /> + } /> {/* */} diff --git a/03_source/mobile/src/pages/DemoList/index.tsx b/03_source/mobile/src/pages/DemoList/index.tsx index dd90a4e..714e050 100644 --- a/03_source/mobile/src/pages/DemoList/index.tsx +++ b/03_source/mobile/src/pages/DemoList/index.tsx @@ -260,6 +260,15 @@ const SettingsPage: React.FC = ({ + + {/* */} + + router.push(paths.DEMO_RECIPE_APP, 'forward')}> + + Demo Recipe App + + + {/* REQ0058/logout */} diff --git a/03_source/mobile/src/pages/DemoRecipeApp/AppPages/Tab1.jsx b/03_source/mobile/src/pages/DemoRecipeApp/AppPages/Tab1.jsx new file mode 100644 index 0000000..a6f4101 --- /dev/null +++ b/03_source/mobile/src/pages/DemoRecipeApp/AppPages/Tab1.jsx @@ -0,0 +1,95 @@ +import { + IonButton, + IonButtons, + IonCol, + IonContent, + IonHeader, + IonIcon, + IonPage, + IonRow, + IonTitle, + IonToolbar, + useIonRouter, +} from '@ionic/react'; + +import { Geolocation } from '@capacitor/geolocation'; +import { useEffect, useState } from 'react'; +import { SkeletonDashboard } from '../components/SkeletonDashboard'; +import { chevronBackOutline, refreshOutline } from 'ionicons/icons'; +import { CurrentWeather } from '../components/CurrentWeather'; + +function Tab1() { + const router = useIonRouter(); + + const [currentWeather, setCurrentWeather] = useState(false); + + useEffect(() => { + getCurrentPosition(); + }, []); + + const getCurrentPosition = async () => { + setCurrentWeather(false); + const coordinates = await Geolocation.getCurrentPosition(); + getAddress(coordinates.coords); + }; + + const getAddress = async (coords) => { + const query = `${coords.latitude},${coords.longitude}`; + const response = await fetch( + `https://api.weatherapi.com/v1/current.json?key=f93eb660b2424258bf5155016210712&q=${query}` + ); + + const data = await response.json(); + console.log(data); + setCurrentWeather(data); + }; + + function handleBackClick() { + router.goBack(); + } + + return ( + + + + My Weather + + + getCurrentPosition()}> + + + + + + handleBackClick()}> + + + + + + + + + Dashboard + + + + + +

Here's your location based weather

+
+
+ +
+ {currentWeather ? ( + + ) : ( + + )} +
+
+
+ ); +} + +export default Tab1; diff --git a/03_source/mobile/src/pages/DemoRecipeApp/AppPages/Tab2.jsx b/03_source/mobile/src/pages/DemoRecipeApp/AppPages/Tab2.jsx new file mode 100644 index 0000000..216544f --- /dev/null +++ b/03_source/mobile/src/pages/DemoRecipeApp/AppPages/Tab2.jsx @@ -0,0 +1,81 @@ +import { + IonButton, + IonCol, + IonContent, + IonHeader, + IonPage, + IonRow, + IonSearchbar, + IonTitle, + IonToolbar, +} from '@ionic/react'; +import { useState } from 'react'; +import { CurrentWeather } from '../components/CurrentWeather'; + +function Tab2() { + const [search, setSearch] = useState(''); + const [currentWeather, setCurrentWeather] = useState(false); + + const performSearch = async () => { + getAddress(search); + }; + + const getAddress = async (city) => { + const response = await fetch( + `https://api.weatherapi.com/v1/current.json?key=f93eb660b2424258bf5155016210712&q=${city}&aqi=no` + ); + const data = await response.json(); + + if (data && data.current && data.location) { + setCurrentWeather(data); + } + }; + + return ( + + + + Search + + + + + + Search + + + + + + setSearch(e.target.value)} + /> + + + + + Search + + + + +
+ {currentWeather ? ( + + ) : ( +

Your search result will appear here

+ )} +
+
+
+ ); +} + +export default Tab2; diff --git a/03_source/mobile/src/pages/DemoRecipeApp/components/CurrentWeather/WeatherProperty.tsx b/03_source/mobile/src/pages/DemoRecipeApp/components/CurrentWeather/WeatherProperty.tsx new file mode 100644 index 0000000..52949af --- /dev/null +++ b/03_source/mobile/src/pages/DemoRecipeApp/components/CurrentWeather/WeatherProperty.tsx @@ -0,0 +1,62 @@ +import { IonCardSubtitle, IonCol, IonIcon, IonNote, IonRow } from '@ionic/react'; +import { pulseOutline, sunnyOutline, thermometerOutline } from 'ionicons/icons'; +import { useEffect, useState } from 'react'; + +export const WeatherProperty = ({ type, currentWeather }: { type: any; currentWeather: any }) => { + const [property, setProperty] = useState(false); + + const properties = { + wind: { + isIcon: false, + icon: '/assets/WeatherDemo/wind.png', + alt: 'wind', + label: 'Wind', + value: `${currentWeather.current.wind_mph}mph`, + }, + feelsLike: { + isIcon: true, + icon: thermometerOutline, + alt: 'feels like', + label: 'Feels like', + value: `${currentWeather.current.feelslike_c}°C`, + }, + indexUV: { + isIcon: true, + icon: sunnyOutline, + alt: 'index uv', + label: 'Index UV', + value: currentWeather.current.uv, + }, + pressure: { + isIcon: true, + icon: pulseOutline, + alt: 'pressure', + label: 'Pressure', + value: `${currentWeather.current.pressure_mb} mbar`, + }, + }; + + useEffect(() => { + setProperty(properties[type]); + }, [type]); + + return ( + + + + {!property.isIcon && ( + {property.alt} + )} + {property.isIcon && ( + + )} + + + + {property.label} + {property.value} + + + + ); +}; diff --git a/03_source/mobile/src/pages/DemoRecipeApp/components/CurrentWeather/index.tsx b/03_source/mobile/src/pages/DemoRecipeApp/components/CurrentWeather/index.tsx new file mode 100644 index 0000000..ceb4332 --- /dev/null +++ b/03_source/mobile/src/pages/DemoRecipeApp/components/CurrentWeather/index.tsx @@ -0,0 +1,48 @@ +import { IonCard, IonCardContent, IonGrid, IonRow, IonText, IonCardTitle } from '@ionic/react'; +import { WeatherProperty } from './WeatherProperty'; + +export const CurrentWeather = ({ currentWeather }: { currentWeather: any }) => ( + + + + +

+ {currentWeather.location.region},{' '} + {currentWeather.location.country} +

+
+ +
+ condition + + +

{currentWeather.current.condition.text}

+
+ + +

{new Date(currentWeather.location.localtime).toDateString()}

+
+
+ + + {currentWeather.current.temp_c}℃ + + + + + + + + + + + + + +
+
+
+); diff --git a/03_source/mobile/src/pages/DemoRecipeApp/components/SkeletonDashboard/index.tsx b/03_source/mobile/src/pages/DemoRecipeApp/components/SkeletonDashboard/index.tsx new file mode 100644 index 0000000..234fb9b --- /dev/null +++ b/03_source/mobile/src/pages/DemoRecipeApp/components/SkeletonDashboard/index.tsx @@ -0,0 +1,117 @@ +import { + IonCard, + IonCardContent, + IonCardSubtitle, + IonCardTitle, + IonCol, + IonGrid, + IonIcon, + IonNote, + IonRow, + IonSkeletonText, + IonText, + IonThumbnail, +} from '@ionic/react'; +import { pulseOutline, sunnyOutline, thermometerOutline } from 'ionicons/icons'; + +export const SkeletonDashboard = () => ( + + + + +

+ +

+
+ +
+ + + + + +

+ +

+
+ + +

+ +

+
+
+ + + + + + + + + + + wind + + + + Wind + + + + + + + + + + + + + + + Feels like + + + + + + + + + + + + + + + + + Index UV + + + + + + + + + + + + + + + Pressure + + + + + + + + +
+
+
+); diff --git a/03_source/mobile/src/pages/DemoRecipeApp/index.tsx b/03_source/mobile/src/pages/DemoRecipeApp/index.tsx new file mode 100644 index 0000000..da2fb7c --- /dev/null +++ b/03_source/mobile/src/pages/DemoRecipeApp/index.tsx @@ -0,0 +1,38 @@ +import { IonIcon, IonLabel, IonRouterOutlet, IonTabBar, IonTabButton, IonTabs } from '@ionic/react'; + +import { cloudOutline, searchOutline } from 'ionicons/icons'; +import { Route, Redirect } from 'react-router'; + +import Tab1 from './AppPages/Tab1'; +import Tab2 from './AppPages/Tab2'; + +function DemoWeatherApp() { + return ( + + + + + + + + + + + + + {/* */} + + + + Dashboard + + + + Search + + + + ); +} + +export default DemoWeatherApp; diff --git a/03_source/mobile/src/pages/DemoRecipeApp/style.scss b/03_source/mobile/src/pages/DemoRecipeApp/style.scss new file mode 100644 index 0000000..37c1e1a --- /dev/null +++ b/03_source/mobile/src/pages/DemoRecipeApp/style.scss @@ -0,0 +1,103 @@ +#about-page { + ion-toolbar { + position: absolute; + + top: 0; + left: 0; + right: 0; + + --background: transparent; + --color: white; + } + + ion-toolbar ion-back-button, + ion-toolbar ion-button, + ion-toolbar ion-menu-button { + --color: white; + } + + .about-header { + position: relative; + + width: 100%; + height: 30%; + } + + .about-header .about-image { + position: absolute; + + top: 0; + left: 0; + bottom: 0; + right: 0; + + background-position: center; + background-size: cover; + background-repeat: no-repeat; + + opacity: 0; + + transition: opacity 500ms ease-in-out; + } + + .about-header .madison { + background-image: url('/assets/WeatherDemo/img/about/madison.jpg'); + } + + .about-header .austin { + background-image: url('/assets/WeatherDemo/img/about/austin.jpg'); + } + + .about-header .chicago { + background-image: url('/assets/WeatherDemo/img/about/chicago.jpg'); + } + + .about-header .seattle { + background-image: url('/assets/WeatherDemo/img/about/seattle.jpg'); + } + + .about-info { + position: relative; + margin-top: -10px; + border-radius: 10px; + background: var(--ion-background-color, #fff); + z-index: 2; // display rounded border above header image + } + + .about-info h3 { + margin-top: 0; + } + + .about-info ion-list { + padding-top: 0; + } + + .about-info p { + line-height: 130%; + + color: var(--ion-color-dark); + } + + .about-info ion-icon { + margin-inline-end: 32px; + } + + /* + * iOS Only + */ + + .ios .about-info { + --ion-padding: 19px; + } + + .ios .about-info h3 { + font-weight: 700; + } +} + +#date-input-popover { + --offset-y: -var(--ion-safe-area-bottom); + + --max-width: 90%; + --width: 336px; +} diff --git a/03_source/mobile/src/paths.ts b/03_source/mobile/src/paths.ts index e3d23e3..5cec391 100644 --- a/03_source/mobile/src/paths.ts +++ b/03_source/mobile/src/paths.ts @@ -31,5 +31,6 @@ const paths = { DEMO_QR_SCANNER: '/demo-qr-scanner', DEMO_SHOP_APP_UI: '/demo-shop-app-ui', DEMO_DICTIONARY_APP: '/demo-dictionary-app', + DEMO_RECIPE_APP: '/demo-recipe-app', }; export default paths;