Skip to content

Commit

Permalink
37 store a set of calculated marks (#78)
Browse files Browse the repository at this point in the history
* move file

* update sentry

* new styling button

* more styling

* #37 store mark set in local storage

* reset set name input

* new markset interface for storing locally

* better documentation of return vaule

* adding sentry env

* adding jest setup

* update button test

* adding mocks, setup and test for setModal

* rearrange imports

* adding metro config for jest-hermes issue

* fixes hermes for now

* conditionally render buttons

* adding confirm modal for removing marks

* new version
  • Loading branch information
haakonhelmenrusas authored Nov 26, 2023
1 parent 697a4b6 commit a86cc9c
Show file tree
Hide file tree
Showing 19 changed files with 292 additions and 254 deletions.
4 changes: 2 additions & 2 deletions app.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@
},
"android": {
"package": "com.aaronshade.bueboka",
"versionCode": 11,
"versionCode": 12,
"adaptiveIcon": {
"foregroundImage": "./assets/images/icon.png",
"backgroundColor": "#FFFFFF"
}
},
"ios": {
"buildNumber": "1.8.0",
"buildNumber": "1.9.0",
"bundleIdentifier": "com.aaronshade.bueboka",
"infoPlist": {
"CFBundleIconName": "./assets/images/ipad.png"
Expand Down
58 changes: 58 additions & 0 deletions app/(tabs)/siktemerker/components/ConfirmRemoveMarks.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { Modal, StyleSheet, Text, View } from 'react-native';
import { Button } from '../../../../components/common';
import { CalculatedMarks } from '../../../../types';
import { storeLocalStorage } from '../../../../utils';

interface Props {
modalVisible: boolean;
setBallistics: (ballistics: CalculatedMarks | null) => void;
closeModal: () => void;
}

const ConfirmRemoveMarks = ({ modalVisible, setBallistics, closeModal }: Props) => {
function handleRemoveMarks() {
storeLocalStorage(null, 'ballistics').then(() => {
setBallistics(null);
closeModal();
});
}

return (
<Modal transparent visible={modalVisible} animationType="fade">
<View style={styles.modal}>
<Text style={{ fontSize: 18 }}>Bekreft fjerning av siktemerker</Text>
<View style={styles.buttons}>
<Button type="outline" label="Avbryt" onPress={closeModal} />
<Button width={100} label="Ja" onPress={handleRemoveMarks} />
</View>
</View>
</Modal>
);
};

const styles = StyleSheet.create({
modal: {
marginTop: 'auto',
marginBottom: '40%',
marginLeft: 'auto',
marginRight: 'auto',
height: 160,
width: '80%',
justifyContent: 'space-around',
alignItems: 'center',
shadowColor: 'black',
shadowOpacity: 0.25,
shadowRadius: 8,
shadowOffset: { width: 0, height: 2 },
elevation: 5,
borderRadius: 8,
backgroundColor: 'white',
},
buttons: {
width: '100%',
flexDirection: 'row',
justifyContent: 'space-around',
},
});

export default ConfirmRemoveMarks;
2 changes: 1 addition & 1 deletion app/(tabs)/siktemerker/components/MarksForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Keyboard, StyleSheet, View } from 'react-native';
import { Button, Input } from '../../../../components/common';
import { MarkValue } from '../../../../types';
import { formatNumber } from '../../../../utils';
import { useCalcForm } from '../useCalcForm';
import { useCalcForm } from './useCalcForm';

interface MarksFormProps {
status: string;
Expand Down
5 changes: 3 additions & 2 deletions app/(tabs)/siktemerker/components/MarksTable.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { faTrash } from '@fortawesome/free-solid-svg-icons/faTrash';
import { FontAwesomeIcon } from '@fortawesome/react-native-fontawesome';
import { StyleSheet, Text, View } from 'react-native';

import Button from '../../../../components/common/Button';
import { CalculatedMarks } from '../../../../types';

Expand Down Expand Up @@ -33,7 +34,7 @@ export default function MarksTable({ ballistics, removeMark }: CalculationTableP
return (
<View>
<View style={{ display: 'flex', flexDirection: 'row', justifyContent: 'space-between' }}>
<Text style={[styles.thead, styles.heading]}>Avstand</Text>
<Text style={[styles.thead, styles.heading, { marginLeft: -8 }]}>Avstand</Text>
<Text style={[styles.thead, styles.heading]}>Merke</Text>
<Text style={[styles.thead, styles.heading]}>Beregnet</Text>
<Text style={styles.theadEnd}></Text>
Expand Down Expand Up @@ -75,7 +76,7 @@ const styles = StyleSheet.create({
marginBottom: 8,
},
info: {
marginTop: 8,
margin: 8,
fontSize: 16,
},
});
75 changes: 75 additions & 0 deletions app/(tabs)/siktemerker/components/SetModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { useState } from 'react';
import { Modal, View } from 'react-native';
import { Button, Input } from '../../../../components/common';
import { CalculatedMarks, MarkSet } from '../../../../types';
import { getLocalStorage, storeLocalStorage } from '../../../../utils';

interface Props {
modalVisible: boolean;
closeModal: () => void;
setBallistics: (ballistics: CalculatedMarks | null) => void;
ballistics: CalculatedMarks | null;
}

const SetModal = ({ modalVisible, closeModal, setBallistics, ballistics }: Props) => {
const [name, setName] = useState('');
const [nameError, setNameError] = useState('');

async function storeMarksWithName(name: string) {
if (ballistics) {
const setList = await getLocalStorage<Array<MarkSet>>('marksSets');

const marksSet = {
name: name,
marks: ballistics.given_marks,
distances: ballistics.given_distances,
};

if (setList) {
const nameExists = setList.some((set) => set.name === name);
if (nameExists) {
setNameError('Name is already taken');
} else {
setList.push(marksSet);
storeLocalStorage(setList, 'marksSets');
setBallistics(null);
setNameError('');
setName('');
closeModal();
}
} else {
const marksSets = Array<MarkSet>();
marksSets.push(marksSet);
storeLocalStorage(marksSets, 'marksSets');
setBallistics(null);
setName('');
setNameError('');
closeModal();
}
}
}

const handleSave = async () => {
await storeMarksWithName(name);
};

return (
<Modal visible={modalVisible} animationType="fade">
<View style={{ flex: 1, margin: 32, justifyContent: 'center', alignItems: 'center' }}>
<Input
label="Sett navn på settet"
error={nameError.length > 0}
errorMessage={nameError}
onChangeText={setName}
value={name}
/>
<View style={{ flexDirection: 'row', marginTop: 24, justifyContent: 'space-between' }}>
<Button type="outline" label="Avbryt" onPress={closeModal} />
<Button label="Lagre sett" onPress={handleSave} />
</View>
</View>
</Modal>
);
};

export default SetModal;
5 changes: 5 additions & 0 deletions app/(tabs)/siktemerker/components/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export { default as ConfirmRemoveMarks } from './ConfirmRemoveMarks';
export { default as MarksForm } from './MarksForm';
export { default as MarksTable } from './MarksTable';
export { default as SetModal } from './SetModal';
export { useCalcForm } from './useCalcForm';
File renamed without changes.
41 changes: 32 additions & 9 deletions app/(tabs)/siktemerker/index.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import * as Sentry from '@sentry/react-native';
import { captureException } from '@sentry/react-native';
import { useEffect, useState } from 'react';
import { Keyboard, StyleSheet, Text, TouchableWithoutFeedback, View } from 'react-native';

import { Button } from '../../../components/common';
import { AimDistanceMark, CalculatedMarks, MarkValue } from '../../../types';
import { Ballistics, getLocalStorage, storeLocalStorage, useBallisticsParams } from '../../../utils/';
import MarksForm from './components/MarksForm';
import MarksTable from './components/MarksTable';
import { ConfirmRemoveMarks, MarksForm, MarksTable, SetModal } from './components';

export default function Calculate() {
const [modalVisible, setModalVisible] = useState(false);
const [conformationModalVisible, setConformationModalVisible] = useState(false);
const [ballistics, setBallistics] = useState<CalculatedMarks | null>(null);
const { error, status, calculateBallisticsParams } = useBallisticsParams();

Expand All @@ -19,6 +21,14 @@ export default function Calculate() {
});
}, []);

const openModal = () => {
setModalVisible(true);
};

const closeModal = () => {
setModalVisible(false);
};

async function sendMarks(newMark: MarkValue) {
const body: AimDistanceMark = {
...Ballistics,
Expand All @@ -40,7 +50,7 @@ export default function Calculate() {
});
}
} catch (error) {
Sentry.captureException(error);
captureException(error);
}
}

