From 71c69c95f089c0c70994a6e9437304c1e5f9ff4c Mon Sep 17 00:00:00 2001 From: Denis Date: Mon, 18 Jun 2018 00:04:21 +0300 Subject: [PATCH] add person avatar --- MobileApp/package.json | 1 + MobileApp/src/components/app-navigator.js | 52 ++++++++------ .../src/components/people/person-avatar.js | 72 +++++++++++++++++++ .../src/components/people/person-card.js | 68 +++++++++--------- .../src/components/screens/people-list.js | 59 ++++++++------- .../src/components/screens/person-avatar.js | 10 +++ MobileApp/src/stores/entities-store.js | 72 +++++++++++++------ MobileApp/src/stores/people.js | 41 ++++++++--- MobileApp/yarn.lock | 4 ++ 9 files changed, 264 insertions(+), 115 deletions(-) create mode 100644 MobileApp/src/components/people/person-avatar.js create mode 100644 MobileApp/src/components/screens/person-avatar.js diff --git a/MobileApp/package.json b/MobileApp/package.json index cf64248..23b7d2d 100644 --- a/MobileApp/package.json +++ b/MobileApp/package.json @@ -19,6 +19,7 @@ "preset": "jest-expo" }, "dependencies": { + "base64-arraybuffer": "^0.1.5", "expo": "^27.0.1", "firebase": "^5.0.4", "lodash": "^4.17.10", diff --git a/MobileApp/src/components/app-navigator.js b/MobileApp/src/components/app-navigator.js index 976330b..2a78557 100644 --- a/MobileApp/src/components/app-navigator.js +++ b/MobileApp/src/components/app-navigator.js @@ -1,34 +1,40 @@ -import {createStackNavigator, createBottomTabNavigator} from 'react-navigation' +import { + createStackNavigator, + createBottomTabNavigator +} from 'react-navigation' import AuthScreen from './screens/auth' import EventList from './screens/event-list' import PeopleList from './screens/people-list' import EventScreen from './screens/event' import EventMapScreen from './screens/event-map' +import PersonAvatar from './screens/person-avatar' const ListsNavigator = createBottomTabNavigator({ - events: { - screen: EventList - }, - people: { - screen: PeopleList - } + events: { + screen: EventList + }, + people: { + screen: PeopleList + } }) - export default createStackNavigator({ - auth: { - screen: AuthScreen, - navigationOptions: { - title: 'Auth' - } - }, - lists: { - screen: ListsNavigator, - navigationOptions: { - title: 'Lists' - } - }, - event: { - screen: EventMapScreen + auth: { + screen: AuthScreen, + navigationOptions: { + title: 'Auth' } -}) \ No newline at end of file + }, + lists: { + screen: ListsNavigator, + navigationOptions: { + title: 'Lists' + } + }, + event: { + screen: EventMapScreen + }, + personAvatar: { + screen: PersonAvatar + } +}) diff --git a/MobileApp/src/components/people/person-avatar.js b/MobileApp/src/components/people/person-avatar.js new file mode 100644 index 0000000..9b45f24 --- /dev/null +++ b/MobileApp/src/components/people/person-avatar.js @@ -0,0 +1,72 @@ +import React, { Component } from 'react' +import { View, Text, StyleSheet, TouchableOpacity } from 'react-native' +import { Camera, Permissions } from 'expo' +import { observable, action } from 'mobx' +import { observer, inject } from 'mobx-react' + +@inject('people') +@inject('navigation') +@observer +class PersonAvatar extends Component { + @observable permissionAsked = false + @observable permissionGranted = false + + async componentDidMount() { + this.setPermissionAsked(true) + const { status } = await Permissions.askAsync(Permissions.CAMERA) + this.setPermissionGranted(status === 'granted') + } + + @action setPermissionAsked = (asked) => (this.permissionAsked = asked) + @action setPermissionGranted = (granted) => (this.permissionGranted = granted) + + render() { + if (!this.permissionAsked) return Not Asked + if (!this.permissionGranted) return Not Granted + + return ( + + + + + + + + ) + } + + setRef = (ref) => { + this.camera = ref + } + + takeAvatar = async () => { + const { uid, people, navigation } = this.props + + if (this.camera) { + const avatar = await this.camera.takePictureAsync({ base64: true }) + people.saveAvatar(uid, avatar) + } + + navigation.navigate('people') + } +} + +const styles = StyleSheet.create({ + container: { + flex: 1 + }, + camera: { + flex: 1, + justifyContent: 'flex-end' + }, + button: { + marginBottom: 20, + width: 60, + height: 60, + backgroundColor: 'white', + borderRadius: 30, + alignSelf: 'center' + } +}) + +export default PersonAvatar diff --git a/MobileApp/src/components/people/person-card.js b/MobileApp/src/components/people/person-card.js index 1c6bac7..0db5e41 100644 --- a/MobileApp/src/components/people/person-card.js +++ b/MobileApp/src/components/people/person-card.js @@ -1,43 +1,45 @@ import React, { Component } from 'react' -import {View, Text, Image, StyleSheet} from 'react-native' +import { View, Text, Image, StyleSheet } from 'react-native' import Card from '../common/card' class PersonCard extends Component { - static propTypes = { + static propTypes = {} - }; - - render() { - const { email, firstName, lastName } = this.props.person - return ( - - - - {email} - {firstName} {lastName} - - - ) - } + render() { + const { email, firstName, lastName, avatar } = this.props.person + return ( + + {avatar ? ( + + ) : null} + + {email} + + {firstName} {lastName} + + + + ) + } } const styles = StyleSheet.create({ - container: { - flexDirection: 'row' - }, - avatar: { - width: 200, - height: 100, - margin: 5 - }, - content: { - flexDirection: 'column', - justifyContent: 'space-around', - alignItems: 'center' - }, - email: { - fontWeight: 'bold' - } + container: { + flexDirection: 'row' + }, + avatar: { + width: 200, + height: 100, + margin: 5 + }, + content: { + flexDirection: 'column', + justifyContent: 'space-around', + alignItems: 'center' + }, + email: { + fontWeight: 'bold' + } }) -export default PersonCard \ No newline at end of file +export default PersonCard diff --git a/MobileApp/src/components/screens/people-list.js b/MobileApp/src/components/screens/people-list.js index cea8fa0..03b6700 100644 --- a/MobileApp/src/components/screens/people-list.js +++ b/MobileApp/src/components/screens/people-list.js @@ -1,36 +1,41 @@ import React, { Component } from 'react' -import {observer, inject} from 'mobx-react' -import {View, StyleSheet, ActivityIndicator} from 'react-native' +import { observer, inject } from 'mobx-react' +import { View, StyleSheet, ActivityIndicator } from 'react-native' import PeopleList from '../people/people-list' @inject('people') @observer class PeopleListScreen extends Component { - static propTypes = { - - }; - - static navigationOptions = { - title: 'People List' - } - - componentDidMount() { - const {people} = this.props - if (!people.loaded && !people.loading) people.loadAll() - } - - render() { - const {people} = this.props - if (people.loading) return this.getLoader() - return - } - - getLoader() { - return - } + static propTypes = {} + + static navigationOptions = { + title: 'People List' + } + + componentDidMount() { + const { people } = this.props + if (!people.loaded && !people.loading) people.loadAll() + } + + render() { + const { people } = this.props + if (people.loading) return this.getLoader() + return + } + + getLoader() { + return ( + + + + ) + } + + handlePersonPress = (uid) => { + this.props.navigation.navigate('personAvatar', { uid }) + } } -const styles = StyleSheet.create({ -}) +const styles = StyleSheet.create({}) -export default PeopleListScreen \ No newline at end of file +export default PeopleListScreen diff --git a/MobileApp/src/components/screens/person-avatar.js b/MobileApp/src/components/screens/person-avatar.js new file mode 100644 index 0000000..8a0b237 --- /dev/null +++ b/MobileApp/src/components/screens/person-avatar.js @@ -0,0 +1,10 @@ +import React, { Component } from 'react' +import PersonAvatar from '../people/person-avatar' + +class PersonAvatarScreen extends Component { + render() { + return + } +} + +export default PersonAvatarScreen diff --git a/MobileApp/src/stores/entities-store.js b/MobileApp/src/stores/entities-store.js index c7d100d..4706aad 100644 --- a/MobileApp/src/stores/entities-store.js +++ b/MobileApp/src/stores/entities-store.js @@ -1,34 +1,64 @@ -import {observable, computed, action} from 'mobx' +import { observable, computed, action } from 'mobx' import BasicStore from './basic-store' import firebase from 'firebase/app' -import {entitiesFromFB} from './utils' +import { entitiesFromFB } from './utils' class EntitiesStore extends BasicStore { - @observable loading = false - @observable loaded = false + @observable loading = false + @observable loaded = false - @observable entities = {} + @observable entities = {} - @computed get list() { - return Object.values(this.entities) - } + @computed + get list() { + return Object.values(this.entities) + } - @computed get size() { - return Object.keys(this.entities).length - } + @computed + get size() { + return Object.keys(this.entities).length + } } export function loadAllHelper(refName) { - return action(function () { - this.loading = true - - firebase.database().ref(refName) - .once('value', action(data => { - this.entities = entitiesFromFB(data.val()) - this.loading = false - this.loaded = true - })) + return action(function() { + this.loading = true + + firebase + .database() + .ref(refName) + .once( + 'value', + action((data) => { + this.entities = entitiesFromFB(data.val()) + this.loading = false + this.loaded = true + }) + ) + }) +} + +export function subscribeHelper(refName) { + return action(function() { + this.loading = true + + const callback = action((data) => { + this.entities = entitiesFromFB(data.val()) + this.loading = false + this.loaded = true }) + + firebase + .database() + .ref(refName) + .on('value', callback) + + return () => + firebase + .database() + .ref(refName) + .off('value', callback) + }) } -export default EntitiesStore \ No newline at end of file +export default EntitiesStore diff --git a/MobileApp/src/stores/people.js b/MobileApp/src/stores/people.js index c1877f6..b20a404 100644 --- a/MobileApp/src/stores/people.js +++ b/MobileApp/src/stores/people.js @@ -1,18 +1,37 @@ -import EntitiesStore, {loadAllHelper} from './entities-store' -import {computed, action} from 'mobx' +import EntitiesStore, { subscribeHelper } from './entities-store' +import { computed, action } from 'mobx' import groupBy from 'lodash/groupBy' +import firebase from 'firebase/app' +import { decode } from 'base64-arraybuffer' class PeopleStore extends EntitiesStore { - @computed get sections() { - const grouped = groupBy(this.list, person => person.firstName.charAt(0)) + @computed + get sections() { + const grouped = groupBy(this.list, (person) => person.firstName.charAt(0)) - return Object.entries(grouped).map(([letter, list]) => ({ - title: `${letter}, ${list.length} people`, - data: list.map(person => ({key: person.uid, person})) - })) - } + return Object.entries(grouped).map(([letter, list]) => ({ + title: `${letter}, ${list.length} people`, + data: list.map((person) => ({ key: person.uid, person })) + })) + } - @action loadAll = loadAllHelper('people') + @action loadAll = subscribeHelper('people') + + async saveAvatar(uid, { base64 }) { + const ref = firebase.storage().ref(`people/${uid}/avatar.jpg`) + + await ref.put(decode(base64)) + const avatar = await ref.getDownloadURL() + + this.updatePerson(uid, { avatar }) + } + + @action + updatePerson = (uid, data) => + firebase + .database() + .ref(`people/${uid}`) + .update(data) } -export default PeopleStore \ No newline at end of file +export default PeopleStore diff --git a/MobileApp/yarn.lock b/MobileApp/yarn.lock index 00d582a..02f5e65 100644 --- a/MobileApp/yarn.lock +++ b/MobileApp/yarn.lock @@ -1750,6 +1750,10 @@ balanced-match@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" +base64-arraybuffer@^0.1.5: + version "0.1.5" + resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz#73926771923b5a19747ad666aa5cd4bf9c6e9ce8" + base64-js@0.0.8: version "0.0.8" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-0.0.8.tgz#1101e9544f4a76b1bc3b26d452ca96d7a35e7978"