This commit is contained in:
louiscklaw
2025-06-05 14:05:24 +08:00
parent c2a02cff77
commit 56d43062c9
13 changed files with 673 additions and 0 deletions

View File

@@ -0,0 +1,96 @@
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);
};
// const router = useIonRouter();
function handleBackClick() {
router.goBack();
}
return (
<IonPage>
<IonHeader>
<IonToolbar>
<IonTitle>My Weather</IonTitle>
<IonButtons slot="end">
<IonButton onClick={() => getCurrentPosition()}>
<IonIcon icon={refreshOutline} color="primary" />
</IonButton>
</IonButtons>
<IonButtons slot="start">
<IonButton onClick={() => handleBackClick()}>
<IonIcon icon={chevronBackOutline} color="primary" />
</IonButton>
</IonButtons>
</IonToolbar>
</IonHeader>
<IonContent fullscreen>
<IonHeader collapse="condense">
<IonToolbar>
<IonTitle size="large">Dashboard</IonTitle>
</IonToolbar>
</IonHeader>
<IonRow className="ion-margin-start ion-margin-end ion-justify-content-center ion-text-center">
<IonCol size="12">
<h4>Here's your location based weather</h4>
</IonCol>
</IonRow>
<div style={{ marginTop: '-1.5rem' }}>
{currentWeather ? (
<CurrentWeather currentWeather={currentWeather} />
) : (
<SkeletonDashboard />
)}
</div>
</IonContent>
</IonPage>
);
}
export default Tab1;

View File

@@ -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 (
<IonPage>
<IonHeader>
<IonToolbar>
<IonTitle>Search</IonTitle>
</IonToolbar>
</IonHeader>
<IonContent fullscreen>
<IonHeader collapse="condense">
<IonToolbar>
<IonTitle size="large">Search</IonTitle>
</IonToolbar>
</IonHeader>
<IonRow className="ion-justify-content-center ion-margin-top ion-align-items-center">
<IonCol size="7">
<IonSearchbar
placeholder="Try 'London'"
animated
value={search}
onIonChange={(e) => setSearch(e.target.value)}
/>
</IonCol>
<IonCol size="5">
<IonButton
expand="block"
className="ion-margin-start ion-margin-end"
onClick={performSearch}
>
Search
</IonButton>
</IonCol>
</IonRow>
<div style={{ marginTop: '-0.8rem' }}>
{currentWeather ? (
<CurrentWeather currentWeather={currentWeather} />
) : (
<h3 className="ion-text-center">Your search result will appear here</h3>
)}
</div>
</IonContent>
</IonPage>
);
}
export default Tab2;

View File

@@ -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 (
<IonCol size="6">
<IonRow className="ion-justify-content-center ion-align-items-center">
<IonCol size="3">
{!property.isIcon && (
<img alt={property.alt} src={property.icon} height="32" width="32" />
)}
{property.isIcon && (
<IonIcon icon={property.icon} color="medium" style={{ fontSize: '2rem' }} />
)}
</IonCol>
<IonCol size="9">
<IonCardSubtitle>{property.label}</IonCardSubtitle>
<IonNote>{property.value}</IonNote>
</IonCol>
</IonRow>
</IonCol>
);
};

View File

@@ -0,0 +1,48 @@
import { IonCard, IonCardContent, IonGrid, IonRow, IonText, IonCardTitle } from '@ionic/react';
import { WeatherProperty } from './WeatherProperty';
export const CurrentWeather = ({ currentWeather }: { currentWeather: any }) => (
<IonGrid>
<IonCard>
<IonCardContent className="ion-text-center">
<IonText color="primary">
<h1>
{currentWeather.location.region},{' '}
<span style={{ color: 'gray' }}>{currentWeather.location.country}</span>
</h1>
</IonText>
<div className="ion-margin-top">
<img
alt="condition"
src={currentWeather.current.condition.icon.replace('//', 'https://')}
/>
<IonText color="dark">
<h1 style={{ fontWeight: 'bold' }}>{currentWeather.current.condition.text}</h1>
</IonText>
<IonText color="medium">
<p>{new Date(currentWeather.location.localtime).toDateString()}</p>
</IonText>
</div>
<IonCardTitle style={{ fontSize: '3rem' }} className="ion-margin-top">
{currentWeather.current.temp_c}&#8451;
</IonCardTitle>
<IonGrid className="ion-margin-top">
<IonRow>
<WeatherProperty type="wind" currentWeather={currentWeather} />
<WeatherProperty type="feelsLike" currentWeather={currentWeather} />
</IonRow>
<IonRow className="ion-margin-top">
<WeatherProperty type="indexUV" currentWeather={currentWeather} />
<WeatherProperty type="pressure" currentWeather={currentWeather} />
</IonRow>
</IonGrid>
</IonCardContent>
</IonCard>
</IonGrid>
);

