From 674bebcf7fcfc2fc7c5afb719736d36c1016d6bf Mon Sep 17 00:00:00 2001 From: Miha Drofenik Date: Fri, 5 Apr 2024 20:15:34 +0200 Subject: [PATCH] Fixed scanning problems and improved the flow (#36) Solved https://github.com/wildlifeai/wildlife-watcher-mobile-app/issues/35. --- src/components/AppDrawer.tsx | 15 +--- src/components/DeviceItem.tsx | 4 +- src/components/SideNavigation.tsx | 81 +++++++++++++++++++ src/hooks/useBleListeners.tsx | 59 +++++++------- src/navigation/index.tsx | 30 +++++++ .../screens/CommunityDiscussion.tsx | 13 +++ src/navigation/screens/Home.tsx | 44 ++++++---- src/navigation/screens/Notifications.tsx | 13 +++ src/navigation/screens/Profile.tsx | 13 +++ src/navigation/screens/Settings.tsx | 13 +++ src/providers/AuthProvider.tsx | 19 +---- src/redux/slices/devicesSlice.ts | 7 +- 12 files changed, 237 insertions(+), 74 deletions(-) create mode 100644 src/components/SideNavigation.tsx create mode 100644 src/navigation/screens/CommunityDiscussion.tsx create mode 100644 src/navigation/screens/Notifications.tsx create mode 100644 src/navigation/screens/Profile.tsx create mode 100644 src/navigation/screens/Settings.tsx diff --git a/src/components/AppDrawer.tsx b/src/components/AppDrawer.tsx index fff6468..d8df327 100644 --- a/src/components/AppDrawer.tsx +++ b/src/components/AppDrawer.tsx @@ -7,12 +7,13 @@ import { } from "react" import { StyleSheet, View } from "react-native" import { Drawer } from "react-native-drawer-layout" -import { Avatar, Button, Surface } from "react-native-paper" +import { Avatar, Surface } from "react-native-paper" import { WWText } from "./ui/WWText" import { useExtendedTheme } from "../theme" import { useAuth } from "../providers/AuthProvider" import { useSafeAreaInsets } from "react-native-safe-area-context" import { getReadableVersion } from "react-native-device-info" +import { SideNavigation } from "./SideNavigation" type DrawerContextProps = { isOpen: boolean @@ -25,14 +26,9 @@ export const useAppDrawer = () => useContext(DrawerContext) export const AppDrawer = ({ children }: PropsWithChildren) => { const [isOpen, setIsOpen] = useState(false) const { appPadding, spacing } = useExtendedTheme() - const { setIsLoggedIn, isLoggedIn } = useAuth() + const { isLoggedIn } = useAuth() const { top } = useSafeAreaInsets() - const onLogout = () => { - setIsLoggedIn(false) - setIsOpen(false) - } - return ( ) => { ]} > - I'm empty at the moment. - + Current version: diff --git a/src/components/DeviceItem.tsx b/src/components/DeviceItem.tsx index e68e6f9..8103aa7 100644 --- a/src/components/DeviceItem.tsx +++ b/src/components/DeviceItem.tsx @@ -8,15 +8,17 @@ type DeviceItemProps = { item: ExtendedPeripheral disconnect: (item: ExtendedPeripheral) => Promise go: (item: ExtendedPeripheral) => Promise + disabled?: boolean } export const DeviceItem = React.memo( - ({ item, disconnect, go }: DeviceItemProps) => { + ({ item, disconnect, go, disabled }: DeviceItemProps) => { return ( go(item)} onLongPress={() => disconnect(item)} + disabled={disabled} > > +} + +export const SideNavigation = ({ drawerControls }: Props) => { + const navigation = useAppNavigation() + const { setIsLoggedIn } = useAuth() + const { spacing, colors, appPadding } = useExtendedTheme() + + const goTo = (link: string) => { + navigation.navigate(link) + drawerControls(false) + } + + const onLogout = () => { + setIsLoggedIn(false) + drawerControls(false) + } + + return ( + + + + + + + + ) +} + +const styles = StyleSheet.create({ + list: { + flex: 1, + alignItems: "flex-start", + }, + link: { + margin: 10, + }, +}) diff --git a/src/hooks/useBleListeners.tsx b/src/hooks/useBleListeners.tsx index 6640921..4802f6e 100644 --- a/src/hooks/useBleListeners.tsx +++ b/src/hooks/useBleListeners.tsx @@ -243,6 +243,7 @@ export const useBleListeners = () => { ...DEFAULT_PERIPHERAL(peripheral.id), device: peripheral, name: peripheral.name, + signalLost: false, } /** @@ -267,41 +268,41 @@ export const useBleListeners = () => { const peripherals: Peripheral[] = await guard(() => BleManager.getDiscoveredPeripherals(), ) - const filteredPeripherals = peripherals.filter( - (p) => p.name === DEVICE_NAME, + const filteredPeripherals = peripherals.filter((p) => + p.name?.includes(DEVICE_NAME), ) - if (Array.isArray(filteredPeripherals) && filteredPeripherals.length > 0) { - const notFoundAnymore: Peripheral[] = [] + const notFoundAnymore: Peripheral[] = [] - Object.keys(devicesRef.current).forEach((key) => { - const peripheral = devicesRef.current[key] - if ( - !filteredPeripherals.find( - (filteredPeripheral) => filteredPeripheral.id === peripheral.id, - ) - ) { + Object.keys(devicesRef.current).forEach((key) => { + const peripheral = devicesRef.current[key] + if ( + !filteredPeripherals.find( + (filteredPeripheral) => filteredPeripheral.id === peripheral.id, + ) + ) { + if (peripheral.connected) { log(`Disconnecting device ${peripheral.id} after scan stopped.`) disconnectDevice(peripheral) - notFoundAnymore.push(peripheral) } - }) - - dispatch( - deviceSignalChanged({ - data: [ - ...filteredPeripherals.map((peripheral) => ({ - peripheral, - value: false, - })), - ...notFoundAnymore.map((peripheral) => ({ - peripheral, - value: true, - })), - ], - }), - ) - } + notFoundAnymore.push(peripheral) + } + }) + + dispatch( + deviceSignalChanged({ + data: [ + ...filteredPeripherals.map((peripheral) => ({ + peripheral, + value: false, + })), + ...notFoundAnymore.map((peripheral) => ({ + peripheral, + value: true, + })), + ], + }), + ) dispatch(scanStop()) log("Scan stopped.") diff --git a/src/navigation/index.tsx b/src/navigation/index.tsx index 4135a87..e43cf8b 100644 --- a/src/navigation/index.tsx +++ b/src/navigation/index.tsx @@ -14,13 +14,23 @@ import { Login } from "./screens/Login" import { useAuth } from "../providers/AuthProvider" import { AppLoading } from "./screens/AppLoading" import { AppDrawer } from "../components/AppDrawer" +import { Notifications } from "./screens/Notifications" +import { CommunityDiscussion } from "./screens/CommunityDiscussion" +import { Profile } from "./screens/Profile" +import { Settings } from "./screens/Settings" export interface RootStackParamList extends ParamListBase { + CommunityDiscussion: undefined + Notifications: undefined + Profile: undefined + Settings: undefined Home: undefined DeviceNavigator: { deviceId: string } Terminal: { deviceId: string } } +export type Routes = keyof RootStackParamList + export type AppParams = RouteProp< RootStackParamList, T @@ -110,6 +120,26 @@ export const MainNavigation = () => { component={Home} options={{ title: "Wildlife Watcher" }} /> + + + + { + return ( + + + Community discussion screen. + + + ) +} diff --git a/src/navigation/screens/Home.tsx b/src/navigation/screens/Home.tsx index 0f451c8..cce21d0 100644 --- a/src/navigation/screens/Home.tsx +++ b/src/navigation/screens/Home.tsx @@ -11,6 +11,7 @@ import { Button } from "react-native-paper" import { WWText } from "../../components/ui/WWText" import { WWScreenView } from "../../components/ui/WWScreenView" import { log } from "../../utils/logger" +import { useIsFocused } from "@react-navigation/native" export const Home = memo(() => { const { isBleConnecting, startScan, connectDevice, disconnectDevice } = @@ -19,17 +20,24 @@ export const Home = memo(() => { const { isScanning } = useAppSelector((state) => state.scanning) const navigation = useAppNavigation() const { bottom } = useSafeAreaInsets() + const isFocused = useIsFocused() const isBleBusy = isBleConnecting || isScanning const devicesToDisplay = useMemo(() => { - return Object.values(devices).sort((a, b) => { - if (a.rssi && b.rssi) { - if (b.rssi === 127 || a.rssi === 127) return -1 - return b.rssi - a.rssi - } - return -1 - }) + return Object.values(devices) + .sort((a, b) => { + if (a.rssi && b.rssi) { + if (b.rssi === 127 || a.rssi === 127) return -1 + return b.rssi - a.rssi + } + return -1 + }) + .filter((device) => !device.signalLost) + }, [devices]) + + const isAnyDeviceConnecting = useMemo(() => { + return !!Object.values(devices).find((device) => device.loading) }, [devices]) const disconnect = useCallback( @@ -57,23 +65,26 @@ export const Home = memo(() => { } useEffect(() => { - startScan(2) + if (isFocused) { + startScan(10) + } // eslint-disable-next-line react-hooks/exhaustive-deps - }, []) + }, [isFocused]) useEffect(() => { const interval = setInterval(() => { if (!isBleBusy) { - startScan(2) + startScan(10) } else { log("Scanning already taking place, skipping.") } - }, 4 * 1000) + }, 15 * 1000) return () => { + log("Clearing interval.") clearInterval(interval) } - }, [isScanning, isBleConnecting, isBleBusy, startScan]) + }, [isScanning, isBleConnecting, isBleBusy, startScan, isFocused]) return ( @@ -82,7 +93,7 @@ export const Home = memo(() => { @@ -95,7 +106,12 @@ export const Home = memo(() => { contentContainerStyle={styles.list} data={devicesToDisplay} renderItem={({ item }: { item: ExtendedPeripheral }) => ( - + )} keyExtractor={(item: ExtendedPeripheral) => item.id} /> diff --git a/src/navigation/screens/Notifications.tsx b/src/navigation/screens/Notifications.tsx new file mode 100644 index 0000000..a39ad90 --- /dev/null +++ b/src/navigation/screens/Notifications.tsx @@ -0,0 +1,13 @@ +import { View } from "react-native" +import { WWScreenView } from "../../components/ui/WWScreenView" +import { WWText } from "../../components/ui/WWText" + +export const Notifications = () => { + return ( + + + Notifications screen. + + + ) +} diff --git a/src/navigation/screens/Profile.tsx b/src/navigation/screens/Profile.tsx new file mode 100644 index 0000000..9c89c1d --- /dev/null +++ b/src/navigation/screens/Profile.tsx @@ -0,0 +1,13 @@ +import { View } from "react-native" +import { WWScreenView } from "../../components/ui/WWScreenView" +import { WWText } from "../../components/ui/WWText" + +export const Profile = () => { + return ( + + + Profile screen. + + + ) +} diff --git a/src/navigation/screens/Settings.tsx b/src/navigation/screens/Settings.tsx new file mode 100644 index 0000000..4de907f --- /dev/null +++ b/src/navigation/screens/Settings.tsx @@ -0,0 +1,13 @@ +import { View } from "react-native" +import { WWScreenView } from "../../components/ui/WWScreenView" +import { WWText } from "../../components/ui/WWText" + +export const Settings = () => { + return ( + + + Settings screen. + + + ) +} diff --git a/src/providers/AuthProvider.tsx b/src/providers/AuthProvider.tsx index 74dc629..5afd140 100644 --- a/src/providers/AuthProvider.tsx +++ b/src/providers/AuthProvider.tsx @@ -1,10 +1,4 @@ -import { - PropsWithChildren, - createContext, - useContext, - useEffect, - useState, -} from "react" +import { PropsWithChildren, createContext, useContext, useState } from "react" type AuthContextType = { isLoggedIn: boolean | undefined @@ -18,17 +12,6 @@ export const useAuth = () => useContext(AuthContext) export const AuthProvider = ({ children }: PropsWithChildren) => { const [isLoggedIn, setIsLoggedIn] = useState() - // Mock - useEffect(() => { - const t = setTimeout(() => { - setIsLoggedIn(false) - }, 2000) - - return () => { - clearTimeout(t) - } - }, []) - return ( {children} diff --git a/src/redux/slices/devicesSlice.ts b/src/redux/slices/devicesSlice.ts index 3229fb2..eb3325a 100644 --- a/src/redux/slices/devicesSlice.ts +++ b/src/redux/slices/devicesSlice.ts @@ -59,8 +59,13 @@ export const devicesSlice = createSlice({ if (state[id]) { state[id].connected = false + state[id].signalLost = true } else { - state[id] = { ...DEFAULT_PERIPHERAL(id), connected: false } + state[id] = { + ...DEFAULT_PERIPHERAL(id), + connected: false, + signalLost: true, + } } }, deviceLoading: (