Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import io.invertase.firebase.fabric.crashlytics.RNFirebaseCrashlyticsPackage;
import io.invertase.firebase.analytics.RNFirebaseAnalyticsPackage;
import io.invertase.firebase.firestore.RNFirebaseFirestorePackage;
import io.invertase.firebase.storage.RNFirebaseStoragePackage;

import java.util.List;

Expand All @@ -32,6 +33,7 @@ protected List<ReactPackage> getPackages() {
packages.add(new RNFirebaseCrashlyticsPackage());
packages.add(new RNFirebaseAnalyticsPackage());
packages.add(new RNFirebaseFirestorePackage());
packages.add(new RNFirebaseStoragePackage());
return packages;
}

Expand Down
1 change: 0 additions & 1 deletion ios/FriendlyPlans.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
};
objectVersion = 46;
objects = {

/* Begin PBXBuildFile section */
13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.m */; };
13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB11A68108700A75B9A /* LaunchScreen.xib */; };
Expand Down
6 changes: 6 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,14 @@
},
"dependencies": {
"@react-native-community/slider": "^2.0.8",
"@react-native-firebase/app": "^6.4.0",
Copy link
Contributor

@bklukaczewski bklukaczewski Jun 15, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We use v5 of firebase library already. We shouldn't mix it with v6

the best option would be to migrate to v6 ;) but that's a separate task

"@react-native-firebase/storage": "^6.4.0",
"@types/lodash.every": "^4.6.6",
"@types/lodash.isempty": "^4.4.6",
"@types/lodash.noop": "^3.0.6",
"@types/lodash.sortby": "^4.7.6",
"@types/react-native-uuid": "^1.4.0",
"buffer": "^5.5.0",
"formik": "^1.5.7",
"i18next": "^18.0.0",
"lodash.every": "^4.6.0",
Expand All @@ -40,10 +44,12 @@
"react-native-floating-action": "^1.19.1",
"react-native-gesture-handler": "^1.5.6",
"react-native-image-crop-picker": "^0.26.2",
"react-native-image-picker": "^2.3.1",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest to use react-native-image-crop-picker as it's already on the dependency list

"react-native-localize": "^1.3.1",
"react-native-reanimated": "^1.7.0",
"react-native-splash-screen": "^3.2.0",
"react-native-svg": "^9.13.3",
"react-native-uuid": "^1.4.9",
"react-native-vector-icons": "^6.6.0",
"react-navigation": "^3.11.0",
"yup": "^0.27.0"
Expand Down
38 changes: 38 additions & 0 deletions src/components/runPlanSlide/SlideImage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import React from 'react';
import { Image, StyleSheet, View } from 'react-native';
import { palette } from '../../styles';
import { StyledText } from '../StyledText';

interface Props {
imageUri: string;
}

export class SlideImage extends React.PureComponent<Props> {
render() {
if (this.props.imageUri) {
return (
<View style={styles.imageContainer}>
<Image resizeMode="contain" style={styles.image} source={{ uri: this.props.imageUri }} />
</View>
);
}
return <StyledText style={[styles.loadingText]}>Loading...</StyledText>;
}
}

const styles = StyleSheet.create({
imageContainer: {
flex: 1,
alignSelf: 'stretch',
marginBottom: 16,
},
loadingText: {
fontSize: 24,
},
image: {
flex: 1,
},
nameTextColor: {
color: palette.textBlack,
},
});
19 changes: 19 additions & 0 deletions src/infrastructure/Images.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import uuid from 'react-native-uuid';
import { getImagesStorage } from '../models/FirebaseRefProxy';

export const uploadImage = async (imageUri: string, fileName: string | undefined): Promise<string> => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if fileName can be undefined, we can make it optional parameter fileName?

const DEFAULT_EXTENSION = '.jpg';
const fileExtension = fileName ? fileName.split('.').pop() : DEFAULT_EXTENSION;
const uploadedFileName = `${uuid.v1()}.${fileExtension}`;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Firebase would generate filename for us :)


await getImagesStorage()
.child(uploadedFileName)
.putFile(imageUri);
return uploadedFileName;
};

export const loadImage = async (imageName: string): Promise<string> => {
return getImagesStorage()
.child(imageName)
.getDownloadURL();
};
7 changes: 7 additions & 0 deletions src/models/FirebaseRefProxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@ export const getStudentRef = (studentId: string): RNFirebase.firestore.DocumentR
export const getStudentsRef = (userId: string = getAuthenticatedUserId()): RNFirebase.firestore.CollectionReference =>
getUserRef(userId).collection('students');