View File

@@ -0,0 +1,24 @@
.container {
text-align: center;
position: absolute;
left: 0;
right: 0;
top: 50%;
transform: translateY(-50%);
}
.container strong {
font-size: 20px;
line-height: 26px;
}
.container p {
font-size: 16px;
line-height: 22px;
color: #8c8c8c;
margin: 0;
}
.container a {
text-decoration: none;
}

View File

@@ -0,0 +1,14 @@
import './ExploreContainer.css';
interface ContainerProps { }
const ExploreContainer: React.FC<ContainerProps> = () => {
return (
<div className="container">
<strong>Ready to create an app?</strong>
<p>Start with Ionic <a target="_blank" rel="noopener noreferrer" href="https://ionicframework.com/docs/components">UI Components</a></p>
</div>
);
};
export default ExploreContainer;

View File

@@ -0,0 +1,54 @@
import { IonButton, IonCol, IonContent, IonGrid, IonIcon, IonLabel, IonNote, IonRow } from '@ionic/react';
import { globeOutline, heartOutline, locationOutline, navigateOutline, phonePortraitOutline } from 'ionicons/icons';
export const MarkerInfoWindow = ({ marker, dismiss }) => {
return (
<IonContent>
<IonGrid className="ion-padding">
<IonRow className="ion-margin-bottom">
<IonCol size="12">
<IonLabel>
<h1>{marker.title}</h1>
<IonNote>{marker.description}</IonNote>
</IonLabel>
</IonCol>
</IonRow>
<IonRow className="ion-justify-content-start ion-align-items-center">
<IonCol size="2">
<IonIcon icon={locationOutline} color="primary" style={{ fontSize: '1.5rem' }} />
</IonCol>
<IonCol size="10">{marker.address}</IonCol>
</IonRow>
<IonRow className="ion-justify-content-start ion-align-items-center">
<IonCol size="2">
<IonIcon icon={globeOutline} color="primary" style={{ fontSize: '1.5rem' }} />
</IonCol>
<IonCol size="10">{marker.website}</IonCol>
</IonRow>
<IonRow className="ion-justify-content-start ion-align-items-center">
<IonCol size="2">
<IonIcon icon={phonePortraitOutline} color="primary" style={{ fontSize: '1.5rem' }} />
</IonCol>
<IonCol size="10">{marker.phone}</IonCol>
</IonRow>
<IonRow>
<IonButton>
<IonIcon icon={heartOutline} />
&nbsp; Favourite
</IonButton>
<IonButton>
<IonIcon icon={navigateOutline} />
&nbsp; Navigate
</IonButton>
</IonRow>
</IonGrid>
</IonContent>
);
};

View File