Expand All @@ -55,12 +65,25 @@ export default function Calculate() {
<View style={{ flex: 1 }}>
<Text style={styles.title}>Siktemerker</Text>
<MarksForm sendMarks={sendMarks} status={status} />
{error && (
<>
<View style={{ marginBottom: 8, padding: 8 }}>Oisann, noe gikk galt. Prøv igjen senere.</View>
</>
)}
{error && <View style={{ marginBottom: 8, padding: 8 }}>Oisann, noe gikk galt. Prøv igjen!</View>}
<MarksTable ballistics={ballistics} removeMark={handleRemoveMark} />
{ballistics && ballistics.given_marks.length > 0 && (
<View style={{ flexDirection: 'row', justifyContent: 'space-between', marginTop: 'auto' }}>
<Button label="Fjern merker" type="outline" onPress={() => setConformationModalVisible(true)} />
<Button label="Lagre sett" type="filled" onPress={() => openModal()} />
</View>
)}
<SetModal
modalVisible={modalVisible}
closeModal={closeModal}
setBallistics={setBallistics}
ballistics={ballistics}
/>
<ConfirmRemoveMarks
modalVisible={conformationModalVisible}
setBallistics={setBallistics}
closeModal={() => setConformationModalVisible(false)}
/>
</View>
</TouchableWithoutFeedback>
);
Expand Down
1 change: 1 addition & 0 deletions app/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Sentry.init({
dsn: process.env.EXPO_PUBLIC_SENTRY_DSN,
enableInExpoDevelopment: true,
debug: false,
environment: process.env.EXPO_PUBLIC_SENTRY_ENVIRONMENT,
});

