diff --git a/README.md b/README.md index d9ee3f1..d4322dc 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,37 @@ -![Blitz Scouter](https://i.imgur.com/eANWZcA.png "Blitz Scouter") +

+ +Blitz Scouter + +

-# Features -- Easy to use -- Import from [The Blue Alliance](https://www.thebluealliance.com/) -- Import & Export data without internet access -- Customizable color palette +

+ +GitHub all releases + + +GitHub issues + +

+ +

+BlitzScouter Matches +BlitzScouter Teams +

# Download -.APKs can be downloaded and installed under the [Releases](https://github.com/NB-Blitz/BlitzScouter-Offline/releases) tab +At the moment, BlitzScouter is only supported on **Android** devices. APKs can be downloaded and installed under the [Releases](https://github.com/NB-Blitz/BlitzScouter/releases) tab. + +# Features +- 🎨 Customizable color palette +- 📋 Customizable scouting templates +- 📷 Take and manage robot photos +- 💾 Take event data from [The Blue Alliance](https://www.thebluealliance.com/) offline +- 📁 Share data using json, csv, or QR codes +- 🥊 Quick match summaries for strategy +- 🧑‍🤝‍🧑 Sort and analyze teams # Building -Requires [NPM](https://www.npmjs.com/) or [Yarn](https://yarnpkg.com/) +Requires [Node](https://nodejs.org/en/) 1. Install Expo: ``` npm install --global expo-cli diff --git a/app.json b/app.json index 9ce7813..5cfbd76 100644 --- a/app.json +++ b/app.json @@ -2,7 +2,7 @@ "expo": { "name": "Blitz Scouter", "slug": "BlitzScouter", - "version": "1.0.3", + "version": "1.1.0", "orientation": "portrait", "scheme": "blitz", "icon": "./assets/images/icon.png", @@ -22,7 +22,7 @@ "ios": { "supportsTablet": true, "bundleIdentifier": "com.team5148.blitzscouter", - "buildNumber": "1.0.3", + "buildNumber": "1.1.0", "icon": "./assets/images/icon.png" }, "android": { diff --git a/components/elements/CheckboxElement.tsx b/components/elements/CheckboxElement.tsx index 0ddb82f..1b3c830 100644 --- a/components/elements/CheckboxElement.tsx +++ b/components/elements/CheckboxElement.tsx @@ -29,7 +29,7 @@ export default function CheckboxElement(props: ElementProps) { // Vibrate if (isChecked) - Vibration.vibrate(10); + Vibration.vibrate(30); else Vibration.vibrate(100); diff --git a/components/elements/CounterElement.tsx b/components/elements/CounterElement.tsx index c03a7ca..31d2e22 100644 --- a/components/elements/CounterElement.tsx +++ b/components/elements/CounterElement.tsx @@ -34,7 +34,7 @@ export default function CounterElement(props: ElementProps) { // Vibrate if (delta > 0) - Vibration.vibrate(10); + Vibration.vibrate(30); else Vibration.vibrate(100); diff --git a/hooks/useCompressedData.ts b/hooks/useCompressedData.ts index 8ba23e2..78b7265 100644 --- a/hooks/useCompressedData.ts +++ b/hooks/useCompressedData.ts @@ -75,21 +75,26 @@ export function useJSONImporter() { // Check Duplicates if (importedIDs.includes(decompressedData.exportID)) { + ToastAndroid.show("Duplicate JSON Data", ToastAndroid.SHORT); return; } importedIDs.push(decompressedData.exportID); setImportedIDs(importedIDs); - // Check Event - if (decompressedData.eventID !== event.id) { - ToastAndroid.show("Invalid Event", ToastAndroid.SHORT); - return; - } - - // Append Data - scoutingData.push(...decompressedData.scoutingData); + /* // Check Event + * if (decompressedData.eventID !== event.id) { + * ToastAndroid.show("Invalid Event", ToastAndroid.SHORT); + * return; + * } + */ + + // Filter & Append Data + const uniqueData = decompressedData.scoutingData.filter(scoutA => + scoutingData.findIndex(scoutB => scoutB.id === scoutA.id) === -1 + ); + scoutingData.push(...uniqueData); setScoutingData(scoutingData); - ToastAndroid.show("Imported " + scoutingData.length + " matches.", ToastAndroid.SHORT); + ToastAndroid.show("Imported " + uniqueData.length + " matches.", ToastAndroid.SHORT); } catch (e) { ToastAndroid.show("Invalid Data Import", ToastAndroid.SHORT); diff --git a/navigation/RootNavigator.tsx b/navigation/RootNavigator.tsx index acc1127..2901da3 100644 --- a/navigation/RootNavigator.tsx +++ b/navigation/RootNavigator.tsx @@ -82,7 +82,7 @@ export default function RootNavigator() { - + diff --git a/navigation/TabNavigator.tsx b/navigation/TabNavigator.tsx index a77583f..69c004a 100644 --- a/navigation/TabNavigator.tsx +++ b/navigation/TabNavigator.tsx @@ -1,7 +1,7 @@ import { MaterialIcons } from '@expo/vector-icons'; import { BottomTabBarButtonProps, createBottomTabNavigator } from '@react-navigation/bottom-tabs'; import * as React from 'react'; -import { PixelRatio, TouchableNativeFeedback, View } from 'react-native'; +import { TouchableNativeFeedback, View } from 'react-native'; import { usePalette } from '../hooks/usePalette'; import MatchesScreen from '../screens/Matches/MatchesScreen'; import SettingsScreen from '../screens/Settings/SettingsScreen'; @@ -13,8 +13,6 @@ const Tab = createBottomTabNavigator(); export default function TabNavigator() { const [palette] = usePalette(); - const dpi = PixelRatio.get(); - const buttonNativeFeedback = ({ children, style, ...props }: BottomTabBarButtonProps) => ( { const index = template.findIndex(e => e.id === element.id); if (index >= 0) template[index] = element; } const onSubmit = () => { - let data: ScoutingData = { - id: "s_" + route.params.matchID + "_" + route.params.teamID, - teamID: route.params.teamID, - matchID: route.params.matchID, - values: template.map(elem => elem.value).filter(val => val != undefined) as number[] - }; - setScoutingDataHook([...scoutingData, data]); - - if (navigator.canGoBack()) - navigator.goBack(); - if (navigator.canGoBack()) - navigator.goBack(); - Vibration.vibrate(200); - Alert.alert("Success", "Data has been saved to storage", [ + Alert.alert("Are you sure?", "This will save your scouting data to local storage. You won't be able to return to this match in the future", [ { - text: "Undo", - onPress: () => { - getScoutingData().then((data) => { - if (data) { - const oldData = data.splice(scoutingData.length - 1, 1); - setScoutingData(scoutingData); - Vibration.vibrate(200); - Alert.alert("Success", "Last round has been cleared"); - } - }); + text: "Confirm", onPress: () => { + const data: ScoutingData = { + id: "s_" + route.params.matchID + "_" + route.params.teamID, + teamID: route.params.teamID, + matchID: route.params.matchID, + values: template.map(elem => elem.value).filter(val => val != undefined) as number[] + }; + setScoutingDataHook([...scoutingData, data]); + + if (navigator.canGoBack()) + navigator.goBack(); + if (navigator.canGoBack()) + navigator.goBack(); + Vibration.vibrate(200); + Alert.alert("Success", "Data has been saved to storage"); } }, - { - text: "OK", - style: "cancel" - } + { text: "Cancel", style: "cancel" }, ], { cancelable: true }); } @@ -68,11 +61,11 @@ export default function ScoutingScreen({ route }: any) { {team.mediaPaths.length > 0 ? : null} @@ -81,6 +74,8 @@ export default function ScoutingScreen({ route }: any) { + + {template.map((element, index) => >(); - const [palette] = usePalette(); const [qrHistory, setQRHistory] = useQRHistory(); - const version = React.useState(0); + const [version, setVersion] = React.useState(0); // Sort By Date React.useEffect(() => { - qrHistory.sort((a, b) => Date.parse(a.timestamp) - Date.parse(b.timestamp)) + qrHistory.sort((a, b) => Date.parse(b.timestamp) - Date.parse(a.timestamp)) }, [qrHistory]) React.useEffect(() => { - const t = setInterval(() => { - - }) + const interval = setInterval(() => { + setVersion(v => v + 1); + }, 1000); + + return () => { + clearInterval(interval); + } }, []) return ( QR History - Recover Previous Scouting Data + Share Previous QR Codes {qrHistory.length > 0 ? qrHistory.map((scan, index) => { const date = new Date(scan.timestamp); @@ -44,7 +46,7 @@ export default function ExportQRHistory({ route }: any) { return ( { navigator.push("ExportQR", { scoutIDs: scan.scoutIDs }) }} /> diff --git a/screens/Sharing/ExportQRScreen.tsx b/screens/Sharing/ExportQRScreen.tsx index a92fe55..4e16126 100644 --- a/screens/Sharing/ExportQRScreen.tsx +++ b/screens/Sharing/ExportQRScreen.tsx @@ -64,8 +64,6 @@ export default function ExportQRScreen({ route }: any) { setQRHistory(qrHistory); } - - Vibration.vibrate(100); } const onHistory = () => { @@ -91,7 +89,9 @@ export default function ExportQRScreen({ route }: any) { bgColor="black" fgColor="white" /> - {scoutingChunk.length} / {scoutIDs !== undefined ? scoutingChunk.length : scoutingData.filter((scout) => !(scout.isQRCodeScanned)).length} Matches + + {scoutingChunk.length} out of {scoutIDs !== undefined ? scoutingChunk.length : scoutingData.filter((scout) => !(scout.isQRCodeScanned)).length} Matches + diff --git a/screens/Teams/TeamsScreen.tsx b/screens/Teams/TeamsScreen.tsx index 542ed20..a09aa15 100644 --- a/screens/Teams/TeamsScreen.tsx +++ b/screens/Teams/TeamsScreen.tsx @@ -93,7 +93,8 @@ export default function TeamsScreen() { selectedValue={sortType} onValueChange={(type) => { onSort(type) }} dropdownIconColor={palette.background} - style={{ alignSelf: "flex-end" }}> + + style={{ alignSelf: "flex-end", minWidth: 200, color: palette.background }}>