@@ -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 = () => (
<IonGrid>
<IonCard>
<IonCardContent className="ion-text-center">
<IonText color="primary">
<h1>
<IonSkeletonText animated style={{ height: '2rem', width: '90%' }} />
</h1>
</IonText>
<div className="ion-margin-top">
<IonThumbnail>
<IonSkeletonText animated style={{ width: '2rem', height: '2rem' }} />
</IonThumbnail>
<IonText color="dark">
<h1 style={{ fontWeight: 'bold' }}>
<IonSkeletonText animated style={{ height: '2rem', width: '90%' }} />
</h1>
</IonText>
<IonText color="medium">
<p>
<IonSkeletonText animated style={{ height: '2rem', width: '90%' }} />
</p>
</IonText>
</div>
<IonCardTitle style={{ fontSize: '3rem' }} className="ion-margin-top">
<IonSkeletonText animated style={{ height: '3rem', width: '30%', textAlign: 'center' }} />
</IonCardTitle>
<IonGrid className="ion-margin-top">
<IonRow>
<IonCol size="6">
<IonRow className="ion-justify-content-center ion-align-items-center">
<IonCol size="3">
<img alt="wind" src="/assets/WeatherDemo/wind.png" height="32" width="32" />
</IonCol>
<IonCol size="9">
<IonCardSubtitle>Wind</IonCardSubtitle>
<IonNote>
<IonSkeletonText animated style={{ height: '2rem', width: '90%' }} />
</IonNote>
</IonCol>
</IonRow>
</IonCol>
<IonCol size="6">
<IonRow className="ion-justify-content-center ion-align-items-center">
<IonCol size="3">
<IonIcon icon={thermometerOutline} color="medium" style={{ fontSize: '2rem' }} />
</IonCol>
<IonCol size="9">
<IonCardSubtitle>Feels like</IonCardSubtitle>
<IonNote>
<IonSkeletonText animated style={{ height: '2rem', width: '90%' }} />
</IonNote>
</IonCol>
</IonRow>
</IonCol>
</IonRow>
<IonRow className="ion-margin-top">
<IonCol size="6">
<IonRow className="ion-justify-content-center ion-align-items-center">
<IonCol size="3">
<IonIcon icon={sunnyOutline} color="medium" style={{ fontSize: '2rem' }} />
</IonCol>
<IonCol size="9">
<IonCardSubtitle>Index UV</IonCardSubtitle>
<IonNote>
<IonSkeletonText animated style={{ height: '2rem', width: '90%' }} />
</IonNote>
</IonCol>
</IonRow>
</IonCol>
<IonCol size="6">
<IonRow className="ion-justify-content-center ion-align-items-center">
<IonCol size="3">
<IonIcon icon={pulseOutline} color="medium" style={{ fontSize: '2rem' }} />
</IonCol>
<IonCol size="9">
<IonCardSubtitle>Pressure</IonCardSubtitle>
<IonNote>
<IonSkeletonText animated style={{ height: '2rem', width: '90%' }} />
</IonNote>
</IonCol>
</IonRow>
</IonCol>
</IonRow>
</IonGrid>
</IonCardContent>
</IonCard>
</IonGrid>
);

View File

@@ -0,0 +1,8 @@
export const markers = [
{lat: 54.5258924, lng: -6.0812478, title: "Man Lee", description: "Chinese Takeaway", address: "122 Ballymacash Rd, Lisburn BT28 3EZ", website: "https://m.facebook.com/pages/Man-Lee/228415820694835", phone: "028 92 662853"},
{lat: 54.5097827, lng: -6.0572343, title: "Cam Hing", description: "Chinese Takeaway", address: "70 Longstone St, Lisburn BT28 1TR", website: "https://camhinglisbunr.com", phone: "028 92 677928"},
{lat: 54.5095162, lng: -6.0595896, title: "Golden Garden", description: "Chinese Takeaway", address: "140 Longstone St, Lisburn BT28 1TR", website: "https://golden-garden.business.site", phone: "028 92 671311"},
{lat: 54.5091808, lng: -6.0363902, title: "Pagoda", description: "Chinese Takeaway", address: "79 Sloan St, Lisburn BT27 5AG", website: "https://pagodalisburn.com", phone: "028 92 665289"},
{lat: 54.5989611, lng: -5.9972126, title: "Little Wing", description: "Pizzeria", address: "10 Ann St, Belfast BT1 4EF", website: "https://littlewingpizzeria.com", phone: "028 90 247000"}
];

View File