const Index = () => {
Expand Down
2 changes: 1 addition & 1 deletion components/common/Button.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,6 @@ describe('Button', () => {

test('renders a button with the correct text color when type is outline', () => {
const { getByText } = render(<Button label="Click me!" type="outline" />);
expect(getByText('Click me!').props.style[1].color).toBe('blue');
expect(getByText('Click me!').props.style[1].color).toBe('#0066b2');
});
});
8 changes: 5 additions & 3 deletions components/common/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ const Button: React.FC<ButtonProps> = ({
loading = false,
...props
}) => {
const buttonColor = type === 'outline' ? 'transparent' : 'blue';
const textColor = type === 'outline' ? 'blue' : 'white';
const buttonColor = type === 'outline' ? 'transparent' : '#0066b2';
const textColor = type === 'outline' ? '#0066b2' : 'white';

return (
<Pressable
Expand All @@ -35,9 +35,11 @@ const Button: React.FC<ButtonProps> = ({
justifyContent: 'center',
alignItems: 'center',
width: width,
borderColor: buttonColor,
borderWidth: type === 'outline' ? 1 : 0,
paddingVertical: 12,
paddingHorizontal: 24,
borderRadius: 4,
borderRadius: 32,
height: 48,
},
buttonStyle,
Expand Down
2 changes: 1 addition & 1 deletion components/common/Input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const Input = ({ label, error, errorMessage, placeholderText, ...props }: InputP
const textInputColor = editableTextInputColor;

const styles = StyleSheet.create({
container: { marginBottom: 16, height: 54, marginRight: 8 },
container: { marginBottom: 16, height: 54, marginRight: 8, width: '100%' },
label: { color: textInputColor, fontWeight: '500', fontSize: 16, marginBottom: 4 },
input: {
backgroundColor: '#FFF',
Expand Down
4 changes: 4 additions & 0 deletions jestSetup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
jest.mock('@react-native-async-storage/async-storage', () =>
require('@react-native-async-storage/async-storage/jest/async-storage-mock'),
);
jest.mock('@sentry/react-native', () => ({ init: () => jest.fn() }));
8 changes: 8 additions & 0 deletions metro.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// eslint-disable-next-line prettier/prettier
const { getDefaultConfig } = require('expo/metro-config');

const config = getDefaultConfig(__dirname);

config.resolver.blockList = [/(.*.test.ts?)$/];

module.exports = config;
Loading

0 comments on commit a86cc9c

Please sign in to comment.