diff --git a/ReactNativeClient/package.json b/ReactNativeClient/package.json index 30df70057f..d0ac058354 100644 --- a/ReactNativeClient/package.json +++ b/ReactNativeClient/package.json @@ -12,6 +12,7 @@ "react-native-action-button": "^2.6.9", "react-native-checkbox": "^1.1.0", "react-native-popup-menu": "^0.7.4", + "react-native-side-menu": "^0.20.1", "react-native-vector-icons": "^2.0.3", "react-navigation": "^1.0.0-beta.9", "uuid": "^3.0.1" diff --git a/ReactNativeClient/src/components/screens/folder.js b/ReactNativeClient/src/components/screens/folder.js index ac12ab91f6..b25e1d5e58 100644 --- a/ReactNativeClient/src/components/screens/folder.js +++ b/ReactNativeClient/src/components/screens/folder.js @@ -64,7 +64,6 @@ const FolderScreen = connect( (state) => { return { folderId: state.selectedFolderId, - //folder: state.selectedFolderId ? Folder.byId(state.folders, state.selectedFolderId) : Folder.newFolder(), }; } )(FolderScreenComponent) diff --git a/ReactNativeClient/src/menu.js b/ReactNativeClient/src/menu.js new file mode 100644 index 0000000000..82f8c742b0 --- /dev/null +++ b/ReactNativeClient/src/menu.js @@ -0,0 +1,101 @@ +const React = require('react'); +const { + Dimensions, + StyleSheet, + ScrollView, + View, + Image, + Text, +} = require('react-native'); +const { Component } = React; + +const window = Dimensions.get('window'); +const uri = 'https://pickaface.net/gallery/avatar/Opi51c74d0125fd4.png'; + +const styles = StyleSheet.create({ + menu: { + flex: 1, + width: window.width, + height: window.height, + backgroundColor: 'gray', + padding: 20, + }, + avatarContainer: { + marginBottom: 20, + marginTop: 20, + }, + avatar: { + width: 48, + height: 48, + borderRadius: 24, + flex: 1, + }, + name: { + position: 'absolute', + left: 70, + top: 20, + }, + item: { + fontSize: 14, + fontWeight: '300', + paddingTop: 5, + }, +}); + +module.exports = class Menu extends Component { + static propTypes = { + onItemSelected: React.PropTypes.func.isRequired, + }; + + render() { + return ( + + + + Your name + + + this.props.onItemSelected('About')} + style={styles.item}> + About + + + this.props.onItemSelected('Contacts')} + style={styles.item}> + Contacts + + + Contacts + Contacts + Contacts + Contacts + Contacts + Contacts + Contacts + Contacts + Contacts + Contacts + Contacts + Contacts + Contacts + Contacts + Contacts + Contacts + Contacts + Contacts + Contacts + Contacts + Contacts + Contacts + Contacts + Contacts + Contacts + ContactsLL + + ); + } +}; \ No newline at end of file diff --git a/ReactNativeClient/src/root.js b/ReactNativeClient/src/root.js index f1275b1eff..01b33882f6 100644 --- a/ReactNativeClient/src/root.js +++ b/ReactNativeClient/src/root.js @@ -159,6 +159,11 @@ const AppNavigator = StackNavigator({ Login: {screen: LoginScreen}, }); +const SideMenu = require('react-native-side-menu'); + +import Menu from 'src/menu.js'; + + class AppComponent extends React.Component { componentDidMount() { @@ -207,13 +212,17 @@ class AppComponent extends React.Component { } render() { + const menu = ; + return ( - - - + + + + + ); } } diff --git a/ReactNativeClient/src/synchronizer.js b/ReactNativeClient/src/synchronizer.js index 4a3c295dcd..c5ccffdf72 100644 --- a/ReactNativeClient/src/synchronizer.js +++ b/ReactNativeClient/src/synchronizer.js @@ -131,12 +131,20 @@ class Synchronizer { return p.then(() => { processedChangeIds = processedChangeIds.concat(c.ids); }).catch((error) => { - Log.warn('Failed applying changes', c.ids); + Log.warn('Failed applying changes', c.ids, error.message, error.type); + // This is fine - trying to apply changes to an object that has been deleted + if (error.type == 'NotFoundException') { + processedChangeIds = processedChangeIds.concat(c.ids); + } else { + throw error; + } }); }); } - return promiseChain(chain).then(() => { + return promiseChain(chain).catch((error) => { + Log.warn('Synchronization was interrupted due to an error:', error); + }).then(() => { Log.info('IDs to delete: ', processedChangeIds); Change.deleteMultiple(processedChangeIds); }); diff --git a/ReactNativeClient/src/web-api.js b/ReactNativeClient/src/web-api.js index 0de2461299..c49ed396c7 100644 --- a/ReactNativeClient/src/web-api.js +++ b/ReactNativeClient/src/web-api.js @@ -1,6 +1,21 @@ import { Log } from 'src/log.js'; import { stringify } from 'query-string'; +class WebApiError extends Error { + + constructor(msg) { + let type = 'WebApiError'; + // Create a regular JS Error object from a web api error response { error: "something", type: "NotFoundException" } + if (typeof msg === 'object' && msg !== null) { + if (msg.type) type = msg.type; + msg = msg.error ? msg.error : 'error'; + } + super(msg); + this.type = type; + } + +} + class WebApi { constructor(baseUrl) { @@ -73,7 +88,7 @@ class WebApi { let responseClone = response.clone(); return response.json().then(function(data) { if (data && data.error) { - reject(new Error(data.error)); + reject(new WebApiError(data)); } else { resolve(data); } diff --git a/src/AppBundle/Controller/FoldersController.php b/src/AppBundle/Controller/FoldersController.php index 94fe347ba4..6ae4ec5895 100755 --- a/src/AppBundle/Controller/FoldersController.php +++ b/src/AppBundle/Controller/FoldersController.php @@ -7,10 +7,8 @@ use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\HttpFoundation\Request; use AppBundle\Controller\ApiController; use AppBundle\Model\Folder; - - - -use AppBundle\Model\BaseItem; +use AppBundle\Exception\NotFoundException; +use AppBundle\Exception\MethodNotAllowedException; class FoldersController extends ApiController { @@ -30,7 +28,7 @@ class FoldersController extends ApiController { return static::successResponse($folder); } - return static::errorResponse('Invalid method'); + throw new MethodNotAllowedException(); } /** @@ -38,7 +36,7 @@ class FoldersController extends ApiController { */ public function oneAction($id, Request $request) { $folder = Folder::byId(Folder::unhex($id)); - if (!$folder && !$request->isMethod('PUT')) return static::errorResponse('Not found', 0, 404); + if (!$folder && !$request->isMethod('PUT')) throw new NotFoundException(); if ($request->isMethod('GET')) { return static::successResponse($folder); @@ -68,7 +66,7 @@ class FoldersController extends ApiController { return static::successResponse(array('id' => $id)); } - return static::errorResponse('Invalid method'); + throw new MethodNotAllowedException(); } /** @@ -76,7 +74,7 @@ class FoldersController extends ApiController { */ public function linkAction($id, Request $request) { $folder = Folder::byId(Folder::unhex($id)); - if (!$folder) return static::errorResponse('Not found', 0, 404); + if (!$folder) throw new NotFoundException(); if ($request->isMethod('GET')) { return static::successResponse($folder->notes()); @@ -91,7 +89,7 @@ class FoldersController extends ApiController { return static::successResponse(); } - return static::errorResponse('Invalid method'); + throw new MethodNotAllowedException(); } } diff --git a/src/AppBundle/Controller/NotesController.php b/src/AppBundle/Controller/NotesController.php index 834418ecfc..da7ef97792 100755 --- a/src/AppBundle/Controller/NotesController.php +++ b/src/AppBundle/Controller/NotesController.php @@ -7,6 +7,7 @@ use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Symfony\Component\HttpFoundation\Request; use AppBundle\Controller\ApiController; use AppBundle\Model\Note; +use AppBundle\Exception\NotFoundException; class NotesController extends ApiController { @@ -30,7 +31,7 @@ class NotesController extends ApiController { */ public function oneAction($id, Request $request) { $note = Note::find(Note::unhex($id)); - if (!$note && !$request->isMethod('PUT')) return static::errorResponse('Not found', 0, 404); + if (!$note && !$request->isMethod('PUT')) throw new NotFoundException(); if ($request->isMethod('GET')) { return static::successResponse($note); diff --git a/src/AppBundle/Model/Folder.php b/src/AppBundle/Model/Folder.php index 6db57ae6d3..d560c7ccd4 100755 --- a/src/AppBundle/Model/Folder.php +++ b/src/AppBundle/Model/Folder.php @@ -30,4 +30,18 @@ class Folder extends BaseItem { return Note::where('parent_id', '=', $this->id)->get(); } + static public function countByOwnerId($ownerId) { + return Folder::where('owner_id', '=', $ownerId)->count(); + } + + public function delete() { + if (self::countByOwnerId($this->owner_id) <= 1) throw new \Exception('Cannot delete the last folder'); + + $notes = $this->notes(); + foreach ($notes as $note) { + $note->delete(); + } + return parent::delete(); + } + } diff --git a/start_emulator.bat b/start_emulator.bat new file mode 100644 index 0000000000..775fb7e433 --- /dev/null +++ b/start_emulator.bat @@ -0,0 +1,3 @@ +c: +C:\Users\Laurent\AppData\Local\Android\sdk\tools +emulator.exe -avd Nexus_5X_API_23_Google_API_ \ No newline at end of file