export const getImagesStorage = (userId: string = getAuthenticatedUserId()): RNFirebase.storage.Reference =>
firebase
.storage()
.ref()
.child('images')
.child(userId);

export const getUserRef = (userId: string): RNFirebase.firestore.DocumentReference => getUsersRef().doc(userId);

const getUsersRef = (): RNFirebase.firestore.CollectionReference => {
Expand Down
1 change: 1 addition & 0 deletions src/models/PlanElement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export interface PlanElement {
completed: boolean;
time: number;
lector: boolean;
image: string;

complete: () => void;
update: (changes: any) => void;
Expand Down
3 changes: 3 additions & 0 deletions src/models/PlanItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export class PlanItem implements SubscribableModel, PlanElement {
plan: Plan,
type: PlanItemType,
name: string = i18n.t('updatePlan:planItemNamePlaceholder'),
image: string = '',
lastItemOrder: number,
): Promise<PlanItem> {
const { id } = await getPlanItemsRef(plan.studentId, plan.id).add({
Expand All @@ -51,6 +52,7 @@ export class PlanItem implements SubscribableModel, PlanElement {
planId: plan.id,
type,
completed: false,
image,
lector: false,
nameForChild: i18n.t('planItemActivity:taskNameForChild'),
order: lastItemOrder + 1,
Expand All @@ -63,6 +65,7 @@ export class PlanItem implements SubscribableModel, PlanElement {
planId: plan.id,
type,
completed: false,
image,
lector: false,
nameForChild: i18n.t('planItemActivity:taskNameForChild'),
order: lastItemOrder + 1,
Expand Down
1 change: 1 addition & 0 deletions src/models/PlanSubItem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export class PlanSubItem implements SubscribableModel, PlanElement {
time!: number;
type: PlanItemType = PlanItemType.SubElement;
lector!: boolean;
image!: string;

complete = () => {
this.update({ completed: true });
Expand Down
63 changes: 54 additions & 9 deletions src/screens/planItemActivity/ImagePicker.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,64 @@
import React, { SFC } from 'react';
import React, { PureComponent } from 'react';
import { StyleSheet, View } from 'react-native';
import { Image } from 'react-native-elements';
import { ImagePickerResponse } from 'react-native-image-picker';

import { Icon, ModalTrigger } from 'components';
import { i18n } from 'locale';
import { PlanItem } from 'models';
import { palette } from 'styles';
import { loadImage, uploadImage } from '../../infrastructure/Images';
import { ImagePickerModal } from './ImagePickerModal';

interface Props {
planItem: PlanItem;
setFieldValue: (field: string, value: any) => void;
submitForm: () => void;
}

export const ImagePicker: SFC<Props> = ({ planItem }) => (
<View style={styles.container}>
<ModalTrigger modalContent={<ImagePickerModal planItem={planItem} />} title={i18n.t('planItemActivity:addImage')}>
<View style={styles.imagePicker}>
<Icon name="add-a-photo" type="material" size={82} color={palette.textInputPlaceholder} />
interface State {
imageUri: string;
}

export class ImagePicker extends PureComponent<Props, State> {
state = {
imageUri: '',
};

componentDidMount = async () => {
if (this.props.planItem && this.props.planItem.image) {
const imageUri = await loadImage(this.props.planItem.image);
this.setState({ imageUri });
}
};

updateImage = async (image: ImagePickerResponse) => {
const imageName = await uploadImage(image.uri, image.fileName);
this.props.setFieldValue('image', imageName);
this.props.submitForm();
this.setState({ imageUri: image.uri });
};

render() {
const { planItem } = this.props;
return (
<View style={styles.container}>
<ModalTrigger
modalContent={<ImagePickerModal planItem={planItem} updateImage={this.updateImage} />}
title={i18n.t('planItemActivity:addImage')}
>
<View style={styles.imagePicker}>
{planItem && this.state.imageUri ? (
<Image source={{ uri: this.state.imageUri }} style={styles.image} />
) : (
<Icon name="add-a-photo" type="material" size={82} color={palette.textInputPlaceholder} />
)}
</View>
</ModalTrigger>
</View>
</ModalTrigger>
</View>
);
);
}
}

const styles = StyleSheet.create({
container: {
Expand All @@ -33,5 +72,11 @@ const styles = StyleSheet.create({
borderColor: palette.backgroundSurface,
paddingHorizontal: 85,
paddingVertical: 67,
flex: 1,
justifyContent: 'center',
},
image: {
width: 200,
height: 200,
},
});
36 changes: 33 additions & 3 deletions src/screens/planItemActivity/ImagePickerModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,33 +5,63 @@ import { PlanItem } from 'models';
import { Route } from 'navigation';
import React, { SFC } from 'react';
import { StyleSheet, View } from 'react-native';
import ImagePicker, { ImagePickerResponse } from 'react-native-image-picker';
import { NavigationService } from 'services';
import { dimensions } from 'styles';
import { ImageAction } from './ImageAction';

interface Props {
closeModal?: () => void;
updateImage: (image: ImagePickerResponse) => void;
planItem: PlanItem;
}

export const ImagePickerModal: SFC<Props> = ({ closeModal = noop, planItem }) => {
export const ImagePickerModal: SFC<Props> = ({ closeModal = noop, planItem, updateImage }) => {
const navigateToImageLibrary = () => {
closeModal();
NavigationService.navigate(Route.ImageLibrary, {
planItem,
});
};

const openCamera = () => {
closeModal();
ImagePicker.launchCamera(
{
mediaType: 'photo',
},
response => {
if (!response.didCancel && !response.error) {
updateImage(response);
}
},
);
};

const openGallery = () => {
closeModal();
ImagePicker.launchImageLibrary(
{
mediaType: 'photo',
},
response => {
if (!response.didCancel && !response.error) {
updateImage(response);
}
},
);
};

return (
<View style={styles.imageActionContainer}>
<ImageAction title={i18n.t('planItemActivity:imageActionTakePhoto')}>
<IconButton name="photo-camera" type="material" size={24} />
<IconButton name="photo-camera" type="material" size={24} onPress={openCamera} />
</ImageAction>
<ImageAction title={i18n.t('planItemActivity:imageActionLibrary')}>
<IconButton name="photo-library" type="material" size={24} onPress={navigateToImageLibrary} />
</ImageAction>
<ImageAction title={i18n.t('planItemActivity:imageActionBrowse')}>
<IconButton name="file-download" type="material" size={24} />
<IconButton name="file-download" type="material" size={24} onPress={openGallery} />
</ImageAction>
</View>
);
Expand Down
2 changes: 2 additions & 0 deletions src/screens/planItemActivity/PlanItemForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { SimpleTask } from './SimpleTask';
export interface PlanItemFormData {
name: string;
nameForChild: string;
image: string;
}

interface Props {
Expand All @@ -35,6 +36,7 @@ export class PlanItemForm extends React.PureComponent<Props, State> {
? this.props.planItem.name
: `${i18n.t('planItemActivity:newTask')}${this.props.taskNumber}`,
nameForChild: this.props.planItem ? this.props.planItem.nameForChild : '',
image: '',
};

validationSchema = Yup.object().shape({
Expand Down
12 changes: 7 additions & 5 deletions src/screens/planItemActivity/PlanItemTaskScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,26 +27,28 @@ export class PlanItemTaskScreen extends React.PureComponent<NavigationInjectedPr
return order;
};

createPlanItem = async (name: string) => {
createPlanItem = async (formData: PlanItemFormData) => {
const { name, image } = formData;
const plan = this.props.navigation.getParam('plan');

const planItem = await PlanItem.createPlanItem(plan, PlanItemType.SimpleTask, name, this.getLastItemOrder());
const planItem = await PlanItem.createPlanItem(plan, PlanItemType.SimpleTask, name, image, this.getLastItemOrder());

this.setState({ planItem });
};

updatePlanItem = async (formData: PlanItemFormData) => {
const { name, nameForChild } = formData;
const { name, nameForChild, image } = formData;
await this.state.planItem.update({
name,
nameForChild,
image,
});

this.setState({ planItem: { ...this.state.planItem, name, nameForChild } });
this.setState({ planItem: { ...this.state.planItem, name, nameForChild, image } });
};

onSubmit = (formData: PlanItemFormData) =>
this.state.planItem ? this.updatePlanItem(formData) : this.createPlanItem(formData.name);
this.state.planItem ? this.updatePlanItem(formData) : this.createPlanItem(formData);

render() {
const { planItem } = this.state;
Expand Down
4 changes: 2 additions & 2 deletions src/screens/planItemActivity/SimpleTask.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,12 @@ export class SimpleTask extends React.PureComponent<Props> {
};

render() {
const { values, handleChange, submitForm } = this.props.formikProps;
const { values, handleChange, submitForm, setFieldValue } = this.props.formikProps;

return (
<SafeAreaView style={this.props.style}>
<Card style={[styles.container]}>
<ImagePicker planItem={this.props.planItem} />
<ImagePicker planItem={this.props.planItem} setFieldValue={setFieldValue} submitForm={submitForm} />
<TextInput
style={styles.imageInputTextContainer}
textStyle={styles.imageInputText}
Expand Down
Loading