@@ -0,0 +1,34 @@
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';
import Home from './pages/Home';
import './style.scss';
function DemoCapacitorGoogleMapsTutorial() {
return (
<IonTabs>
<IonRouterOutlet>
<Route exact path="/demo-capacitor-google-maps-tutorial/tab1">
<Tab1 />
</Route>
<Route exact path="/demo-capacitor-google-maps-tutorial/tab2">
<Tab2 />
</Route>
<Route exact path="/demo-capacitor-google-maps-tutorial/home">
<Home />
</Route>
<Redirect exact path="/demo-capacitor-google-maps-tutorial" to="/demo-capacitor-google-maps-tutorial/home" />
</IonRouterOutlet>
</IonTabs>
);
}
export default DemoCapacitorGoogleMapsTutorial;

View File

@@ -0,0 +1,5 @@
capacitor-google-map {
display: inline-block;
width: 100%;
height: 86vh;
}

View File

@@ -0,0 +1,128 @@
import {
IonButton,
IonButtons,
IonCol,
IonContent,
IonHeader,
IonIcon,
IonPage,
IonRow,
IonTitle,
IonToolbar,
useIonModal,
useIonRouter,
useIonViewWillEnter,
} from '@ionic/react';
import { useRef, useState } from 'react';
import './Home.css';
import { GoogleMap } from '@capacitor/google-maps';
import { markers } from '../data';
import { MarkerInfoWindow } from '../components/MarkerInfoWindow.jsx';
import { chevronBackOutline } from 'ionicons/icons';
const Home = () => {
// This key is now dead!
// Replace with your own :)
// Remember to secure keys using env files or requesting from server!
// This was for demo purposes :)
const key = 'AIzaSyBJwKYcub1yNcDd2V8iu4ZfGvDi4eW_fpU';
let newMap;
const mapRef = useRef(null);
const [selectedMarker, setSelectedMarker] = useState(null);
const [present, dismiss] = useIonModal(MarkerInfoWindow, {
marker: selectedMarker,
});
const modalOptions = {
initialBreakpoint: 0.4,
breakpoints: [0, 0.4],
backdropBreakpoint: 0,
onDidDismiss: () => dismiss(),
};
const [mapConfig, setMapConfig] = useState({
zoom: 10,
center: {
lat: markers[0].lat,
lng: markers[0].lng,
},
});
const markerClick = (marker) => {
setSelectedMarker(markers.filter((m) => m.lat === marker.latitude && m.lng === marker.longitude)[0]);
present(modalOptions);
};
const addMapMarker = async (marker) => {
await newMap.addMarker({
coordinate: {
lat: marker.lat,
lng: marker.lng,
},
title: marker.title,
});
};
const addMapMarkers = () => markers.forEach((marker) => addMapMarker(marker));
const createMap = async () => {
if (!mapRef.current) return;
newMap = await GoogleMap.create({
id: 'google-map',
element: mapRef.current,
apiKey: key,
config: mapConfig,
});
newMap.setOnMarkerClickListener((marker) => markerClick(marker));
addMapMarkers();
};
useIonViewWillEnter(() => createMap());
const router = useIonRouter();
function handleBackClick() {
router.goBack();
}
return (
<IonPage>
<IonHeader>
<IonToolbar>
<IonTitle>Capacitor Google Map</IonTitle>
<IonButtons slot="start">
<IonButton onClick={() => handleBackClick()}>
<IonIcon icon={chevronBackOutline} color="primary" />
</IonButton>
</IonButtons>
</IonToolbar>
</IonHeader>
<IonContent fullscreen>
<IonHeader collapse="condense">
<IonToolbar>
<IonTitle size="large">Capacitor Google Map</IonTitle>
<IonButtons slot="start">
<IonButton onClick={() => handleBackClick()}>
<IonIcon icon={chevronBackOutline} color="primary" />
</IonButton>
</IonButtons>
</IonToolbar>
</IonHeader>
<IonRow>
<IonCol size="12">
<capacitor-google-map ref={mapRef} id="map"></capacitor-google-map>
</IonCol>
</IonRow>
</IonContent>
</IonPage>
);
};
export default Home;

View File

@@ -0,0 +1,2 @@
.demo-capacitor-google-maps-tutorial {
}