Skip to content

Commit

Permalink
feat: add recording screens
Browse files Browse the repository at this point in the history
  • Loading branch information
billyjacoby committed Oct 13, 2023
1 parent 6f6b096 commit d7e3af9
Show file tree
Hide file tree
Showing 24 changed files with 373 additions and 38 deletions.
12 changes: 12 additions & 0 deletions ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -502,8 +502,12 @@ PODS:
- React-jsi (= 0.72.4)
- React-logger (= 0.72.4)
- React-perflogger (= 0.72.4)
- RNFlashList (1.6.1):
- React-Core
- RNGestureHandler (2.12.1):
- React-Core
- RNLocalize (3.0.2):
- React-Core
- RNReanimated (3.5.0):
- DoubleConversion
- FBLazyVector
Expand Down Expand Up @@ -610,7 +614,9 @@ DEPENDENCIES:
- React-runtimescheduler (from `../node_modules/react-native/ReactCommon/react/renderer/runtimescheduler`)
- React-utils (from `../node_modules/react-native/ReactCommon/react/utils`)
- ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`)
- "RNFlashList (from `../node_modules/@shopify/flash-list`)"
- RNGestureHandler (from `../node_modules/react-native-gesture-handler`)
- RNLocalize (from `../node_modules/react-native-localize`)
- RNReanimated (from `../node_modules/react-native-reanimated`)
- RNScreens (from `../node_modules/react-native-screens`)
- RNSVG (from `../node_modules/react-native-svg`)
Expand Down Expand Up @@ -722,8 +728,12 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native/ReactCommon/react/utils"
ReactCommon:
:path: "../node_modules/react-native/ReactCommon"
RNFlashList:
:path: "../node_modules/@shopify/flash-list"
RNGestureHandler:
:path: "../node_modules/react-native-gesture-handler"
RNLocalize:
:path: "../node_modules/react-native-localize"
RNReanimated:
:path: "../node_modules/react-native-reanimated"
RNScreens:
Expand Down Expand Up @@ -791,7 +801,9 @@ SPEC CHECKSUMS:
React-runtimescheduler: 4941cc1b3cf08b792fbf666342c9fc95f1969035
React-utils: b79f2411931f9d3ea5781404dcbb2fa8a837e13a
ReactCommon: 4b2bdcb50a3543e1c2b2849ad44533686610826d
RNFlashList: 236646d48f224a034f35baa0242e1b77db063b1e
RNGestureHandler: c0d04458598fcb26052494ae23dda8f8f5162b13
RNLocalize: dbea38dcb344bf80ff18a1757b1becf11f70cae4
RNReanimated: fd0dc9a5889bfac6c45a922b26c902dc6185b4dc
RNScreens: b21dc57dfa2b710c30ec600786a3fc223b1b92e7
RNSVG: ed492aaf3af9ca01bc945f7a149d76d62e73ec82
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,14 @@
"@react-navigation/bottom-tabs": "^6.5.8",
"@react-navigation/native": "^6.1.7",
"@react-navigation/native-stack": "^6.9.13",
"@shopify/flash-list": "^1.6.1",
"@types/react-native-video": "^5.0.15",
"clsx": "^2.0.0",
"nativewind": "^2.0.11",
"react": "18.2.0",
"react-native": "0.72.4",
"react-native-gesture-handler": "^2.12.1",
"react-native-localize": "^3.0.2",
"react-native-mmkv": "^2.10.2",
"react-native-reanimated": "^3.5.0",
"react-native-safe-area-context": "^4.7.1",
Expand Down
4 changes: 4 additions & 0 deletions src/assets/icons/play-rect.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions src/assets/icons/recording.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 5 additions & 2 deletions src/components/VideoPlayer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,26 @@ export const VideoPlayer = ({
videoURI,
isPaused = true,
snapshotURL,
isForcedFullscreen,
}: {
videoURI: string;
isPaused?: boolean;
snapshotURL?: string;
isForcedFullscreen?: boolean;
}) => {
React.useEffect(() => {}, []);
return (
<Video
poster={snapshotURL}
posterResizeMode="cover"
className="w-full h-full rounded-md"
resizeMode="cover"
className="w-full h-full"
resizeMode="contain"
ignoreSilentSwitch="ignore"
paused={isPaused}
pictureInPicture={true}
controls
source={{uri: videoURI}}
fullscreen={isForcedFullscreen}
/>
);
};
1 change: 1 addition & 0 deletions src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export * from './WebRTCView';
export * from './label';
export * from './snapshotCard';
export * from './VideoPlayer';
export * from './loadingView';
11 changes: 11 additions & 0 deletions src/components/loadingView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import {ActivityIndicator} from 'react-native';

import {BaseView} from './baseView';

export const LoadingView = () => {
return (
<BaseView className="flex-1">
<ActivityIndicator size={'large'} />
</BaseView>
);
};
4 changes: 4 additions & 0 deletions src/lib/api/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from './useCameraEvents';
export * from './useConfig';
export * from './useRecordings';
export * from './useRecordingSummary';
33 changes: 33 additions & 0 deletions src/lib/api/hooks/useRecordingSummary.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import {getTimeZone} from 'react-native-localize';
import {useQuery} from 'react-query';

import {API_BASE} from '@env';

import {RecordingResponse} from '../types/recordings';

const URL = `${API_BASE}api/:camera/recordings/summary?timezone=:tz`;

const fetchRecordingSummary = async (cameraName: string) => {
const url = URL.replace(':camera', cameraName).replace(':tz', getTimeZone());
try {
const response = await fetch(url, {method: 'get'});
const data = await response.json();
if (response.ok) {
if (data) {
return Promise.resolve(data as RecordingResponse[]);
}
} else {
return Promise.reject(new Error('ResNotOK'));
}
} catch (e) {
return Promise.reject(e);
}
};

export const useRecordingSummary = (cameraName?: string) => {
return useQuery({
queryFn: () => fetchRecordingSummary(cameraName || ''),
queryKey: ['recordings', cameraName],
enabled: !!cameraName,
});
};
34 changes: 34 additions & 0 deletions src/lib/api/hooks/useRecordings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import {useQuery} from 'react-query';

import {API_BASE} from '@env';

// const buildEventUrl = (eventId: string) => {
// const REC_URL = `${API_BASE}vod/event/${eventId}/index.m3u8`;
// return REC_URL;
// };

const URL = `${API_BASE}api/:camera/recordings`;

const fetchRecordings = async (cameraName: string) => {
const url = URL.replace(':camera', cameraName);
try {
const response = await fetch(url, {method: 'get'});
const data = await response.json();
if (response.ok) {
if (data) {
return Promise.resolve(data);
}
} else {
return Promise.reject(new Error('ResNotOK'));
}
} catch (e) {
return Promise.reject(e);
}
};

export const useRecordings = (cameraName?: string) =>
useQuery({
queryFn: () => fetchRecordings(cameraName || ''),
queryKey: ['recordings', cameraName],
enabled: !!cameraName,
});
3 changes: 1 addition & 2 deletions src/lib/api/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
export * from './hooks/useCameraEvents';
export * from './hooks/useConfig';
export * from './hooks';
export * from './getLatestCameraFrame';

//? Types
Expand Down
13 changes: 13 additions & 0 deletions src/lib/api/types/recordings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export interface RecordingResponse {
day: string;
events: number;
hours?: HoursEntity[] | null;
}

export interface HoursEntity {
duration: number;
events: number;
hour: string;
motion: number;
objects: number;
}
4 changes: 2 additions & 2 deletions src/navigation/CameraTabNavigator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {useColorScheme} from 'react-native';

import {createBottomTabNavigator} from '@react-navigation/bottom-tabs';

import {EventsScreen, LiveViewScreen} from '@screens';
import {EventsScreen, LiveViewScreen, RecordingsScreen} from '@screens';
import {hslToHex} from '@utils';

import {colors} from '../../themeColors.js';
Expand Down Expand Up @@ -39,7 +39,7 @@ export const CameraTabNavigator = () => {
})}>
<CameraTabStack.Screen name="Live" component={LiveViewScreen} />
<CameraTabStack.Screen name="Events" component={EventsScreen} />
<CameraTabStack.Screen name="Recordings" component={EventsScreen} />
<CameraTabStack.Screen name="Recordings" component={RecordingsScreen} />
</CameraTabStack.Navigator>
);
};
13 changes: 13 additions & 0 deletions src/navigation/RootNavigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
import {LeftHeaderButton, RightHeaderButton} from './components';
import {
BirdseyeScreen,
FullscreenVideoPlayer,
HomeScreen,
OnBoardingScreen,
SettingsScreen,
Expand All @@ -28,6 +29,10 @@ export type MainStackParamList = {
CameraTabs: NavigatorScreenParams<CameraTabsStackParamList>;
Settings: undefined;
Onboarding: undefined;
'Fullscreen Video': {
videoURI: string;
title?: string;
};
};

const Stack = createNativeStackNavigator<MainStackParamList>();
Expand Down Expand Up @@ -89,6 +94,14 @@ export const RootNavigation = () => {
presentation: 'modal',
}}
/>
<Stack.Screen
name="Fullscreen Video"
component={FullscreenVideoPlayer}
options={{
animation: 'slide_from_bottom',
headerBackTitleVisible: false,
}}
/>
</Stack.Navigator>
</NavigationContainer>
);
Expand Down
20 changes: 6 additions & 14 deletions src/screens/CameraScreens/EventsScreen/EventsScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,17 @@
import React from 'react';
import {
ActivityIndicator,
LayoutAnimation,
SectionList,
View,
} from 'react-native';
import {LayoutAnimation, SectionList, View} from 'react-native';

import clsx from 'clsx';

import {CameraEvent} from './components';
import {FrigateEvent, useCameraEvents} from '@api';
import {BaseText, BaseView} from '@components';
import {BaseText, BaseView, LoadingView} from '@components';
import {useAppDataStore} from '@stores';
import {bgBackground} from '@utils';

import {SectionDateHeader} from '../components';

import {EventListFooter} from './components/EventListFooter';
import {SectionDateHeader} from './components/SectionDateHeader';

export const EventsScreen = () => {
const currentCamera = useAppDataStore(state => state.currentCamera);
Expand Down Expand Up @@ -63,11 +59,7 @@ export const EventsScreen = () => {
};

if (isLoading) {
return (
<BaseView>
<ActivityIndicator size={'large'} />
</BaseView>
);
return <LoadingView />;
}

if (error || !groupedEvents || (!isLoading && !currentCamera)) {
Expand Down Expand Up @@ -104,7 +96,7 @@ export const EventsScreen = () => {
sections={groupedEvents}
renderSectionHeader={props => (
<SectionDateHeader
{...props}
title={props.section.title}
handleHeaderPress={handleHeaderPress}
isCollapsed={collapsedSections.has(props.section.title)}
/>
Expand Down
Loading

0 comments on commit d7e3af9

Please sign in to comment.