diff --git a/bower.json b/bower.json
index 53d46adde1e266ec7387d796e2ca784a167fff1d..b0734e769b79b3532a303f274e5890995be5cd90 100644
--- a/bower.json
+++ b/bower.json
@@ -12,7 +12,8 @@
     "angular-animate": "1.4.3",
     "angular-sanitize": "1.5.3",
     "angular": "1.5.3",
-    "angular-bind-notifier": "^1.1.7"
+    "angular-bind-notifier": "^1.1.7",
+    "angular-image-crop": "^2.0.0"
   },
   "resolutions": {
     "angular-sanitize": "1.5.3",
diff --git a/config.xml b/config.xml
index c8aa375ee163db25a01bbe1ec7749d62bedc693e..b41329b83a3e26dde0142df0f5b02b1e480fc4dc 100644
--- a/config.xml
+++ b/config.xml
@@ -23,12 +23,12 @@
   <preference name="SplashScreenDelay" value="4000" />
   <preference name="xwalkVersion" value="19" />
   <preference name="xwalkMultipleApk" value="false"/>
+  <preference name="android-minSdkVersion" value="16" />
   <allow-navigation href="*" />
   <feature name="StatusBar">
     <param name="ios-package" onload="true" value="CDVStatusBar" />
   </feature>
   <platform name="android">
-    <preference name="android-minSdkVersion" value="19" />
     <icon density="ldpi" src="resources/android/icon/drawable-ldpi-icon.png" />
     <icon density="mdpi" src="resources/android/icon/drawable-mdpi-icon.png" />
     <icon density="hdpi" src="resources/android/icon/drawable-hdpi-icon.png" />
diff --git a/hooks/after_prepare/020_remove_code.js b/hooks/after_prepare/020_remove_code.js
index e9ced16196a7c7e9340870a3dc10b65579cd07a2..8b518ab85611d1ca8fb077fd24a1873e6d8ee0ce 100755
--- a/hooks/after_prepare/020_remove_code.js
+++ b/hooks/after_prepare/020_remove_code.js
@@ -50,11 +50,16 @@ if (rootdir) {
         .pipe(htmlmin())
         .pipe(gulp.dest(wwwPath)),
 
-      // Remove unused JS code
-      gulp.src([wwwPath +  + '/js/**/*.js', pluginPath +  + '/js/**/*.js'])
+      // Remove unused JS code + add ng annotations
+      gulp.src([wwwPath + '/js/**/*.js'])
         .pipe(removeCode({device: true}))
         .pipe(ngAnnotate({single_quotes: true}))
-        .pipe(gulp.dest("."))
+        .pipe(gulp.dest(wwwPath + '/dist/dist_js/app')),
+
+      gulp.src([pluginPath + '/js/**/*.js'])
+        .pipe(removeCode({device: true}))
+        .pipe(ngAnnotate({single_quotes: true}))
+        .pipe(gulp.dest(wwwPath + '/dist/dist_js/plugins'))
      );
 
   }
diff --git a/www/i18n/locale-en-GB.json b/www/i18n/locale-en-GB.json
index e43cbef60caf9c1fb48b900bbdc2aab429904749..3b9e4ed3d5019e5d55501ead43cbaa6f7e8a5436 100644
--- a/www/i18n/locale-en-GB.json
+++ b/www/i18n/locale-en-GB.json
@@ -106,7 +106,6 @@
     "BTN_CURRENCY": "Explore currency",
     "BTN_ABOUT": "about",
     "BTN_HELP": "Help",
-    "BTN_ACCOUNT": "My Account",
     "REPORT_ISSUE": "Report an issue"
   },
   "SETTINGS": {
diff --git a/www/i18n/locale-en.json b/www/i18n/locale-en.json
index dc3eb1a2815ba35f22eba8fd7114698738bf9fd7..85be275d1aa2a568597bcebbd59970e41ebe7fb7 100644
--- a/www/i18n/locale-en.json
+++ b/www/i18n/locale-en.json
@@ -106,7 +106,6 @@
     "BTN_CURRENCY": "Explore currency",
     "BTN_ABOUT": "about",
     "BTN_HELP": "Help",
-    "BTN_ACCOUNT": "My Account",
     "REPORT_ISSUE": "Report an issue"
   },
   "SETTINGS": {
diff --git a/www/i18n/locale-fr-FR.json b/www/i18n/locale-fr-FR.json
index 1cca03259838576fcecb75c3872a0b4abb183bac..f388b5bfd56849ef7a853977cec529bda887c4c2 100644
--- a/www/i18n/locale-fr-FR.json
+++ b/www/i18n/locale-fr-FR.json
@@ -106,7 +106,6 @@
     "BTN_CURRENCY": "Explorer la monnaie",
     "BTN_ABOUT": "à propos",
     "BTN_HELP": "Aide en ligne",
-    "BTN_ACCOUNT": "Mon compte",
     "REPORT_ISSUE": "anomalie"
   },
   "SETTINGS": {
diff --git a/www/i18n/locale-nl-NL.json b/www/i18n/locale-nl-NL.json
index 9eee9dc1198ff464b29cdf952ffde84a508fb26e..710b957e35b6275cd954aafb0c43c1cb8076ae32 100644
--- a/www/i18n/locale-nl-NL.json
+++ b/www/i18n/locale-nl-NL.json
@@ -101,7 +101,6 @@
     "BTN_CURRENCY": "Verken valuta",
     "BTN_ABOUT": "over",
     "BTN_HELP": "Help",
-    "BTN_ACCOUNT": "Mijn rekening",
     "REPORT_ISSUE": "Meld een probleem"
   },
   "SETTINGS": {
diff --git a/www/index.html b/www/index.html
index 1e7c8e2e17ce419d8f136cb7b01638683d4ff235..a9a243ed654635058b2b242537b76b8d34069e31 100644
--- a/www/index.html
+++ b/www/index.html
@@ -12,6 +12,7 @@
   <link href="css/style.css" rel="stylesheet">
 
   <!--removeIf(device)-->
+  <link href="lib/angular-image-crop/image-crop-styles.css" rel="stylesheet">
   <link href="css/style-no-device.css" rel="stylesheet">
   <!--endRemoveIf(device)-->
 
@@ -41,11 +42,10 @@
   <script src="lib/ionic/js/angular/angular-cache.js"></script>
   <script src="lib/ionic/js/angular/angular-screenmatch.min.js"></script>
   <script src="lib/ionic/js/angular/angular-bind-notifier.min.js"></script>
-
-  <!-- crypto libs -->
+  <script src="lib/angular-image-crop/image-crop.js"></script>
   <script src="js/vendor/base58.js" async></script>
-  <script src="js/vendor/nacl_factory.js" async></script>
   <!--removeIf(device)-->
+  <script src="js/vendor/nacl_factory.js" async></script>
   <script src="js/vendor/scrypt-em.js" async></script>
   <script src="js/vendor/base64.js" async></script>
   <!--endRemoveIf(device)-->
diff --git a/www/js/app.js b/www/js/app.js
index d6f2bdad8755d443a93f74137e6dcfa484ac271b..20bdfe6b0e556343cd5d823972eed834c59e29af 100644
--- a/www/js/app.js
+++ b/www/js/app.js
@@ -7,6 +7,7 @@
 angular.module('cesium', ['ionic', 'ionic-material', 'ngMessages', 'pascalprecht.translate',
   'ngApi', 'angular-cache', 'angular.screenmatch', 'angular.bind.notifier',
   // removeIf(device)
+  'ImageCropper',
   // endRemoveIf(device)
   // removeIf(no-device)
   'ngCordova',
diff --git a/www/js/services/crypto-services.js b/www/js/services/crypto-services.js
index fc4a840002166478c0a1942c13b0ebe9473ed20e..062a22383ab68c20c9c9d56dc0bae30ae9b89c8c 100644
--- a/www/js/services/crypto-services.js
+++ b/www/js/services/crypto-services.js
@@ -5,250 +5,288 @@ angular.module('cesium.crypto.services', ['ngResource', 'cesium.device.services'
   .factory('CryptoUtils', function($q, $timeout, Device) {
     'ngInject';
 
-    // Const
-    var
-      crypto_sign_BYTES = 64,
-      crypto_secretbox_NONCEBYTES = 24,
-      SEED_LENGTH = 32, // Length of the key
-      SCRYPT_PARAMS = {
+    /**
+     * CryptoAbstract, abstract class with useful methods
+     * @type {number}
+     */
+    function CryptoAbstractService() {
+      this.loaded = false;
+      var that = this;
+
+      this.copy = function(source) {
+        _.forEach(_.keys(source), function(key) {
+          that[key] = source[key];
+        });
+      };
+
+      this.isLoaded = function() {
+        return this.loaded;
+      };
+
+      this.util = this.util || {};
+
+      /**
+       * Converts an array buffer to a string
+       *
+       * @private
+       * @param {ArrayBuffer} buf The buffer to convert
+       * @param {Function} callback The function to call when conversion is complete
+       */
+      this.util.array_to_string = function(buf, callback) {
+        var bb = new Blob([new Uint8Array(buf)]);
+        var f = new FileReader();
+        f.onload = function(e) {
+          callback(e.target.result);
+        };
+        f.readAsText(bb);
+      };
+    }
+
+    CryptoAbstractService.prototype.constants = {
+      crypto_sign_BYTES: 64,
+      crypto_secretbox_NONCEBYTES: 24,
+      SEED_LENGTH: 32, // Length of the key
+      SCRYPT_PARAMS:{
         N: 4096,
         r: 16,
         p: 1
-      },
-      // Web crypto API - see https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API
-      crypto = window.crypto || window.msCrypto || window.Crypto,
+      }
+    };
 
-    async_load_base58 = function(on_ready) {
+    CryptoAbstractService.prototype.async_load_base58 = function(on_ready) {
+      var that = this;
       if (Base58 !== null){return on_ready(Base58);}
-      else {$timeout(function(){async_load_base58(on_ready);}, 100);}
-    },
+      else {$timeout(function(){that.async_load_base58(on_ready);}, 100);}
+    };
 
-    async_load_scrypt = function(on_ready, options) {
+    CryptoAbstractService.prototype.async_load_scrypt = function(on_ready, options) {
+      var that = this;
       if (scrypt_module_factory !== null){on_ready(scrypt_module_factory(options.requested_total_memory));}
-      else {$timeout(function(){async_load_scrypt(on_ready, options);}, 100);}
-    },
+      else {$timeout(function(){that.async_load_scrypt(on_ready, options);}, 100);}
+    };
 
-    async_load_nacl_js = function(on_ready, options) {
+    CryptoAbstractService.prototype.async_load_nacl_js = function(on_ready, options) {
+      var that = this;
       if (nacl_factory !== null) {nacl_factory.instantiate(on_ready, options);}
-      else {$timeout(function(){async_load_nacl_js(on_ready, options);}, 100);}
-    },
+      else {$timeout(function(){that.async_load_nacl_js(on_ready, options);}, 100);}
+    };
 
-    async_load_base64 = function(on_ready) {
+    CryptoAbstractService.prototype.async_load_base64 = function(on_ready) {
+      var that = this;
       if (Base64 !== null) {on_ready(Base64);}
-      else {$timetout(function(){async_load_base64(on_ready);}, 100);}
-    },
+      else {$timetout(function(){that.async_load_base64(on_ready);}, 100);}
+    };
 
-    async_load_sha256 = function(on_ready) {
+    CryptoAbstractService.prototype.async_load_sha256 = function(on_ready) {
+      var that = this;
       if (sha256 !== null){return on_ready(sha256);}
-      else {$timeout(function(){async_load_sha256(on_ready);}, 100);}
+      else {$timeout(function(){that.async_load_sha256(on_ready);}, 100);}
     };
 
-    if (crypto) {
-      console.debug('Web Crypto API (window.crypto) exists: getRandomValues=[{0}]'.format(!!crypto.getRandomValues));
+    // Web crypto API - see https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API
+    var crypto =  window.crypto || window.msCrypto || window.Crypto;
+    if (crypto && crypto.getRandomValues) {
+      CryptoAbstractService.prototype.crypto = crypto;
+      CryptoAbstractService.prototype.util = {};
+      CryptoAbstractService.prototype.util.random_nonce = function() {
+        var nonce = new Uint8Array(crypto_secretbox_NONCEBYTES);
+        this.crypto.getRandomValues(nonce);
+        return $q.when(nonce);
+      };
+    }
+    else {
+      // TODO: add a default function ?
+      //CryptoAbstractService.prototype.random_nonce = function() {
+      // var nonce = new Uint8Array(crypto_secretbox_NONCEBYTES);
+      // var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+      // for(var i = 0; i < length; i++) {
+      //   text += possible.charAt(Math.floor(Math.random() * possible.length));
+      // }
+      //}
     }
 
     function FullJSServiceFactory() {
+      this.id = 'FullJS';
+
+      // libraries handlers
+      this.scrypt = null;
+      this.nacl = null;
+      this.base58 = null;
+      this.base64 = null;
+      var that = this;
+
+      this.util = this.util || {};
+      this.util.decode_utf8 = function (s) {
+        var i, d = unescape(encodeURIComponent(s)), b = new Uint8Array(d.length);
+        for (i = 0; i < d.length; i++) b[i] = d.charCodeAt(i);
+        return b;
+      };
+      this.util.encode_utf8 = function (s) {
+        return that.nacl.encode_utf8(s);
+      };
+      this.util.encode_base58 = function (a) {
+        return that.base58.encode(a);
+      };
+      this.util.decode_base58 = function (a) {
+        var i;
+        a = that.base58.decode(a);
+        var b = new Uint8Array(a.length);
+        for (i = 0; i < a.length; i++) b[i] = a[i];
+        return b;
+      };
+      this.util.decode_base64 = function (a) {
+        return that.base64.decode(a);
+      };
+      this.util.encode_base64 = function () {
+        return that.base64.encode(arguments);
+      };
 
-      var
-        // libraries handlers
-        scrypt,
-        nacl,
-        base58,
-        base64,
-        loadedLib = 0,
-
-        // functions
-        decode_utf8 = function(s) {
-            var i, d = unescape(encodeURIComponent(s)), b = new Uint8Array(d.length);
-            for (i = 0; i < d.length; i++) b[i] = d.charCodeAt(i);
-            return b;
-        },
-        encode_utf8 = function(s) {
-          return nacl.encode_utf8(s);
-        },
-        encode_base58 = function(a) {
-          return base58.encode(a);
-        },
-        decode_base58 = function(a) {
-          var i;
-          a = base58.decode(a);
-          var b = new Uint8Array(a.length);
-          for (i = 0; i < a.length; i++) b[i] = a[i];
-          return b;
-        },
-        hash_sha256 = function(message) {
-          return $q(function(resolve) {
-            var msg = decode_utf8(message);
-            var hash = nacl.to_hex(nacl.crypto_hash_sha256(msg));
-            resolve(hash.toUpperCase());
-          });
-        },
-        random_nonce = function() {
-          if (crypto && crypto.getRandomValues) {
-            var nonce = new Uint8Array(crypto_secretbox_NONCEBYTES);
-            crypto.getRandomValues(nonce);
-            return $q.when(nonce);
-          }
-          else {
-            // TODO
-            // var nonce = new Uint8Array(crypto_secretbox_NONCEBYTES);
-            // var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
-            // for(var i = 0; i < length; i++) {
-            //   text += possible.charAt(Math.floor(Math.random() * possible.length));
-            // }
-            return $q.when(nacl.crypto_box_random_nonce());
-          }
-        },
-
-        /**
-         * Converts an array buffer to a string
-         *
-         * @private
-         * @param {ArrayBuffer} buf The buffer to convert
-         * @param {Function} callback The function to call when conversion is complete
-         */
-        array_to_string = function(buf, callback) {
-          var bb = new Blob([new Uint8Array(buf)]);
-          var f = new FileReader();
-          f.onload = function(e) {
-            callback(e.target.result);
-          };
-          f.readAsText(bb);
-        },
-
-        /**
-         * Create key pairs (sign and box), from salt+password
-         */
-        connect = function(salt, password) {
-          return $q(function(resolve, reject) {
-            var seed = scrypt.crypto_scrypt(
-              nacl.encode_utf8(password),
-              nacl.encode_utf8(salt),
-              SCRYPT_PARAMS.N,
-              SCRYPT_PARAMS.r,
-              SCRYPT_PARAMS.p,
-              SEED_LENGTH);
-            var signKeypair = nacl.crypto_sign_seed_keypair(seed);
-            var boxKeypair = nacl.crypto_box_seed_keypair(seed);
-            resolve({
-              signPk: signKeypair.signPk,
-              signSk: signKeypair.signSk,
-              boxPk: boxKeypair.boxPk,
-              boxSk: boxKeypair.boxSk
-            });
-          });
-        },
-
-        /**
-         * Verify a signature of a message, for a pubkey
-         */
-        verify = function (message, signature, pubkey) {
-          return $q(function(resolve, reject) {
-            var msg = decode_utf8(message);
-            var sig = base64.decode(signature);
-            var pub = base58.decode(pubkey);
-            var m = new Uint8Array(crypto_sign_BYTES + msg.length);
-            var sm = new Uint8Array(crypto_sign_BYTES + msg.length);
-            var i;
-            for (i = 0; i < crypto_sign_BYTES; i++) sm[i] = sig[i];
-            for (i = 0; i < msg.length; i++) sm[i+crypto_sign_BYTES] = msg[i];
-
-            // Call to verification lib...
-            var verified = nacl.crypto_sign_open(sm, pub) !== null;
-            resolve(verified);
-          });
-        },
-
-        /**
-         * Sign a message, from a key pair
-         */
-        sign = function(message, keypair) {
-          return $q(function(resolve, reject) {
-            var m = decode_utf8(message);
-            var sk = keypair.signSk;
-            var signedMsg = nacl.crypto_sign(m, sk);
-            var sig = new Uint8Array(crypto_sign_BYTES);
-            for (var i = 0; i < sig.length; i++) sig[i] = signedMsg[i];
-            var signature = base64.encode(sig);
-            resolve(signature);
-          });
-        },
+      this.util.hash_sha256 = function (message) {
+        return $q(function (resolve) {
+          var msg = that.util.decode_utf8(message);
+          var hash = that.nacl.to_hex(that.nacl.crypto_hash_sha256(msg));
+          resolve(hash.toUpperCase());
+        });
+      };
+      this.util.random_nonce = function () {
+        if (that.crypto && that.crypto.getRandomValues) {
+          var nonce = new Uint8Array(that.constants.crypto_secretbox_NONCEBYTES);
+          that.crypto.getRandomValues(nonce);
+          return $q.when(nonce);
+        }
+        else {
+          return $q.when(that.nacl.crypto_box_random_nonce());
+        }
+      };
 
-        /**
-         * Compute the box key pair, from a sign key pair
-         */
-        box_keypair_from_sign = function(signKeyPair) {
-          if (signKeyPair.bokSk && signKeyPair.boxPk) return $q.when(signKeyPair);
-          return $q.when(nacl.crypto_box_keypair_from_sign_sk(signKeyPair.signSk));
-        },
+      /**
+       * Compute the box key pair, from a sign key pair
+       */
+      this.box_keypair_from_sign = function (signKeyPair) {
+        if (signKeyPair.bokSk && signKeyPair.boxPk) return $q.when(signKeyPair);
+        return $q.when(that.nacl.crypto_box_keypair_from_sign_sk(signKeyPair.signSk));
+      };
 
-        /**
-         * Compute the box public key, from a sign public key
-         */
-        box_pk_from_sign = function(signPk) {
-          return $q.when(nacl.crypto_box_pk_from_sign_pk(signPk));
-        },
+      /**
+       * Compute the box public key, from a sign public key
+       */
+      this.box_pk_from_sign = function (signPk) {
+        return $q.when(that.nacl.crypto_box_pk_from_sign_pk(signPk));
+      };
 
-        /**
-         * Encrypt a message, from a key pair
-         */
-        box = function(message, nonce, recipientPk, senderSk) {
-          return $q(function(resolve, reject) {
-            if (!message) {
-              resolve(message);
-              return;
-            }
-            var messageBin = decode_utf8(message);
-            if (typeof recipientPk === "string") {
-              recipientPk = decode_base58(recipientPk);
-            }
+      /**
+       * Encrypt a message, from a key pair
+       */
+      this.box = function (message, nonce, recipientPk, senderSk) {
+        return $q(function (resolve, reject) {
+          if (!message) {
+            resolve(message);
+            return;
+          }
+          var messageBin = that.util.decode_utf8(message);
+          if (typeof recipientPk === "string") {
+            recipientPk = that.util.decode_base58(recipientPk);
+          }
 
-            //console.debug('Original message: ' + message);
-            try {
-              var ciphertextBin = nacl.crypto_box(messageBin, nonce, recipientPk, senderSk);
-              var ciphertext = base64.encode(ciphertextBin);
-              //console.debug('Encrypted message: ' + ciphertext);
-              resolve(ciphertext);
-            }
-            catch(err) {
-              reject(err);
-            }
-          });
-        },
+          //console.debug('Original message: ' + message);
+          try {
+            var ciphertextBin = that.nacl.crypto_box(messageBin, nonce, recipientPk, senderSk);
+            var ciphertext = that.util.encode_base64(ciphertextBin);
+            //console.debug('Encrypted message: ' + ciphertext);
+            resolve(ciphertext);
+          }
+          catch (err) {
+            reject(err);
+          }
+        });
+      };
 
       /**
        * Decrypt a message, from a key pair
        */
-      box_open = function(cypherText, nonce, senderPk, recipientSk) {
-        return $q(function(resolve, reject) {
+      this.box_open = function (cypherText, nonce, senderPk, recipientSk) {
+        return $q(function (resolve, reject) {
           if (!cypherText) {
             resolve(cypherText);
             return;
           }
-          var ciphertextBin = base64.decode(cypherText);
+          var ciphertextBin = that.util.decode_base64(cypherText);
           if (typeof senderPk === "string") {
-            senderPk = CryptoUtils.util.decode_base58(senderPk);
+            senderPk = that.util.decode_base58(senderPk);
           }
 
           try {
-            var message = nacl.crypto_box_open(ciphertextBin, nonce, senderPk, recipientSk);
-            array_to_string(message, function(result) {
+            var message = that.nacl.crypto_box_open(ciphertextBin, nonce, senderPk, recipientSk);
+            that.util.array_to_string(message, function (result) {
               //console.debug('Decrypted text: ' + result);
               resolve(result);
             });
           }
-          catch(err) {
+          catch (err) {
             reject(err);
           }
 
         });
-      },
+      };
 
-      isLoaded = function() {
-        return loadedLib === 4;
-      },
+      /**
+       * Create key pairs (sign and box), from salt+password
+       */
+      this.connect = function(salt, password) {
+        return $q(function(resolve, reject) {
+          var seed = that.scrypt.crypto_scrypt(
+            that.util.encode_utf8(password),
+            that.util.encode_utf8(salt),
+            that.constants.SCRYPT_PARAMS.N,
+            that.constants.SCRYPT_PARAMS.r,
+            that.constants.SCRYPT_PARAMS.p,
+            that.constants.SEED_LENGTH);
+          var signKeypair = that.nacl.crypto_sign_seed_keypair(seed);
+          var boxKeypair = that.nacl.crypto_box_seed_keypair(seed);
+          resolve({
+            signPk: signKeypair.signPk,
+            signSk: signKeypair.signSk,
+            boxPk: boxKeypair.boxPk,
+            boxSk: boxKeypair.boxSk
+          });
+        });
+      };
 
-      load = function() {
+      /**
+       * Verify a signature of a message, for a pubkey
+       */
+      this.verify = function (message, signature, pubkey) {
+        return $q(function(resolve, reject) {
+          var msg = that.util.decode_utf8(message);
+          var sig = that.util.decode_base64(signature);
+          var pub = that.util.decode_base58(pubkey);
+          var sm = new Uint8Array(that.constants.crypto_sign_BYTES + msg.length);
+          var i;
+          for (i = 0; i < that.constants.crypto_sign_BYTES; i++) sm[i] = sig[i];
+          for (i = 0; i < msg.length; i++) sm[i+that.constants.crypto_sign_BYTES] = msg[i];
+
+          // Call to verification lib...
+          var verified = that.nacl.crypto_sign_open(sm, pub) !== null;
+          resolve(verified);
+        });
+      };
+
+      /**
+       * Sign a message, from a key pair
+       */
+      this.sign = function(message, keypair) {
+        return $q(function(resolve, reject) {
+          var m = that.util.decode_utf8(message);
+          var sk = keypair.signSk;
+          var signedMsg = that.nacl.crypto_sign(m, sk);
+          var sig = new Uint8Array(that.constants.crypto_sign_BYTES);
+          for (var i = 0; i < sig.length; i++) sig[i] = signedMsg[i];
+          var signature = that.base64.encode(sig);
+          resolve(signature);
+        });
+      };
+
+      this.load = function() {
         var deferred = $q.defer();
         var naclOptions = {};
         var scryptOptions = {};
@@ -258,274 +296,252 @@ angular.module('cesium.crypto.services', ['ngResource', 'cesium.device.services'
           console.log('Reduce Scrypt memory because plateform grade is not [a] but [' + ionic.Platform.grade + ']');
           scryptOptions.requested_total_memory = 16 * 1048576; // 16 Mo
         }
+        var loadedLib = 0;
         var checkAllLibLoaded = function() {
           loadedLib++;
-          if (isLoaded()) {
+          if (loadedLib === 4) {
+            that.loaded = true;
             deferred.resolve();
           }
         };
-        async_load_nacl_js(function(lib) {
-          nacl = lib;
+        this.async_load_nacl_js(function(lib) {
+          that.nacl = lib;
           checkAllLibLoaded();
         }, naclOptions);
-        async_load_scrypt(function(lib) {
-          scrypt = lib;
+        this.async_load_scrypt(function(lib) {
+          that.scrypt = lib;
           checkAllLibLoaded();
         }, scryptOptions);
-        async_load_base58(function(lib) {
-          base58 = lib;
+        this.async_load_base58(function(lib) {
+          that.base58 = lib;
           checkAllLibLoaded();
         });
-        async_load_base64(function(lib) {
-          base64 = lib;
+        this.async_load_base64(function(lib) {
+          that.base64 = lib;
           checkAllLibLoaded();
         });
         return deferred.promise;
       };
 
-      // Service's exposed methods
-      return {
-        isLoaded: isLoaded,
-        load: load,
-        util: {
-          encode_utf8: encode_utf8,
-          decode_utf8: decode_utf8,
-          encode_base58: encode_base58,
-          decode_base58: decode_base58,
-          hash: hash_sha256,
-          encode_base64: function() {return base64.encode(arguments);},
-          random_nonce: random_nonce
+      // Shortcuts
+      this.util.hash = that.util.hash_sha256;
+      this.box = {
+        keypair: {
+          fromSignKeypair: that.box_keypair_from_sign,
+          pkFromSignPk: that.box_pk_from_sign
         },
-        connect: connect,
-        sign: sign,
-        verify: verify,
-        box: {
-          keypair: {
-            fromSignKeypair: box_keypair_from_sign,
-            pkFromSignPk: box_pk_from_sign
-          },
-          pack: box,
-          open: box_open
-        }
+        pack: that.box,
+        open: that.box_open
       };
     }
+    FullJSServiceFactory.prototype = new CryptoAbstractService();
+
+
+    /* -----------------------------------------------------------------------------------------------------------------
+     * Service that use Cordova MiniSodium plugin
+     * ----------------------------------------------------------------------------------------------------------------*/
 
     /***
      * Factory for crypto, using Cordova plugin
      */
     function CordovaServiceFactory() {
-      var
-        // libraries handlers
-        nacl, // the cordova plugin
-        nacl_js, // the full JS lib (need for random values)
-        base58,
-        sha256,
-        loadedLib = 0,
-
-        // functions
-        decode_utf8 = function(s) {
-          return nacl.to_string(s);
-        },
-        encode_utf8 = function(s) {
-          return nacl.from_string(s);
-        },
-        encode_base58 = function(a) {
-          return base58.encode(a);
-        },
-        decode_base58 = function(a) {
-          var i;
-          a = base58.decode(a);
-          var b = new Uint8Array(a.length);
-          for (i = 0; i < a.length; i++) b[i] = a[i];
-          return b;
-        },
-        hash_sha256 = function(message) {
-          return $q.when(sha256(message).toUpperCase());
-        },
-        random_nonce = function() {
-          if (crypto && crypto.getRandomValues) {
-            var nonce = new Uint8Array(crypto_secretbox_NONCEBYTES);
-            crypto.getRandomValues(nonce);
-            return $q.when(nonce);
-          }
-          else {
-            return $q.when(nacl_js.crypto_box_random_nonce());
-          }
-        },
-        /**
-         * Converts an array buffer to a string
-         *
-         * @private
-         * @param {ArrayBuffer} buf The buffer to convert
-         * @param {Function} callback The function to call when conversion is complete
-         */
-        array_to_string = function(buf, callback) {
-          var bb = new Blob([new Uint8Array(buf)]);
-          var f = new FileReader();
-          f.onload = function(e) {
-            callback(e.target.result);
-          };
-          f.readAsText(bb);
-        },
 
-        /**
-         * Create key pairs (sign and box), from salt+password, using cordova
-         */
-        connect = function(salt, password) {
-          var deferred = $q.defer();
-
-          nacl.crypto_pwhash_scryptsalsa208sha256_ll(
-            nacl.from_string(password),
-            nacl.from_string(salt),
-            SCRYPT_PARAMS.N,
-            SCRYPT_PARAMS.r,
-            SCRYPT_PARAMS.p,
-            SEED_LENGTH,
-            function (err, seed) {
-              if (err) { deferred.reject(err); return;}
-              nacl.crypto_sign_seed_keypair(seed, function (err, signKeypair) {
-                if (err) { deferred.reject(err); return;}
-                var result = {
-                  signPk: signKeypair.pk,
-                  signSk: signKeypair.sk
-                };
-                box_keypair_from_sign(result)
-                  .then(function(boxKeypair) {
-                    result.boxPk = boxKeypair.pk;
-                    result.boxSk = boxKeypair.sk;
-                    deferred.resolve(result);
-                  })
-                  .catch(function(err) {
-                    deferred.reject(err);
-                  });
-              });
+      this.id = 'MiniSodium';
 
-            }
-          );
+      // libraries handlers
+      this.nacl = null; // the cordova plugin
+      this.nacl_js= null; // the full JS lib (need for random values)
+      this.base58= null;
+      this.sha256= null;
+      var that = this;
 
-          return deferred.promise;
-        },
+      // functions
+      this.util = this.util || {};
+      this.util.decode_utf8 = function(s) {
+        return that.nacl.to_string(s);
+      };
+      this.util.encode_utf8 = function(s) {
+        return that.nacl.from_string(s);
+      };
+      this.util.encode_base58 = function(a) {
+        return that.base58.encode(a);
+      };
+      this.util.decode_base58 = function(a) {
+        var i;
+        a = that.base58.decode(a);
+        var b = new Uint8Array(a.length);
+        for (i = 0; i < a.length; i++) b[i] = a[i];
+        return b;
+      };
+      this.util.hash_sha256 = function(message) {
+        return $q.when(that.sha256(message).toUpperCase());
+      };
+      this.util.random_nonce = function() {
+        if (that.crypto && that.crypto.getRandomValues) {
+          var nonce = new Uint8Array(that.constants.crypto_secretbox_NONCEBYTES);
+          that.crypto.getRandomValues(nonce);
+          return $q.when(nonce);
+        }
+        else {
+          return $q.when(that.nacl_js.crypto_box_random_nonce());
+        }
+      };
 
-        /**
-         * Verify a signature of a message, for a pubkey
-         */
-        verify = function (message, signature, pubkey) {
-          var deferred = $q.defer();
-          nacl.crypto_sign_verify_detached(
-            nacl.from_base64(signature),
-            nacl.from_string(message),
-            nacl.from_base64(pubkey),
-            function(err, verified) {
-              if (err) { deferred.reject(err); return;}
-              deferred.resolve(verified);
-            });
-          return deferred.promise;
-        },
+      /**
+       * Create key pairs (sign and box), from salt+password, using cordova
+       */
+      this.connect = function(salt, password) {
+        var deferred = $q.defer();
 
-        /**
-         * Sign a message, from a key pair
-         */
-        sign = function(message, keypair) {
-          var deferred = $q.defer();
-          var nacl = window.plugins.MiniSodium;
-
-          nacl.crypto_sign(
-            nacl.from_string(message), // message
-            keypair.signSk, // sk
-            function(err, signedMsg) {
+        that.nacl.crypto_pwhash_scryptsalsa208sha256_ll(
+          that.nacl.from_string(password),
+          that.nacl.from_string(salt),
+          that.constants.SCRYPT_PARAMS.N,
+          that.constants.SCRYPT_PARAMS.r,
+          that.constants.SCRYPT_PARAMS.p,
+          that.constants.SEED_LENGTH,
+          function (err, seed) {
+            if (err) { deferred.reject(err); return;}
+            that.nacl.crypto_sign_seed_keypair(seed, function (err, signKeypair) {
               if (err) { deferred.reject(err); return;}
-              var sig;
-              if (signedMsg.length > crypto_sign_BYTES) {
-                sig = new Uint8Array(crypto_sign_BYTES);
-                for (var i = 0; i < sig.length; i++) sig[i] = signedMsg[i];
-                console.debug("//******** HAS REDUCE signedMsg ********* /// ");
-              }
-              else {
-                sig = signedMsg;
-              }
-              var signature = nacl.to_base64(sig);
-              deferred.resolve(signature);
+              var result = {
+                signPk: signKeypair.pk,
+                signSk: signKeypair.sk
+              };
+              that.box_keypair_from_sign(result)
+                .then(function(boxKeypair) {
+                  result.boxPk = boxKeypair.pk;
+                  result.boxSk = boxKeypair.sk;
+                  deferred.resolve(result);
+                })
+                .catch(function(err) {
+                  deferred.reject(err);
+                });
             });
 
-          return deferred.promise;
-        },
+          }
+        );
 
-        /**
-         * Compute the box key pair, from a sign key pair
-         */
-        box_keypair_from_sign = function(signKeyPair) {
-          console.log("box_keypair_from_sign");
-          if (signKeyPair.bokSk && signKeyPair.boxPk) return $q.when(signKeyPair);
-          var deferred = $q.defer();
-          var result = {};
-          nacl.crypto_sign_ed25519_pk_to_curve25519(signKeyPair.signPk, function(err, boxPk) {
-            if (err) { deferred.reject(err); return;}
-            result.boxPk = boxPk;
-            if (result.boxSk) deferred.resolve(result);
-          });
-          nacl.crypto_sign_ed25519_sk_to_curve25519(signKeyPair.signSk, function(err, boxSk) {
+        return deferred.promise;
+      };
+
+      /**
+       * Verify a signature of a message, for a pubkey
+       */
+      this.verify = function (message, signature, pubkey) {
+        var deferred = $q.defer();
+        that.nacl.crypto_sign_verify_detached(
+          that.nacl.from_base64(signature),
+          that.nacl.from_string(message),
+          that.nacl.from_base64(pubkey),
+          function(err, verified) {
             if (err) { deferred.reject(err); return;}
-            result.boxSk = boxSk;
-            if (result.boxPk) deferred.resolve(result);
+            deferred.resolve(verified);
           });
+        return deferred.promise;
+      };
 
-          return deferred.promise;
-        },
+      /**
+       * Sign a message, from a key pair
+       */
+      this.sign = function(message, keypair) {
+        var deferred = $q.defer();
 
-        /**
-         * Compute the box public key, from a sign public key
-         */
-        box_pk_from_sign = function(signPk) {
-          var deferred = $q.defer();
-          nacl.crypto_sign_ed25519_pk_to_curve25519(signPk, function(err, boxPk) {
+        that.nacl.crypto_sign(
+          that.nacl.from_string(message), // message
+          keypair.signSk, // sk
+          function(err, signedMsg) {
             if (err) { deferred.reject(err); return;}
-            deferred.resolve(boxPk);
+            var sig;
+            if (signedMsg.length > that.constants.crypto_sign_BYTES) {
+              sig = new Uint8Array(that.constants.crypto_sign_BYTES);
+              for (var i = 0; i < sig.length; i++) sig[i] = signedMsg[i];
+              console.debug("//******** HAS REDUCE signedMsg ********* /// ");
+            }
+            else {
+              sig = signedMsg;
+            }
+            var signature = that.nacl.to_base64(sig);
+            deferred.resolve(signature);
           });
-          return deferred.promise;
-        },
 
-        /**
-         * Encrypt a message, from a key pair
-         */
-        box = function(message, nonce, recipientPk, senderSk) {
-          if (!message) {
-            return $q.reject('No message');
-          }
-          var deferred = $q.defer();
-          var messageBin = encode_utf8(message);
-          if (typeof recipientPk === "string") {
-            recipientPk = decode_base58(recipientPk);
-          }
+        return deferred.promise;
+      };
 
-          //console.debug('Original message: ' + message);
+      /**
+       * Compute the box key pair, from a sign key pair
+       */
+      this.box_keypair_from_sign = function(signKeyPair) {
+        if (signKeyPair.bokSk && signKeyPair.boxPk) return $q.when(signKeyPair);
+        var deferred = $q.defer();
+        var result = {};
+        that.nacl.crypto_sign_ed25519_pk_to_curve25519(signKeyPair.signPk, function(err, boxPk) {
+          if (err) { deferred.reject(err); return;}
+          result.boxPk = boxPk;
+          if (result.boxSk) deferred.resolve(result);
+        });
+        that.nacl.crypto_sign_ed25519_sk_to_curve25519(signKeyPair.signSk, function(err, boxSk) {
+          if (err) { deferred.reject(err); return;}
+          result.boxSk = boxSk;
+          if (result.boxPk) deferred.resolve(result);
+        });
 
-          nacl.crypto_box_easy(messageBin, nonce, recipientPk, senderSk, function(err, ciphertextBin) {
-            if (err) { deferred.reject(err); return;}
-            var ciphertext = nacl.to_base64(ciphertextBin);
-            //console.debug('Encrypted message: ' + ciphertext);
-            deferred.resolve(ciphertext);
-          });
-          return deferred.promise;
-        };
+        return deferred.promise;
+      };
+
+      /**
+       * Compute the box public key, from a sign public key
+       */
+      this.box_pk_from_sign = function(signPk) {
+        var deferred = $q.defer();
+        that.nacl.crypto_sign_ed25519_pk_to_curve25519(signPk, function(err, boxPk) {
+          if (err) { deferred.reject(err); return;}
+          deferred.resolve(boxPk);
+        });
+        return deferred.promise;
+      };
+
+      /**
+       * Encrypt a message, from a key pair
+       */
+      this.box = function(message, nonce, recipientPk, senderSk) {
+        if (!message) {
+          return $q.reject('No message');
+        }
+        var deferred = $q.defer();
+        var messageBin = that.nacl.from_string(message);
+        if (typeof recipientPk === "string") {
+          recipientPk = that.util.decode_base58(recipientPk);
+        }
+
+        //console.debug('Original message: ' + message);
+
+        that.nacl.crypto_box_easy(messageBin, nonce, recipientPk, senderSk, function(err, ciphertextBin) {
+          if (err) { deferred.reject(err); return;}
+          var ciphertext = that.nacl.to_base64(ciphertextBin);
+          //console.debug('Encrypted message: ' + ciphertext);
+          deferred.resolve(ciphertext);
+        });
+        return deferred.promise;
+      };
 
       /**
        * Decrypt a message, from a key pair
        */
-      box_open = function(cypherText, nonce, senderPk, recipientSk) {
-        console.log("box_open");
+      this.box_open = function(cypherText, nonce, senderPk, recipientSk) {
         if (!cypherText) {
           return $q.reject('No cypherText');
         }
         var deferred = $q.defer();
 
-        var ciphertextBin = nacl.from_base64(cypherText);
+        var ciphertextBin = that.nacl.from_base64(cypherText);
         if (typeof senderPk === "string") {
-          senderPk = decode_base58(senderPk);
+          senderPk = that.util.decode_base58(senderPk);
         }
 
-        nacl.crypto_box_open_easy(ciphertextBin, nonce, senderPk, recipientSk, function(err, message) {
+        that.nacl.crypto_box_open_easy(ciphertextBin, nonce, senderPk, recipientSk, function(err, message) {
           if (err) { deferred.reject(err); return;}
-          array_to_string(message, function(result) {
+          that.util.array_to_string(message, function(result) {
             //console.debug('Decrypted text: ' + result);
             deferred.resolve(result);
           });
@@ -534,99 +550,84 @@ angular.module('cesium.crypto.services', ['ngResource', 'cesium.device.services'
         return deferred.promise;
       };
 
-      isLoaded = function(){
-        return loadedLib === 3;
-      };
-
-      load = function() {
+      this.load = function() {
         var deferred = $q.defer();
         if (!window.plugins || !window.plugins.MiniSodium) {
-          deferred.reject("Cordova plugin 'MiniSodium' not found");
+          deferred.reject("Cordova plugin 'MiniSodium' not found. Please load Full JS implementation instead.");
         }
         else {
-          nacl = window.plugins.MiniSodium;
+          that.nacl = window.plugins.MiniSodium;
+          var loadedLib = 0;
+          var expectedLoadedLib = 2;
           var checkAllLibLoaded = function() {
             loadedLib++;
-            if (isLoaded()) {
+            if (loadedLib == expectedLoadedLib) {
+              that.loaded = true;
               deferred.resolve();
             }
           };
-          async_load_base58(function(lib) {
-            base58 = lib;
+          that.async_load_base58(function(lib) {
+            that.base58 = lib;
             checkAllLibLoaded();
           });
-          async_load_sha256(function(lib) {
-            sha256 = lib;
+          that.async_load_sha256(function(lib) {
+            that.sha256 = lib;
             checkAllLibLoaded();
           });
-          async_load_nacl_js(function(lib) {
-            nacl_js = lib;
-            checkAllLibLoaded();
-          });
-
+          if (!that.crypto || !that.crypto.getRandomValues) {
+            console.debug('[crypto] Web Crypto API (window.crypto) NOT exists with getRandomValues. Will load nacl_factory');
+            expectedLoadedLib++;
+            that.async_load_nacl_js(function(lib) {
+              that.nacl_js = lib;
+              checkAllLibLoaded();
+            });
+          }
         }
 
         return deferred.promise;
       };
 
-      // Service's exposed methods
-      return {
-        isLoaded: isLoaded,
-        load: load,
-        util: {
-          encode_utf8: encode_utf8,
-          decode_utf8: decode_utf8,
-          encode_base58: encode_base58,
-          decode_base58: decode_base58,
-          hash: hash_sha256,
-          encode_base64: function() {return nacl.to_base64(arguments);},
-          random_nonce: random_nonce
+      // Shortcuts
+      this.util.hash = that.util.hash_sha256;
+      this.box = {
+        keypair: {
+          fromSignKeypair: that.box_keypair_from_sign,
+          pkFromSignPk: that.box_pk_from_sign
         },
-        connect: connect,
-        sign: sign,
-        verify: verify,
-        box: {
-          keypair: {
-            fromSignKeypair: box_keypair_from_sign,
-            pkFromSignPk: box_pk_from_sign
-          },
-          pack: box,
-          open: box_open
-        }
+        pack: that.box,
+        open: that.box_open
       };
     }
+    CordovaServiceFactory.prototype = new CryptoAbstractService();
 
-    var service = {
-      isLoaded: function(){return false;}
-    };
+    /* -----------------------------------------------------------------------------------------------------------------
+     * Create service instance
+     * ----------------------------------------------------------------------------------------------------------------*/
 
-    // We use 'Device.ready()' instead of '$ionicPlatform.ready()', because it could be call many times
-    Device.ready()
-      .then(function() {
-        var now = new Date().getTime();
-        var serviceImpl;
-        var serviceImplName;
-
-        // Use cordova implementaion, when possible
-        if (window.plugins && window.plugins.MiniSodium) {
-          serviceImplName = 'MiniSodium';
-          serviceImpl = CordovaServiceFactory();
-        }
-        else {
-          serviceImplName = 'full JS';
-          serviceImpl = FullJSServiceFactory();
-        }
 
-        return serviceImpl.load()
-          .catch(function(err) {
-            console.error(err);
-            throw err;
-          })
-          .then(function() {
-            angular.copy(serviceImpl, service);
-            console.debug('[crypto] Loaded  \'{0}\' implementation in {1}ms'.format(serviceImplName, new Date().getTime() - now));
-          });
-      });
+    // Use cordova implementation, when possible
+    var service;
+    if (Device.enable) {
+      service = new CordovaServiceFactory();
+    }
+    else {
+      service = new FullJSServiceFactory();
+    }
+
+    Device.ready().then(function() {
+      var now = new Date().getTime();
+      // Load (async lib)
+      service.load()
+        .catch(function(err) {
+          //console.error(err);
+          throw err;
+        })
+        .then(function() {
+          console.debug('[crypto] Loaded  \'{0}\' implementation in {1}ms'.format(service.id, new Date().getTime() - now));
+        });
+
+    });
+
 
     return service;
   })
diff --git a/www/js/services/device-services.js b/www/js/services/device-services.js
index 2571d2527f780e6dc7f5e698fc202816ebc81efc..5c3ed73208f08334062c2ee0157735252c783e53 100644
--- a/www/js/services/device-services.js
+++ b/www/js/services/device-services.js
@@ -1,162 +1,156 @@
 
 angular.module('cesium.device.services', ['ngResource', 'cesium.utils.services'])
 
-.factory('Device',
-  function(UIUtils, $translate, $ionicPopup, $q,
-    // removeIf(no-device)
-    $cordovaClipboard, $cordovaBarcodeScanner, $cordovaCamera,
-    // endRemoveIf(no-device)
-    $ionicPlatform
-  ) {
-  'ngInject';
-
-  var CONST = {
-    MAX_HEIGHT: 400,
-    MAX_WIDTH: 400
-  },
-  readyPromise,
-
-  // workaround to quickly no is device or not (even before the ready() event)
-  enable = true;
-  // removeIf(device)
-  enable = false;
-  // endRemoveIf(device)
-
-  // Replace the '$ionicPlatform.ready()', to enable multiple calls
-  ready = function () {
-    if (!readyPromise) {
-      readyPromise = $ionicPlatform.ready().then(function(){
-        console.debug('[ionic] Platform is ready');
-      });
-    }
-    return readyPromise;
-  };
-
-  /*isEnable = function() {
-    return enable;
-  };*/
-
-  getPicture = function(sourceType) {
-    return $q(function (resolve, reject) {
-      if (!enable) {
-        reject("Camera scanner not enable. Please call 'Device.ready()' once before use (e.g in app.js).");
-        return;
-      }
-      if (!sourceType) {
-        $translate(['SYSTEM.PICTURE_CHOOSE_TYPE', 'SYSTEM.BTN_PICTURE_GALLERY', 'SYSTEM.BTN_PICTURE_CAMERA'])
-        .then(function(translations){
-          $ionicPopup.show({
-            title: translations['SYSTEM.PICTURE_CHOOSE_TYPE'],
-            buttons: [
-              {
-                text: translations['SYSTEM.BTN_PICTURE_GALLERY'],
-                type: 'button',
-                onTap: function(e) {
-                  return navigator.camera.PictureSourceType.PHOTOLIBRARY;
-                }
-              },
-              {
-                text: translations['SYSTEM.BTN_PICTURE_CAMERA'],
-                type: 'button button-positive',
-                onTap: function(e) {
-                  return navigator.camera.PictureSourceType.CAMERA;
-                }
-              }
-            ]
-          })
-          .then(function(sourceType){
-            getPicture(sourceType);
-          });
-        });
-      }
-      else {
-        var options = {
-            quality: 50,
-            destinationType: navigator.camera.DestinationType.DATA_URL,
-            sourceType: sourceType,
-            encodingType: navigator.camera.EncodingType.PNG,
-            targetWidth : CONST.MAX_WIDTH,
-            targetHeight : CONST.MAX_HEIGHT
+  .factory('Device',
+    function(UIUtils, $translate, $ionicPopup, $q,
+      // removeIf(no-device)
+      $cordovaClipboard, $cordovaBarcodeScanner, $cordovaCamera,
+      // endRemoveIf(no-device)
+      $ionicPlatform) {
+      'ngInject';
+
+      var
+        CONST = {
+          MAX_HEIGHT: 400,
+          MAX_WIDTH: 400
+        },
+        readyPromise,
+        exports = {
+          // workaround to quickly no is device or not (even before the ready() event)
+          enable: true
         };
-        $cordovaCamera.getPicture(
-          function (imageData) {resolve(imageData);},
-          function(err){reject(err);},
-          options
-        );
+
+      // removeIf(device)
+      // workaround to quickly no is device or not (even before the ready() event)
+      exports.enable = false;
+      console.log("TOTOTOTOT - should have been removed on DEVICE !!!");
+      // endRemoveIf(device)
+
+      // Replace the '$ionicPlatform.ready()', to enable multiple calls
+      function ready() {
+        if (!readyPromise) {
+          readyPromise = $ionicPlatform.ready().then(function(){
+            console.debug('[ionic] Platform is ready');
+          });
+        }
+        return readyPromise;
       }
-    });
-  };
-
-  scan = function (n) {
-    return $q(function(resolve,reject){
-      if (!enable) {
-        reject("Barcode scanner not enable. Please call 'Device.ready()' once before use (e.g in app.js).");
-        return;
+
+      function getPicture(options) {
+        if (!exports.enable) {
+          return $q.reject("Camera not enable. Please call 'Device.ready()' once before use (e.g in app.js).");
+        }
+
+        // Options is the sourceType by default
+        if (options && (typeof options === "string")) {
+          options = {
+            sourceType: options
+          };
+        }
+        options = options || {};
+
+        // Make sure a source type has been given (if not, ask user)
+        if (angular.isUndefined(options.sourceType)) {
+          return $translate(['SYSTEM.PICTURE_CHOOSE_TYPE', 'SYSTEM.BTN_PICTURE_GALLERY', 'SYSTEM.BTN_PICTURE_CAMERA'])
+            .then(function(translations){
+              return $ionicPopup.show({
+                title: translations['SYSTEM.PICTURE_CHOOSE_TYPE'],
+                buttons: [
+                  {
+                    text: translations['SYSTEM.BTN_PICTURE_GALLERY'],
+                    type: 'button',
+                    onTap: function(e) {
+                      return navigator.camera.PictureSourceType.PHOTOLIBRARY;
+                    }
+                  },
+                  {
+                    text: translations['SYSTEM.BTN_PICTURE_CAMERA'],
+                    type: 'button button-positive',
+                    onTap: function(e) {
+                      return navigator.camera.PictureSourceType.CAMERA;
+                    }
+                  }
+                ]
+              })
+              .then(function(sourceType){
+                console.info('[camera] User select sourceType:' + sourceType);
+                options.sourceType = sourceType;
+                return exports.camera.getPicture(options);
+              });
+            });
+        }
+
+        options.quality = options.quality || 50;
+        options.destinationType = options.destinationType || navigator.camera.DestinationType.DATA_URL;
+        options.encodingType = options.encodingType || navigator.camera.EncodingType.PNG;
+        options.targetWidth = options.targetWidth || CONST.MAX_WIDTH;
+        options.targetHeight = options.targetHeight || CONST.MAX_HEIGHT;
+        return $cordovaCamera.getPicture(options);
       }
-      cordova.plugins.barcodeScanner.scan(
-      function(result) {
-        //console.log('bar code result');
-        //console.log(result);
-        if (!result.cancelled) {
-          resolve(result.text); // make sure to convert into String
+
+      function scan(n) {
+        if (!exports.enable) {
+          return $q.reject("Barcode scanner not enable. Please call 'Device.ready()' once before use (e.g in app.js).");
         }
-        else {
-          resolve();
+        var deferred = $q.defer();
+        cordova.plugins.barcodeScanner.scan(
+          function(result) {
+            //console.log('bar code result');
+            //console.log(result);
+            if (!result.cancelled) {
+              deferred.resolve(result.text); // make sure to convert into String
+            }
+            else {
+              deferred.resolve();
+            }
+          },
+          function(err) {deferred.reject(err);},
+          n);
+        return deferred.promise;
+      }
+
+      function copy(text, callback) {
+        if (!exports.enable) {
+          return; // do nothing if not available
         }
-      },
-      function(err) {reject(err);},
-      n);
-    });
-  };
-
-  copy = function (text, callback) {
-    if (!enable) {
-      return; // do nothing if not available
-    }
-    $cordovaClipboard
-      .copy(text)
-      .then(function () {
-        // success
-        if (callback) {
-          callback();
+        $cordovaClipboard
+          .copy(text)
+          .then(function () {
+            // success
+            if (callback) {
+              callback();
+            }
+            else {
+              UIUtils.toast.show('INFO.COPY_TO_CLIPBOARD_DONE');
+            }
+          }, function () {
+            // error
+            UIUtils.alert.error('ERROR.COPY_CLIPBOARD');
+          });
+      }
+
+      // On platform ready: check if device could be used
+      ready().then(function() {
+        var enableCamera = !!navigator.camera;
+
+        exports.enable = enableCamera;
+
+        if (exports.enable){
+          var enableBarcodeScanner = cordova && cordova.plugins && !!cordova.plugins.barcodeScanner;
+          console.debug('[device] Ready with [barcodescanner={0}] [camera={1}]'.format(enableBarcodeScanner, enableCamera));
         }
         else {
-          UIUtils.toast.show('INFO.COPY_TO_CLIPBOARD_DONE');
+          console.debug('[device] No device detected');
         }
-      }, function () {
-        // error
-        UIUtils.alert.error('ERROR.COPY_CLIPBOARD');
       });
-  };
-
-  // On platform ready: check if device could be used
-  ready().then(function() {
-    enable = !!navigator.camera;
-
-    if (enable){
-      var enableBarcodeScanner = cordova && cordova.plugins && cordova.plugins.barcodeScanner;
-      console.debug(' barcodescanner: {0}'.format(enableBarcodeScanner));
-      console.debug(' camera: {0}'.format(enable));
-    }
-    if (!enable) {
-      console.debug('[device] No device detected');
-    }
-    else {
-      console.debug('[device] Ready');
-    }
-  });
-
-  return {
-    ready: ready,
-    enable: enable,
-    clipboard: {
-      copy: copy
-    },
-    camera: {
-      getPicture : getPicture,
-      scan: scan
-    }
-  };
-})
-
-;
+
+      exports.ready = ready;
+      exports.clipboard = {copy: copy};
+      exports.camera = {
+          getPicture : getPicture,
+          scan: scan
+        };
+      return exports;
+    })
+
+  ;
diff --git a/www/js/services/storage-services.js b/www/js/services/storage-services.js
index 5eb5fc16fbba492394fb8e255fa555ec19bc64d9..f62040fc05b491cfd135db424fb03a7fe565a90c 100644
--- a/www/js/services/storage-services.js
+++ b/www/js/services/storage-services.js
@@ -86,8 +86,7 @@ angular.module('cesium.storage.services', ['ngResource', 'cesium.device.services
   });
 
   Device.ready().then(function() {
-    console.log($window.plugins && $window.plugins.SecureStorage);
-    if ($window.plugins) {
+    if (Device.enable) {
       exports.secure.storage = new cordova.plugins.SecureStorage(
         function () {
           console.log('[storage] Secure storage initialized.');
diff --git a/www/lib/ionic/css/image-crop-styles.css b/www/lib/ionic/css/image-crop-styles.css
new file mode 100644
index 0000000000000000000000000000000000000000..2da5e010667905e2e2e5b8f62a25e73a2c8bd9b6
--- /dev/null
+++ b/www/lib/ionic/css/image-crop-styles.css
@@ -0,0 +1,69 @@
+/* Some of these styles you can override, things like colors,
+ * however some styles are required for the structure, and are critical to this module behaving properly!
+ */
+
+/* Core component styles */
+.ng-image-crop {
+  text-align: center;
+  margin: 0 auto;
+  position: relative;
+  -webkit-user-select: none;
+  -moz-user-select: none;
+  -ms-user-select: none;
+  -o-user-select: none;
+  user-select: none;
+}
+/* Each of the 3 steps in the process are contained within sections */
+.ng-image-crop > section {
+  background: #ccc;
+}
+/* The cropping button */
+.ng-image-crop button {
+  margin-top: 10px;
+}
+/* The dashed cropping guideline */
+.ng-image-crop .cropping-guide {
+  display: block;
+  background: rgba(255, 255, 255, .3);
+  border: 2px dashed white;
+  position: absolute;
+  pointer-events: none;
+}
+/* The circular themed cropping guideline */
+.ng-image-crop--circle .cropping-guide {
+  border-radius: 50%;
+  -webkit-border-radius: 50%;
+  -moz-border-radius: 50%;
+  -ms-border-radius: 50%;
+  -o-border-radius: 50%;
+}
+/* The canvas where the user positions the image via dragging and zooming */
+.ng-image-crop .cropping-canvas {
+  background: rgba(255, 255, 255, .3);
+  margin: 0 auto;
+  cursor: move;
+}
+/* The overlayed draggable zoom handle in the corner of the module */
+.ng-image-crop .zoom-handle {
+  display: block;
+  position: absolute;
+  bottom: 1px;
+  left: 1px;
+  background: rgba(255,255,255,0.7);
+  width: 80px;
+  height: 80px;
+  cursor: move;
+  border-radius: 200px 50px;
+}
+/* The text within the zoom handle */
+.ng-image-crop .zoom-handle > span {
+  color: rgba(0, 0, 0, 0.5);
+  -webkit-transform: rotate(-45deg);
+  -moz-transform: rotate(-45deg);
+  -ms-transform: rotate(-45deg);
+  -o-transform: rotate(-45deg);
+  transform: rotate(-45deg);
+  display: block;
+  position: relative;
+  top: 32px;
+}
\ No newline at end of file
diff --git a/www/lib/ionic/js/angular/angular-image-crop.js b/www/lib/ionic/js/angular/angular-image-crop.js
new file mode 100644
index 0000000000000000000000000000000000000000..960d0889db25c869a65bb9ba033e7544dc5557a8
--- /dev/null
+++ b/www/lib/ionic/js/angular/angular-image-crop.js
@@ -0,0 +1,1344 @@
+/**
+ * AngularJS Directive - Image Crop v1.1.0
+ * Copyright (c) 2014 Andy Shora, andyshora@gmail.com, andyshora.com
+ * Licensed under the MPL License [http://www.nihilogic.dk/licenses/mpl-license.txt]
+ */
+(function() {
+
+  /*
+   * DEPENDENCY
+   * Javascript BinaryFile
+   * Copyright (c) 2008 Jacob Seidelin, jseidelin@nihilogic.dk, http://blog.nihilogic.dk/
+   * Licensed under the MPL License [http://www.nihilogic.dk/licenses/mpl-license.txt]
+   */
+
+  var BinaryFile = function(strData, iDataOffset, iDataLength) {
+      var data = strData;
+      var dataOffset = iDataOffset || 0;
+      var dataLength = 0;
+
+      this.getRawData = function() {
+          return data;
+      }
+
+      if (typeof strData == "string") {
+          dataLength = iDataLength || data.length;
+
+          this.getByteAt = function(iOffset) {
+              return data.charCodeAt(iOffset + dataOffset) & 0xFF;
+          }
+
+          this.getBytesAt = function(iOffset, iLength) {
+              var aBytes = [];
+
+              for (var i = 0; i < iLength; i++) {
+                  aBytes[i] = data.charCodeAt((iOffset + i) + dataOffset) & 0xFF
+              }
+              ;
+
+              return aBytes;
+          }
+      } else if (typeof strData == "unknown") {
+          dataLength = iDataLength || IEBinary_getLength(data);
+
+          this.getByteAt = function(iOffset) {
+              return IEBinary_getByteAt(data, iOffset + dataOffset);
+          }
+
+          this.getBytesAt = function(iOffset, iLength) {
+              return new VBArray(IEBinary_getBytesAt(data, iOffset + dataOffset, iLength)).toArray();
+          }
+      }
+
+      this.getLength = function() {
+          return dataLength;
+      }
+
+      this.getSByteAt = function(iOffset) {
+          var iByte = this.getByteAt(iOffset);
+          if (iByte > 127)
+              return iByte - 256;
+          else
+              return iByte;
+      }
+
+      this.getShortAt = function(iOffset, bBigEndian) {
+          var iShort = bBigEndian ?
+                  (this.getByteAt(iOffset) << 8) + this.getByteAt(iOffset + 1)
+                  : (this.getByteAt(iOffset + 1) << 8) + this.getByteAt(iOffset)
+          if (iShort < 0)
+              iShort += 65536;
+          return iShort;
+      }
+      this.getSShortAt = function(iOffset, bBigEndian) {
+          var iUShort = this.getShortAt(iOffset, bBigEndian);
+          if (iUShort > 32767)
+              return iUShort - 65536;
+          else
+              return iUShort;
+      }
+      this.getLongAt = function(iOffset, bBigEndian) {
+          var iByte1 = this.getByteAt(iOffset),
+                  iByte2 = this.getByteAt(iOffset + 1),
+                  iByte3 = this.getByteAt(iOffset + 2),
+                  iByte4 = this.getByteAt(iOffset + 3);
+
+          var iLong = bBigEndian ?
+                  (((((iByte1 << 8) + iByte2) << 8) + iByte3) << 8) + iByte4
+                  : (((((iByte4 << 8) + iByte3) << 8) + iByte2) << 8) + iByte1;
+          if (iLong < 0)
+              iLong += 4294967296;
+          return iLong;
+      }
+      this.getSLongAt = function(iOffset, bBigEndian) {
+          var iULong = this.getLongAt(iOffset, bBigEndian);
+          if (iULong > 2147483647)
+              return iULong - 4294967296;
+          else
+              return iULong;
+      }
+
+      this.getStringAt = function(iOffset, iLength) {
+          var aStr = [];
+
+          var aBytes = this.getBytesAt(iOffset, iLength);
+          for (var j = 0; j < iLength; j++) {
+              aStr[j] = String.fromCharCode(aBytes[j]);
+          }
+          return aStr.join("");
+      }
+
+      this.getCharAt = function(iOffset) {
+          return String.fromCharCode(this.getByteAt(iOffset));
+      }
+      this.toBase64 = function() {
+          return window.btoa(data);
+      }
+      this.fromBase64 = function(strBase64) {
+          data = window.atob(strBase64);
+      }
+  };
+  /*
+   * DEPENDENCY
+   * Javascript EXIF Reader 0.1.6
+   * Copyright (c) 2008 Jacob Seidelin, jseidelin@nihilogic.dk, http://blog.nihilogic.dk/
+   * Licensed under the MPL License [http://www.nihilogic.dk/licenses/mpl-license.txt]
+   */
+  var EXIF = (function() {
+
+      var debug = false;
+
+      var ExifTags = {
+
+          // version tags
+          0x9000: "ExifVersion", // EXIF version
+          0xA000: "FlashpixVersion", // Flashpix format version
+
+          // colorspace tags
+          0xA001: "ColorSpace", // Color space information tag
+
+          // image configuration
+          0xA002: "PixelXDimension", // Valid width of meaningful image
+          0xA003: "PixelYDimension", // Valid height of meaningful image
+          0x9101: "ComponentsConfiguration", // Information about channels
+          0x9102: "CompressedBitsPerPixel", // Compressed bits per pixel
+
+          // user information
+          0x927C: "MakerNote", // Any desired information written by the manufacturer
+          0x9286: "UserComment", // Comments by user
+
+          // related file
+          0xA004: "RelatedSoundFile", // Name of related sound file
+
+          // date and time
+          0x9003: "DateTimeOriginal", // Date and time when the original image was generated
+          0x9004: "DateTimeDigitized", // Date and time when the image was stored digitally
+          0x9290: "SubsecTime", // Fractions of seconds for DateTime
+          0x9291: "SubsecTimeOriginal", // Fractions of seconds for DateTimeOriginal
+          0x9292: "SubsecTimeDigitized", // Fractions of seconds for DateTimeDigitized
+
+          // picture-taking conditions
+          0x829A: "ExposureTime", // Exposure time (in seconds)
+          0x829D: "FNumber", // F number
+          0x8822: "ExposureProgram", // Exposure program
+          0x8824: "SpectralSensitivity", // Spectral sensitivity
+          0x8827: "ISOSpeedRatings", // ISO speed rating
+          0x8828: "OECF", // Optoelectric conversion factor
+          0x9201: "ShutterSpeedValue", // Shutter speed
+          0x9202: "ApertureValue", // Lens aperture
+          0x9203: "BrightnessValue", // Value of brightness
+          0x9204: "ExposureBias", // Exposure bias
+          0x9205: "MaxApertureValue", // Smallest F number of lens
+          0x9206: "SubjectDistance", // Distance to subject in meters
+          0x9207: "MeteringMode", // Metering mode
+          0x9208: "LightSource", // Kind of light source
+          0x9209: "Flash", // Flash status
+          0x9214: "SubjectArea", // Location and area of main subject
+          0x920A: "FocalLength", // Focal length of the lens in mm
+          0xA20B: "FlashEnergy", // Strobe energy in BCPS
+          0xA20C: "SpatialFrequencyResponse", //
+          0xA20E: "FocalPlaneXResolution", // Number of pixels in width direction per FocalPlaneResolutionUnit
+          0xA20F: "FocalPlaneYResolution", // Number of pixels in height direction per FocalPlaneResolutionUnit
+          0xA210: "FocalPlaneResolutionUnit", // Unit for measuring FocalPlaneXResolution and FocalPlaneYResolution
+          0xA214: "SubjectLocation", // Location of subject in image
+          0xA215: "ExposureIndex", // Exposure index selected on camera
+          0xA217: "SensingMethod", // Image sensor type
+          0xA300: "FileSource", // Image source (3 == DSC)
+          0xA301: "SceneType", // Scene type (1 == directly photographed)
+          0xA302: "CFAPattern", // Color filter array geometric pattern
+          0xA401: "CustomRendered", // Special processing
+          0xA402: "ExposureMode", // Exposure mode
+          0xA403: "WhiteBalance", // 1 = auto white balance, 2 = manual
+          0xA404: "DigitalZoomRation", // Digital zoom ratio
+          0xA405: "FocalLengthIn35mmFilm", // Equivalent foacl length assuming 35mm film camera (in mm)
+          0xA406: "SceneCaptureType", // Type of scene
+          0xA407: "GainControl", // Degree of overall image gain adjustment
+          0xA408: "Contrast", // Direction of contrast processing applied by camera
+          0xA409: "Saturation", // Direction of saturation processing applied by camera
+          0xA40A: "Sharpness", // Direction of sharpness processing applied by camera
+          0xA40B: "DeviceSettingDescription", //
+          0xA40C: "SubjectDistanceRange", // Distance to subject
+
+          // other tags
+          0xA005: "InteroperabilityIFDPointer",
+          0xA420: "ImageUniqueID"   // Identifier assigned uniquely to each image
+      };
+
+      var TiffTags = {
+          0x0100: "ImageWidth",
+          0x0101: "ImageHeight",
+          0x8769: "ExifIFDPointer",
+          0x8825: "GPSInfoIFDPointer",
+          0xA005: "InteroperabilityIFDPointer",
+          0x0102: "BitsPerSample",
+          0x0103: "Compression",
+          0x0106: "PhotometricInterpretation",
+          0x0112: "Orientation",
+          0x0115: "SamplesPerPixel",
+          0x011C: "PlanarConfiguration",
+          0x0212: "YCbCrSubSampling",
+          0x0213: "YCbCrPositioning",
+          0x011A: "XResolution",
+          0x011B: "YResolution",
+          0x0128: "ResolutionUnit",
+          0x0111: "StripOffsets",
+          0x0116: "RowsPerStrip",
+          0x0117: "StripByteCounts",
+          0x0201: "JPEGInterchangeFormat",
+          0x0202: "JPEGInterchangeFormatLength",
+          0x012D: "TransferFunction",
+          0x013E: "WhitePoint",
+          0x013F: "PrimaryChromaticities",
+          0x0211: "YCbCrCoefficients",
+          0x0214: "ReferenceBlackWhite",
+          0x0132: "DateTime",
+          0x010E: "ImageDescription",
+          0x010F: "Make",
+          0x0110: "Model",
+          0x0131: "Software",
+          0x013B: "Artist",
+          0x8298: "Copyright"
+      };
+
+      var GPSTags = {
+          0x0000: "GPSVersionID",
+          0x0001: "GPSLatitudeRef",
+          0x0002: "GPSLatitude",
+          0x0003: "GPSLongitudeRef",
+          0x0004: "GPSLongitude",
+          0x0005: "GPSAltitudeRef",
+          0x0006: "GPSAltitude",
+          0x0007: "GPSTimeStamp",
+          0x0008: "GPSSatellites",
+          0x0009: "GPSStatus",
+          0x000A: "GPSMeasureMode",
+          0x000B: "GPSDOP",
+          0x000C: "GPSSpeedRef",
+          0x000D: "GPSSpeed",
+          0x000E: "GPSTrackRef",
+          0x000F: "GPSTrack",
+          0x0010: "GPSImgDirectionRef",
+          0x0011: "GPSImgDirection",
+          0x0012: "GPSMapDatum",
+          0x0013: "GPSDestLatitudeRef",
+          0x0014: "GPSDestLatitude",
+          0x0015: "GPSDestLongitudeRef",
+          0x0016: "GPSDestLongitude",
+          0x0017: "GPSDestBearingRef",
+          0x0018: "GPSDestBearing",
+          0x0019: "GPSDestDistanceRef",
+          0x001A: "GPSDestDistance",
+          0x001B: "GPSProcessingMethod",
+          0x001C: "GPSAreaInformation",
+          0x001D: "GPSDateStamp",
+          0x001E: "GPSDifferential"
+      };
+
+      var StringValues = {
+          ExposureProgram: {
+              0: "Not defined",
+              1: "Manual",
+              2: "Normal program",
+              3: "Aperture priority",
+              4: "Shutter priority",
+              5: "Creative program",
+              6: "Action program",
+              7: "Portrait mode",
+              8: "Landscape mode"
+          },
+          MeteringMode: {
+              0: "Unknown",
+              1: "Average",
+              2: "CenterWeightedAverage",
+              3: "Spot",
+              4: "MultiSpot",
+              5: "Pattern",
+              6: "Partial",
+              255: "Other"
+          },
+          LightSource: {
+              0: "Unknown",
+              1: "Daylight",
+              2: "Fluorescent",
+              3: "Tungsten (incandescent light)",
+              4: "Flash",
+              9: "Fine weather",
+              10: "Cloudy weather",
+              11: "Shade",
+              12: "Daylight fluorescent (D 5700 - 7100K)",
+              13: "Day white fluorescent (N 4600 - 5400K)",
+              14: "Cool white fluorescent (W 3900 - 4500K)",
+              15: "White fluorescent (WW 3200 - 3700K)",
+              17: "Standard light A",
+              18: "Standard light B",
+              19: "Standard light C",
+              20: "D55",
+              21: "D65",
+              22: "D75",
+              23: "D50",
+              24: "ISO studio tungsten",
+              255: "Other"
+          },
+          Flash: {
+              0x0000: "Flash did not fire",
+              0x0001: "Flash fired",
+              0x0005: "Strobe return light not detected",
+              0x0007: "Strobe return light detected",
+              0x0009: "Flash fired, compulsory flash mode",
+              0x000D: "Flash fired, compulsory flash mode, return light not detected",
+              0x000F: "Flash fired, compulsory flash mode, return light detected",
+              0x0010: "Flash did not fire, compulsory flash mode",
+              0x0018: "Flash did not fire, auto mode",
+              0x0019: "Flash fired, auto mode",
+              0x001D: "Flash fired, auto mode, return light not detected",
+              0x001F: "Flash fired, auto mode, return light detected",
+              0x0020: "No flash function",
+              0x0041: "Flash fired, red-eye reduction mode",
+              0x0045: "Flash fired, red-eye reduction mode, return light not detected",
+              0x0047: "Flash fired, red-eye reduction mode, return light detected",
+              0x0049: "Flash fired, compulsory flash mode, red-eye reduction mode",
+              0x004D: "Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected",
+              0x004F: "Flash fired, compulsory flash mode, red-eye reduction mode, return light detected",
+              0x0059: "Flash fired, auto mode, red-eye reduction mode",
+              0x005D: "Flash fired, auto mode, return light not detected, red-eye reduction mode",
+              0x005F: "Flash fired, auto mode, return light detected, red-eye reduction mode"
+          },
+          SensingMethod: {
+              1: "Not defined",
+              2: "One-chip color area sensor",
+              3: "Two-chip color area sensor",
+              4: "Three-chip color area sensor",
+              5: "Color sequential area sensor",
+              7: "Trilinear sensor",
+              8: "Color sequential linear sensor"
+          },
+          SceneCaptureType: {
+              0: "Standard",
+              1: "Landscape",
+              2: "Portrait",
+              3: "Night scene"
+          },
+          SceneType: {
+              1: "Directly photographed"
+          },
+          CustomRendered: {
+              0: "Normal process",
+              1: "Custom process"
+          },
+          WhiteBalance: {
+              0: "Auto white balance",
+              1: "Manual white balance"
+          },
+          GainControl: {
+              0: "None",
+              1: "Low gain up",
+              2: "High gain up",
+              3: "Low gain down",
+              4: "High gain down"
+          },
+          Contrast: {
+              0: "Normal",
+              1: "Soft",
+              2: "Hard"
+          },
+          Saturation: {
+              0: "Normal",
+              1: "Low saturation",
+              2: "High saturation"
+          },
+          Sharpness: {
+              0: "Normal",
+              1: "Soft",
+              2: "Hard"
+          },
+          SubjectDistanceRange: {
+              0: "Unknown",
+              1: "Macro",
+              2: "Close view",
+              3: "Distant view"
+          },
+          FileSource: {
+              3: "DSC"
+          },
+          Components: {
+              0: "",
+              1: "Y",
+              2: "Cb",
+              3: "Cr",
+              4: "R",
+              5: "G",
+              6: "B"
+          }
+      };
+
+      function addEvent(element, event, handler) {
+          if (element.addEventListener) {
+              element.addEventListener(event, handler, false);
+          } else if (element.attachEvent) {
+              element.attachEvent("on" + event, handler);
+          }
+      }
+
+      function imageHasData(img) {
+          return !!(img.exifdata);
+      }
+
+      function getImageData(img, callback) {
+          BinaryAjax(img.src, function(http) {
+              var data = findEXIFinJPEG(http.binaryResponse);
+              img.exifdata = data || {};
+              if (callback) {
+                  callback.call(img)
+              }
+          });
+      }
+
+      function findEXIFinJPEG(file) {
+          if (file.getByteAt(0) != 0xFF || file.getByteAt(1) != 0xD8) {
+              return false; // not a valid jpeg
+          }
+
+          var offset = 2,
+                  length = file.getLength(),
+                  marker;
+
+          while (offset < length) {
+              if (file.getByteAt(offset) != 0xFF) {
+                  if (debug)
+                      console.log("Not a valid marker at offset " + offset + ", found: " + file.getByteAt(offset));
+                  return false; // not a valid marker, something is wrong
+              }
+
+              marker = file.getByteAt(offset + 1);
+
+              // we could implement handling for other markers here,
+              // but we're only looking for 0xFFE1 for EXIF data
+
+              if (marker == 22400) {
+                  if (debug)
+                      console.log("Found 0xFFE1 marker");
+
+                  return readEXIFData(file, offset + 4, file.getShortAt(offset + 2, true) - 2);
+
+                  // offset += 2 + file.getShortAt(offset+2, true);
+
+              } else if (marker == 225) {
+                  // 0xE1 = Application-specific 1 (for EXIF)
+                  if (debug)
+                      console.log("Found 0xFFE1 marker");
+
+                  return readEXIFData(file, offset + 4, file.getShortAt(offset + 2, true) - 2);
+
+              } else {
+                  offset += 2 + file.getShortAt(offset + 2, true);
+              }
+
+          }
+
+      }
+
+
+      function readTags(file, tiffStart, dirStart, strings, bigEnd) {
+          var entries = file.getShortAt(dirStart, bigEnd),
+                  tags = {},
+                  entryOffset, tag,
+                  i;
+
+          for (i = 0; i < entries; i++) {
+              entryOffset = dirStart + i * 12 + 2;
+              tag = strings[file.getShortAt(entryOffset, bigEnd)];
+              if (!tag && debug)
+                  console.log("Unknown tag: " + file.getShortAt(entryOffset, bigEnd));
+              tags[tag] = readTagValue(file, entryOffset, tiffStart, dirStart, bigEnd);
+          }
+          return tags;
+      }
+
+
+      function readTagValue(file, entryOffset, tiffStart, dirStart, bigEnd) {
+          var type = file.getShortAt(entryOffset + 2, bigEnd),
+                  numValues = file.getLongAt(entryOffset + 4, bigEnd),
+                  valueOffset = file.getLongAt(entryOffset + 8, bigEnd) + tiffStart,
+                  offset,
+                  vals, val, n,
+                  numerator, denominator;
+
+          switch (type) {
+              case 1: // byte, 8-bit unsigned int
+              case 7: // undefined, 8-bit byte, value depending on field
+                  if (numValues == 1) {
+                      return file.getByteAt(entryOffset + 8, bigEnd);
+                  } else {
+                      offset = numValues > 4 ? valueOffset : (entryOffset + 8);
+                      vals = [];
+                      for (n = 0; n < numValues; n++) {
+                          vals[n] = file.getByteAt(offset + n);
+                      }
+                      return vals;
+                  }
+
+              case 2: // ascii, 8-bit byte
+                  offset = numValues > 4 ? valueOffset : (entryOffset + 8);
+                  return file.getStringAt(offset, numValues - 1);
+
+              case 3: // short, 16 bit int
+                  if (numValues == 1) {
+                      return file.getShortAt(entryOffset + 8, bigEnd);
+                  } else {
+                      offset = numValues > 2 ? valueOffset : (entryOffset + 8);
+                      vals = [];
+                      for (n = 0; n < numValues; n++) {
+                          vals[n] = file.getShortAt(offset + 2 * n, bigEnd);
+                      }
+                      return vals;
+                  }
+
+              case 4: // long, 32 bit int
+                  if (numValues == 1) {
+                      return file.getLongAt(entryOffset + 8, bigEnd);
+                  } else {
+                      vals = [];
+                      for (var n = 0; n < numValues; n++) {
+                          vals[n] = file.getLongAt(valueOffset + 4 * n, bigEnd);
+                      }
+                      return vals;
+                  }
+
+              case 5: // rational = two long values, first is numerator, second is denominator
+                  if (numValues == 1) {
+                      numerator = file.getLongAt(valueOffset, bigEnd);
+                      denominator = file.getLongAt(valueOffset + 4, bigEnd);
+                      val = new Number(numerator / denominator);
+                      val.numerator = numerator;
+                      val.denominator = denominator;
+                      return val;
+                  } else {
+                      vals = [];
+                      for (n = 0; n < numValues; n++) {
+                          numerator = file.getLongAt(valueOffset + 8 * n, bigEnd);
+                          denominator = file.getLongAt(valueOffset + 4 + 8 * n, bigEnd);
+                          vals[n] = new Number(numerator / denominator);
+                          vals[n].numerator = numerator;
+                          vals[n].denominator = denominator;
+                      }
+                      return vals;
+                  }
+
+              case 9: // slong, 32 bit signed int
+                  if (numValues == 1) {
+                      return file.getSLongAt(entryOffset + 8, bigEnd);
+                  } else {
+                      vals = [];
+                      for (n = 0; n < numValues; n++) {
+                          vals[n] = file.getSLongAt(valueOffset + 4 * n, bigEnd);
+                      }
+                      return vals;
+                  }
+
+              case 10: // signed rational, two slongs, first is numerator, second is denominator
+                  if (numValues == 1) {
+                      return file.getSLongAt(valueOffset, bigEnd) / file.getSLongAt(valueOffset + 4, bigEnd);
+                  } else {
+                      vals = [];
+                      for (n = 0; n < numValues; n++) {
+                          vals[n] = file.getSLongAt(valueOffset + 8 * n, bigEnd) / file.getSLongAt(valueOffset + 4 + 8 * n, bigEnd);
+                      }
+                      return vals;
+                  }
+          }
+      }
+
+
+      function readEXIFData(file, start) {
+          if (file.getStringAt(start, 4) != "Exif") {
+              if (debug)
+                  console.log("Not valid EXIF data! " + file.getStringAt(start, 4));
+              return false;
+          }
+
+          var bigEnd,
+                  tags, tag,
+                  exifData, gpsData,
+                  tiffOffset = start + 6;
+
+          // test for TIFF validity and endianness
+          if (file.getShortAt(tiffOffset) == 0x4949) {
+              bigEnd = false;
+          } else if (file.getShortAt(tiffOffset) == 0x4D4D) {
+              bigEnd = true;
+          } else {
+              if (debug)
+                  console.log("Not valid TIFF data! (no 0x4949 or 0x4D4D)");
+              return false;
+          }
+
+          if (file.getShortAt(tiffOffset + 2, bigEnd) != 0x002A) {
+              if (debug)
+                  console.log("Not valid TIFF data! (no 0x002A)");
+              return false;
+          }
+
+          if (file.getLongAt(tiffOffset + 4, bigEnd) != 0x00000008) {
+              if (debug)
+                  console.log("Not valid TIFF data! (First offset not 8)", file.getShortAt(tiffOffset + 4, bigEnd));
+              return false;
+          }
+
+          tags = readTags(file, tiffOffset, tiffOffset + 8, TiffTags, bigEnd);
+
+          if (tags.ExifIFDPointer) {
+              exifData = readTags(file, tiffOffset, tiffOffset + tags.ExifIFDPointer, ExifTags, bigEnd);
+              for (tag in exifData) {
+                  switch (tag) {
+                      case "LightSource" :
+                      case "Flash" :
+                      case "MeteringMode" :
+                      case "ExposureProgram" :
+                      case "SensingMethod" :
+                      case "SceneCaptureType" :
+                      case "SceneType" :
+                      case "CustomRendered" :
+                      case "WhiteBalance" :
+                      case "GainControl" :
+                      case "Contrast" :
+                      case "Saturation" :
+                      case "Sharpness" :
+                      case "SubjectDistanceRange" :
+                      case "FileSource" :
+                          exifData[tag] = StringValues[tag][exifData[tag]];
+                          break;
+
+                      case "ExifVersion" :
+                      case "FlashpixVersion" :
+                          exifData[tag] = String.fromCharCode(exifData[tag][0], exifData[tag][1], exifData[tag][2], exifData[tag][3]);
+                          break;
+
+                      case "ComponentsConfiguration" :
+                          exifData[tag] =
+                                  StringValues.Components[exifData[tag][0]]
+                                  + StringValues.Components[exifData[tag][1]]
+                                  + StringValues.Components[exifData[tag][2]]
+                                  + StringValues.Components[exifData[tag][3]];
+                          break;
+                  }
+                  tags[tag] = exifData[tag];
+              }
+          }
+
+          if (tags.GPSInfoIFDPointer) {
+              gpsData = readTags(file, tiffOffset, tiffOffset + tags.GPSInfoIFDPointer, GPSTags, bigEnd);
+              for (tag in gpsData) {
+                  switch (tag) {
+                      case "GPSVersionID" :
+                          gpsData[tag] = gpsData[tag][0]
+                                  + "." + gpsData[tag][1]
+                                  + "." + gpsData[tag][2]
+                                  + "." + gpsData[tag][3];
+                          break;
+                  }
+                  tags[tag] = gpsData[tag];
+              }
+          }
+
+          return tags;
+      }
+
+
+      function getData(img, callback) {
+          if (!img.complete)
+              return false;
+          if (!imageHasData(img)) {
+              getImageData(img, callback);
+          } else {
+              if (callback) {
+                  callback.call(img);
+              }
+          }
+          return true;
+      }
+
+      function getTag(img, tag) {
+          if (!imageHasData(img))
+              return;
+          return img.exifdata[tag];
+      }
+
+      function getAllTags(img) {
+          if (!imageHasData(img))
+              return {};
+          var a,
+                  data = img.exifdata,
+                  tags = {};
+          for (a in data) {
+              if (data.hasOwnProperty(a)) {
+                  tags[a] = data[a];
+              }
+          }
+          return tags;
+      }
+
+      function pretty(img) {
+          if (!imageHasData(img))
+              return "";
+          var a,
+                  data = img.exifdata,
+                  strPretty = "";
+          for (a in data) {
+              if (data.hasOwnProperty(a)) {
+                  if (typeof data[a] == "object") {
+                      if (data[a] instanceof Number) {
+                          strPretty += a + " : " + data[a] + " [" + data[a].numerator + "/" + data[a].denominator + "]\r\n";
+                      } else {
+                          strPretty += a + " : [" + data[a].length + " values]\r\n";
+                      }
+                  } else {
+                      strPretty += a + " : " + data[a] + "\r\n";
+                  }
+              }
+          }
+          return strPretty;
+      }
+
+      function readFromBinaryFile(file) {
+          return findEXIFinJPEG(file);
+      }
+
+
+      return {
+          readFromBinaryFile: readFromBinaryFile,
+          pretty: pretty,
+          getTag: getTag,
+          getAllTags: getAllTags,
+          getData: getData,
+          Tags: ExifTags,
+          TiffTags: TiffTags,
+          GPSTags: GPSTags,
+          StringValues: StringValues
+      };
+
+  })();
+
+  angular.module('ImageCropper',[])
+    .directive('imageCrop', function() {
+
+      return {
+        template: '<div id="image-crop-{{ rand }}" class="ng-image-crop ng-image-crop--{{ shape }}" ng-style="moduleStyles"><section ng-style="sectionStyles" ng-show="step==1"></section><section ng-style="sectionStyles" ng-show="step==2"><canvas class="cropping-canvas" width="{{ canvasWidth }}" height="{{ canvasHeight }}" ng-mousemove="onCanvasMouseMove($event)" ng-mousedown="onCanvasMouseDown($event)"></canvas><div ng-style="croppingGuideStyles" class="cropping-guide"></div><div class="zoom-handle" ng-mousemove="onHandleMouseMove($event)" ng-mousedown="onHandleMouseDown($event)" ng-mouseup="onHandleMouseUp($event)"><span>&larr; zoom &rarr;</span></div></section><section ng-style="sectionStyles" class="image-crop-section-final" ng-show="step==3"><img class="image-crop-final" ng-src="{{ croppedDataUri }}" /></section></div>',
+        replace: true,
+        restrict: 'AE',
+        scope: {
+		  crop: '=',
+          width: '@',
+          height: '@',
+          shape: '@',
+		  src: '=',
+          resultBlob: '=',
+		  result: '=',
+          step: '=',
+          padding: '@',
+		  maxSize: '@'
+        },
+        link: function (scope, element, attributes) {
+		  
+		  var padding = scope.padding ? Number(scope.padding) : 200;
+		  
+          scope.rand = Math.round(Math.random() * 99999);
+          scope.step = scope.step || 1;
+          scope.shape = scope.shape || 'circle';
+          scope.width = parseInt(scope.width, 10) || 300;
+          scope.height = parseInt(scope.height, 10) || 300;
+
+          scope.canvasWidth = scope.width + padding;
+          scope.canvasHeight = scope.height + padding;
+
+          var $elm = element[0];
+
+          var $canvas = $elm.getElementsByClassName('cropping-canvas')[0];
+          var $handle = $elm.getElementsByClassName('zoom-handle')[0];
+          var $finalImg = $elm.getElementsByClassName('image-crop-final')[0];
+          var $img = new Image();
+          var fileReader = new FileReader();
+
+          var maxLeft = 0, minLeft = 0, maxTop = 0, minTop = 0, imgLoaded = false, imgWidth = 0, imgHeight = 0;
+          var currentX = 0, currentY = 0, dragging = false, startX = 0, startY = 0, zooming = false;
+          var newWidth = imgWidth, newHeight = imgHeight;
+          var targetX = 0, targetY = 0;
+          var zoom = 1;
+          var maxZoomGestureLength = 0;
+          var maxZoomedInLevel = 0, maxZoomedOutLevel = 2;
+          var minXPos = 0, maxXPos = (padding/2), minYPos = 0, maxYPos = (padding/2); // for dragging bounds		  
+		  var maxSize = scope.maxSize ? Number(scope.maxSize) : null; //max size of the image in px
+		  
+          var zoomWeight = .6;
+          var ctx = $canvas.getContext('2d');
+          var exif = null;
+          var files = [];
+
+          // ---------- INLINE STYLES ----------- //
+          scope.moduleStyles = {
+            width: (scope.width + padding) + 'px',
+            height: (scope.height + padding) + 'px'
+          };
+
+          scope.sectionStyles = {
+            width: (scope.width + padding) + 'px',
+            height: (scope.height + padding) + 'px'
+          };
+
+          scope.croppingGuideStyles = {
+            width: scope.width + 'px',
+            height: scope.height + 'px',
+            top: (padding/2)+'px',
+            left: (padding/2)+'px'
+          };
+  		  
+		  function handleSize(base64ImageSrc) {
+		  
+			return new Promise(function(resolve, reject) {
+				
+				if(!maxSize) {
+					return resolve(base64ImageSrc);
+				}
+				
+				var img = new Image();
+				img.src = base64ImageSrc;
+				
+				img.onload = function() {
+				
+					var height = img.height;
+					var width = img.width;
+															
+					//if the size is already ok, just return the image
+					if(height <= maxSize && width <= maxSize) {						
+						return resolve(base64ImageSrc);
+					}			 	
+					
+					var ratio = width/height;
+					
+					if(ratio > 1) {
+						width = maxSize;
+						height = maxSize/ratio;
+					}
+					else {
+						width = maxSize*ratio;
+						height = maxSize;
+					}							
+					
+					width = Math.round(width);
+					height = Math.round(height);			 	
+					
+					var canvas = document.createElement("canvas");
+					canvas.width = width;
+					canvas.height = height;			 	
+					
+					var context = canvas.getContext("2d");
+									
+					context.drawImage(img, 0, 0, img.width,    img.height,      // source
+										   0, 0, canvas.width, canvas.height);  // destination	 
+					
+					context.save();
+								
+					var dataUrl = canvas.toDataURL();
+					
+					resolve(dataUrl);
+
+				};
+					
+			});		  
+				
+		  }
+					
+		  function handleEXIF(base64ImageSrc, exif) {
+		  		
+			return new Promise(function(resolve, reject) {
+								
+				var img = new Image();
+				img.src = base64ImageSrc;
+				
+				img.onload = function() {
+				
+					var canvas = document.createElement("canvas");
+					
+					if(exif.Orientation >= 5) {
+						canvas.width = img.height;
+						canvas.height = img.width;
+					} else {
+						canvas.width = img.width;
+						canvas.height = img.height;
+					}
+					
+					var context = canvas.getContext("2d");
+		
+					// change mobile orientation, if required
+					switch(exif.Orientation){
+						case 1:
+							// nothing
+							break;
+						case 2:
+							// horizontal flip
+							context.translate(img.width, 0);
+							context.scale(-1, 1);
+							break;
+						case 3:
+							// 180 rotate left
+							context.translate(img.width, img.height);
+							context.rotate(Math.PI);
+							break;
+						case 4:
+							// vertical flip
+							context.translate(0, img.height);
+							context.scale(1, -1);
+							break;
+						case 5:
+							// vertical flip + 90 rotate right
+							context.rotate(0.5 * Math.PI);
+							context.scale(1, -1);
+							break;
+						case 6:
+							// 90 rotate right
+							context.rotate(0.5 * Math.PI);
+							context.translate(0, -img.height);
+							break;
+						case 7:
+							// horizontal flip + 90 rotate right
+							context.rotate(0.5 * Math.PI);
+							context.translate(img.width, -img.height);
+							context.scale(-1, 1);
+							break;
+						case 8:
+							// 90 rotate left					 		                   
+							context.rotate(-0.5 * Math.PI);
+							context.translate(-img.width, 0);
+							break;
+						default:
+							break;
+					}
+					
+					context.drawImage(img, 0, 0);	
+					context.save();
+					
+					var dataUrl = canvas.toDataURL();
+					
+					resolve(dataUrl);										
+				
+				};
+				
+			});				
+				
+		  }
+		  
+		  function loadImage(base64ImageSrc) {
+		  
+			//get the EXIF information from the image
+            var byteString = atob(base64ImageSrc.split(',')[1]);
+            var binary = new BinaryFile(byteString, 0, byteString.length);
+            exif = EXIF.readFromBinaryFile(binary);		  
+           
+		    //handle image size
+            handleSize(base64ImageSrc).then(function(base64ImageSrc) {
+			
+				//if the image has EXIF orientation..
+				if (exif && exif.Orientation && exif.Orientation > 1) {			
+					return handleEXIF(base64ImageSrc, exif);
+				} 
+				//otherwise, just return the image without any treatment
+				else {
+					return base64ImageSrc;
+				}
+				
+			}).then(function(base64ImageSrc) {
+			
+				$img.src = base64ImageSrc;
+				
+			}).catch(function(error) {							
+				console.log(error);				
+			});    
+			
+		  };
+		  
+          // ---------- EVENT HANDLERS ---------- //
+          fileReader.onload = function(e) {
+          	
+          	loadImage(this.resultBlob);	
+
+          };	  
+
+          $img.onload = function() {
+		  
+			scope.step = 2;
+			scope.$apply();		  
+			
+            ctx.drawImage($img, 0, 0);
+
+            imgWidth = $img.width;
+            imgHeight = $img.height;
+
+            minLeft = (scope.width + padding) - this.width;
+            minTop = (scope.height + padding) - this.height;
+            newWidth = imgWidth;
+            newHeight = imgHeight;
+            
+			if(imgWidth >= imgHeight) {
+				maxZoomedInLevel = ($canvas.height - padding) / imgHeight;
+			} else {
+				maxZoomedInLevel = ($canvas.width - padding) / imgWidth;
+			}		
+
+            maxZoomGestureLength = to2Dp(Math.sqrt(Math.pow($canvas.width, 2) + Math.pow($canvas.height, 2)));
+
+            updateDragBounds();
+			
+			var initialX = Math.round((minXPos + maxXPos)/2);
+			var initialY = Math.round((minYPos + maxYPos)/2);
+						
+			moveImage(initialX, initialY);
+			
+          };
+		  
+          function reset() {
+            files = [];
+            zoom = 1;
+			currentX = 0; 
+			currentY = 0; 
+			dragging = false; 
+			startX = 0; 
+			startY = 0; 
+			zooming = false;
+            ctx.clearRect(0, 0, $canvas.width, $canvas.height);            
+            $img.src = '';
+          }		  
+
+          // ---------- PRIVATE FUNCTIONS ---------- //
+          function moveImage(x, y) {
+			
+			x = x < minXPos ? minXPos : x;
+			x = x > maxXPos ? maxXPos : x;
+			y = y < minYPos ? minYPos : y;
+			y = y > maxYPos ? maxYPos : y;			
+
+            targetX = x;
+            targetY = y;
+			
+            ctx.clearRect(0, 0, $canvas.width, $canvas.height);
+            ctx.drawImage($img, x, y, newWidth, newHeight);
+			
+			return x == minXPos || x == maxXPos || y == minYPos || y == maxYPos;
+          }
+
+          function to2Dp(val) {
+            return Math.round(val * 1000) / 1000;
+          }
+
+          function updateDragBounds() {
+            // $img.width, $canvas.width, zoom
+
+            minXPos = $canvas.width - ($img.width * zoom) - (padding/2);
+            minYPos = $canvas.height - ($img.height * zoom) - (padding/2);
+
+          }
+
+          function zoomImage(val) {
+
+            if (!val) {
+              return;
+            }
+			
+            var proposedZoomLevel = to2Dp(zoom + val);
+			
+            if ((proposedZoomLevel < maxZoomedInLevel) || (proposedZoomLevel > maxZoomedOutLevel)) {
+              // image wont fill whole canvas
+              // or image is too far zoomed in, it's gonna get pretty pixelated!
+              return;
+            }
+
+            zoom = proposedZoomLevel;
+            // console.log('zoom', zoom);
+
+            updateDragBounds();
+
+            newWidth = $img.width * zoom;
+            newHeight = $img.height * zoom;
+
+            var newXPos = currentX * zoom;
+            var newYPos = currentY * zoom;
+
+            // check if we've exposed the gutter
+            if (newXPos < minXPos) {
+              newXPos = minXPos;
+            } else if (newXPos > maxXPos) {
+              newXPos = maxXPos;
+            }
+
+            if (newYPos < minYPos) {
+              newYPos = minYPos;
+            } else if (newYPos > maxYPos) {
+              newYPos = maxYPos;
+            }
+
+            // check if image is still going to fit the bounds of the box
+            ctx.clearRect(0, 0, $canvas.width, $canvas.height);
+            ctx.drawImage($img, newXPos, newYPos, newWidth, newHeight);
+          }
+
+          function calcZoomLevel(diffX, diffY) {
+
+            var hyp = Math.sqrt( Math.pow(diffX, 2) + Math.pow(diffY, 2) );
+            var zoomGestureRatio = to2Dp(hyp / maxZoomGestureLength);
+            var newZoomDiff = to2Dp((maxZoomedOutLevel - maxZoomedInLevel) * zoomGestureRatio * zoomWeight);
+            return diffX > 0 ? -newZoomDiff : newZoomDiff;
+			
+          }
+          
+		  function dataURItoBlob(dataURI) {
+			    var byteString, 
+			        mimestring;
+			
+			    if(dataURI.split(',')[0].indexOf('base64') !== -1 ) {
+			        byteString = atob(dataURI.split(',')[1]);
+			    } else {
+			        byteString = decodeURI(dataURI.split(',')[1]);
+			    }
+			
+			    mimestring = dataURI.split(',')[0].split(':')[1].split(';')[0];
+			
+			    var content = new Array();
+			    for (var i = 0; i < byteString.length; i++) {
+			        content[i] = byteString.charCodeAt(i);
+			    }
+			
+			    return new Blob([new Uint8Array(content)], {type: mimestring});
+		  }       
+
+          // ---------- SCOPE FUNCTIONS ---------- //
+
+		  scope.$watch('src', function(){
+			if(scope.src) {
+				if(scope.step != 3) {
+					if(typeof(scope.src) == 'Blob') {
+						fileReader.readAsDataURL(scope.src);	
+					} else {
+						loadImage(scope.src);
+					}
+				}		
+			} else {
+				scope.step = 1;
+				reset();
+			}
+		  });	
+
+		  scope.$watch('crop',function(){
+			if(scope.crop) {
+				scope.doCrop();
+				scope.crop = false;
+			}
+		  });	
+		  
+          $finalImg.onload = function() {			
+            var tempCanvas = document.createElement('canvas');
+            tempCanvas.width = this.width - padding;
+            tempCanvas.height = this.height - padding;
+            tempCanvas.style.display = 'none';
+
+            var tempCanvasContext = tempCanvas.getContext('2d');
+            tempCanvasContext.drawImage($finalImg, -(padding/2), -(padding/2));
+
+            $elm.getElementsByClassName('image-crop-section-final')[0].appendChild(tempCanvas);
+			
+			var dataUrl = tempCanvas.toDataURL();
+			
+			scope.result = dataUrl;
+            scope.resultBlob = dataURItoBlob(dataUrl);
+            
+            scope.$apply();
+          };
+
+          scope.doCrop = function() {
+            scope.croppedDataUri = $canvas.toDataURL();
+            scope.step = 3;
+          };
+
+          scope.onCanvasMouseUp = function(e) {
+
+            if (!dragging) {
+              return;
+            }
+
+            e.preventDefault();
+            e.stopPropagation(); // if event was on canvas, stop it propagating up
+
+            startX = 0;
+            startY = 0;
+            dragging = false;
+            currentX = targetX;
+            currentY = targetY;
+
+            removeBodyEventListener('mouseup', scope.onCanvasMouseUp);
+            removeBodyEventListener('touchend', scope.onCanvasMouseUp);
+            removeBodyEventListener('mousemove', scope.onCanvasMouseMove);
+            removeBodyEventListener('touchmove', scope.onCanvasMouseMove);
+          };
+
+          $canvas.addEventListener('touchend', scope.onCanvasMouseUp, false);
+
+          scope.onCanvasMouseDown = function(e) {
+            startX = e.type === 'touchstart' ? e.changedTouches[0].clientX : e.clientX;
+            startY = e.type === 'touchstart' ? e.changedTouches[0].clientY : e.clientY;
+            zooming = false;
+            dragging = true;
+
+            addBodyEventListener('mouseup', scope.onCanvasMouseUp);
+            addBodyEventListener('mousemove', scope.onCanvasMouseMove);
+          };
+
+          $canvas.addEventListener('touchstart', scope.onCanvasMouseDown, false);
+
+          function addBodyEventListener(eventName, func) {
+            document.documentElement.addEventListener(eventName, func, false);
+          }
+
+          function removeBodyEventListener(eventName, func) {
+            document.documentElement.removeEventListener(eventName, func);
+          }
+
+          scope.onHandleMouseDown = function(e) {
+
+            e.preventDefault();
+            e.stopPropagation(); // if event was on handle, stop it propagating up
+
+            startX = lastHandleX = (e.type === 'touchstart') ? e.changedTouches[0].clientX : e.clientX;
+            startY = lastHandleY = (e.type === 'touchstart') ? e.changedTouches[0].clientY : e.clientY;
+            dragging = false;
+            zooming = true;
+
+            addBodyEventListener('mouseup', scope.onHandleMouseUp);
+            addBodyEventListener('touchend', scope.onHandleMouseUp);
+            addBodyEventListener('mousemove', scope.onHandleMouseMove);
+            addBodyEventListener('touchmove', scope.onHandleMouseMove);
+			
+          };
+
+          $handle.addEventListener('touchstart', scope.onHandleMouseDown, false);
+
+          scope.onHandleMouseUp = function(e) {
+
+            // this is applied on the whole section so check we're zooming
+            if (!zooming) {
+              return;
+            }
+
+            e.preventDefault();
+            e.stopPropagation(); // if event was on canvas, stop it propagating up
+
+            startX = 0;
+            startY = 0;
+            zooming = false;
+            currentX = targetX;
+            currentY = targetY;
+
+            removeBodyEventListener('mouseup', scope.onHandleMouseUp);
+            removeBodyEventListener('touchend', scope.onHandleMouseUp);
+            removeBodyEventListener('mousemove', scope.onHandleMouseMove);
+            removeBodyEventListener('touchmove', scope.onHandleMouseMove);
+          };
+
+          $handle.addEventListener('touchend', scope.onHandleMouseUp, false);
+
+          scope.onCanvasMouseMove = function(e) {
+
+            e.preventDefault();
+            e.stopPropagation();
+
+            if (!dragging) {
+              return;
+            }
+
+            var diffX = startX - ((e.type === 'touchmove') ? e.changedTouches[0].clientX : e.clientX); // how far mouse has moved in current drag
+            var diffY = startY - ((e.type === 'touchmove') ? e.changedTouches[0].clientY : e.clientY); // how far mouse has moved in current drag
+            /*targetX = currentX - diffX; // desired new X position
+            targetY = currentY - diffY; // desired new X position*/
+
+            moveImage(currentX - diffX, currentY - diffY);
+
+          };
+
+          $canvas.addEventListener('touchmove', scope.onCanvasMouseMove, false);
+
+          var lastHandleX = null, lastHandleY = null;
+
+          scope.onHandleMouseMove = function(e) {
+
+            e.stopPropagation();
+            e.preventDefault();
+
+            // this is applied on the whole section so check we're zooming
+            if (!zooming) {
+              return false;
+            }
+
+            var diffX = lastHandleX - ((e.type === 'touchmove') ? e.changedTouches[0].clientX : e.clientX); // how far mouse has moved in current drag
+            var diffY = lastHandleY - ((e.type === 'touchmove') ? e.changedTouches[0].clientY : e.clientY); // how far mouse has moved in current drag
+
+            lastHandleX = (e.type === 'touchmove') ? e.changedTouches[0].clientX : e.clientX;
+            lastHandleY = (e.type === 'touchmove') ? e.changedTouches[0].clientY : e.clientY;
+
+            var zoomVal = calcZoomLevel(diffX, diffY);			
+            zoomImage(zoomVal);
+
+          };
+
+          $handle.addEventListener('touchmove', scope.onHandleMouseMove, false);	  		 
+		  	  		  
+		  scope.onHandleMouseWheel = function(e){
+			  e.preventDefault();		  
+			  
+			  zoomImage(e.deltaY > 0 ? -0.05 : 0.05);			  
+		  };
+
+		  $canvas.addEventListener('mousewheel', scope.onHandleMouseWheel);
+		  $handle.addEventListener('mousewheel', scope.onHandleMouseWheel);
+
+        }
+      };
+    });
+
+
+})();
\ No newline at end of file
diff --git a/www/plugins/es/i18n/locale-en-GB.json b/www/plugins/es/i18n/locale-en-GB.json
index 42af33dd47fff5d9c06351198c053ad34b8657c7..c153ae0d7f1fe438bc7a6c99647e509e878c770b 100644
--- a/www/plugins/es/i18n/locale-en-GB.json
+++ b/www/plugins/es/i18n/locale-en-GB.json
@@ -272,6 +272,13 @@
     "LOCATION_DIVIDER": "Localisation",
     "SOCIAL_NETWORKS_DIVIDER": "Social networks and web site",
     "TECHNICAL_DIVIDER": "Technical data",
+    "MODAL_AVATAR": {
+      "TITLE": "Avatar",
+      "SELECT_FILE_HELP": "<b>Choose an image file</b>, by clicking on the button below:",
+      "BTN_SELECT_FILE": "Choose an image",
+      "RESIZE_HELP": "<b>Re-crop the image</b> if necessary. A click on the image allows to move it. Click on the area at the bottom left to zoom in.",
+      "RESULT_HELP": "<b>Here is the result</b> as seen on your profile:"
+    },
     "ERROR": {
       "LOAD_PROFILE_FAILED": "Could not load user profile.",
       "SAVE_PROFILE_FAILED": "Saving profile failed",
diff --git a/www/plugins/es/i18n/locale-en.json b/www/plugins/es/i18n/locale-en.json
index 42af33dd47fff5d9c06351198c053ad34b8657c7..c153ae0d7f1fe438bc7a6c99647e509e878c770b 100644
--- a/www/plugins/es/i18n/locale-en.json
+++ b/www/plugins/es/i18n/locale-en.json
@@ -272,6 +272,13 @@
     "LOCATION_DIVIDER": "Localisation",
     "SOCIAL_NETWORKS_DIVIDER": "Social networks and web site",
     "TECHNICAL_DIVIDER": "Technical data",
+    "MODAL_AVATAR": {
+      "TITLE": "Avatar",
+      "SELECT_FILE_HELP": "<b>Choose an image file</b>, by clicking on the button below:",
+      "BTN_SELECT_FILE": "Choose an image",
+      "RESIZE_HELP": "<b>Re-crop the image</b> if necessary. A click on the image allows to move it. Click on the area at the bottom left to zoom in.",
+      "RESULT_HELP": "<b>Here is the result</b> as seen on your profile:"
+    },
     "ERROR": {
       "LOAD_PROFILE_FAILED": "Could not load user profile.",
       "SAVE_PROFILE_FAILED": "Saving profile failed",
diff --git a/www/plugins/es/i18n/locale-fr-FR.json b/www/plugins/es/i18n/locale-fr-FR.json
index 79081a55b34f03e5e401216380226f265eb02f50..74a7011d81caf378337a560c8605901460e21389 100644
--- a/www/plugins/es/i18n/locale-fr-FR.json
+++ b/www/plugins/es/i18n/locale-fr-FR.json
@@ -272,6 +272,13 @@
     "LOCATION_DIVIDER": "Adresse",
     "SOCIAL_NETWORKS_DIVIDER": "Réseaux sociaux, sites web",
     "TECHNICAL_DIVIDER": "Informations techniques",
+    "MODAL_AVATAR": {
+      "TITLE": "Photo de profil",
+      "SELECT_FILE_HELP": "Veuillez <b>choisir un fichier image</b>, en cliquant sur le bouton ci-dessous :",
+      "BTN_SELECT_FILE": "Choisir une photo",
+      "RESIZE_HELP": "<b>Recadrez l'image</b>, si besoin. Un clic maintenu sur l'image permet de la déplacer. Cliquez sur la zone en bas à gauche pour zoomer.",
+      "RESULT_HELP": "<b>Voici le résultat</b> tel que visible sur votre profil :"
+    },
     "ERROR": {
       "LOAD_PROFILE_FAILED": "Erreur de chargement du profil utilisateur.",
       "SAVE_PROFILE_FAILED": "Erreur lors de la sauvegarde",
diff --git a/www/plugins/es/js/controllers/common-controllers.js b/www/plugins/es/js/controllers/common-controllers.js
index 9b0d6112ca6a5e4dc1ed6fe1262b608cc6ac5e20..71cd3aff9180c58392abfdaf0084d90309b3acf9 100644
--- a/www/plugins/es/js/controllers/common-controllers.js
+++ b/www/plugins/es/js/controllers/common-controllers.js
@@ -1,6 +1,6 @@
 angular.module('cesium.es.common.controllers', ['ngResource', 'cesium.es.services'])
 
-  .controller('ESPicturesEditCtrl', ESPicturesEditController)
+ .controller('ESPicturesEditCtrl', ESPicturesEditController)
 
  .controller('ESPicturesEditCtrl', ESPicturesEditController)
 
diff --git a/www/plugins/es/js/controllers/user-controllers.js b/www/plugins/es/js/controllers/user-controllers.js
index 49dd2c76a93b33f9d459158f16ae0b9e01e92324..2054cda0559cbe52e2da56fdd750e03f2eff8254 100644
--- a/www/plugins/es/js/controllers/user-controllers.js
+++ b/www/plugins/es/js/controllers/user-controllers.js
@@ -17,10 +17,11 @@ angular.module('cesium.es.user.controllers', ['cesium.es.services'])
 
  .controller('ProfileCtrl', ProfileController)
 
+ .controller('AvatarModalCtrl', AvatarModalController)
 ;
 
 function ProfileController($scope, $rootScope, $timeout, $state, $focus, $translate, $ionicHistory,
-                           esUser, SocialUtils, UIUtils, esHttp) {
+                           esUser, SocialUtils, UIUtils, esHttp, ModalUtils, Device) {
   'ngInject';
 
   $scope.loading = true;
@@ -52,7 +53,7 @@ function ProfileController($scope, $rootScope, $timeout, $state, $focus, $transl
   });
 
   $scope.$on('$stateChangeStart', function (event, next, nextParams, fromState) {
-    /*if ($scope.dirty && !$scope.saving) {
+    if ($scope.dirty && !$scope.saving) {
 
       // stop the change state action
       event.preventDefault();
@@ -78,7 +79,7 @@ function ProfileController($scope, $rootScope, $timeout, $state, $focus, $transl
             $state.go(next.name, nextParams);
           });
       }
-    }*/
+    }
   });
 
   $scope.load = function(walletData) {
@@ -92,8 +93,6 @@ function ProfileController($scope, $rootScope, $timeout, $state, $focus, $transl
           $scope.existing = true;
           $scope.updateView(walletData, profile);
         }
-        UIUtils.loading.hide();
-        $scope.loading = false;
 
         // removeIf(device)
         $focus('profile-name');
@@ -103,7 +102,6 @@ function ProfileController($scope, $rootScope, $timeout, $state, $focus, $transl
         UIUtils.loading.hide(10);
         if (err && err.ucode == 404) {
           $scope.updateView(walletData, {});
-          $scope.loading = false;
           $scope.existing = false;
         }
         else {
@@ -128,6 +126,13 @@ function ProfileController($scope, $rootScope, $timeout, $state, $focus, $transl
       // Set Ink
       UIUtils.ink({selector: 'ion-list > .item.ink'});
     }, 10);
+
+    // Update loading var
+    // Should be done with a delay, to avoid trigger onFormDataChanged()
+    UIUtils.loading.hide();
+    $timeout(function() {
+      $scope.loading = false;
+    }, 1000);
   };
 
   $scope.onFormDataChanged = function() {
@@ -136,21 +141,8 @@ function ProfileController($scope, $rootScope, $timeout, $state, $focus, $transl
   };
   $scope.$watch('formData', $scope.onFormDataChanged, true);
 
-  $scope.fileChanged = function(event) {
-    return UIUtils.loading.show()
-      .then(function() {
-        var file = event.target.files[0];
-        return UIUtils.image.resizeFile(file, true);
-      })
-      .then(function(imageData) {
-        $scope.avatar = {src: imageData};
-        $scope.dirty = true;
-        return UIUtils.loading.hide(10);
-      })
-      .catch(UIUtils.onError('PROFILE.ERROR.IMAGE_RESIZE_FAILED'));
-    };
-
   $scope.save = function(silent) {
+    console.debug('saving');
     if(!$scope.form.$valid || !$rootScope.walletData) {
       return;
     }
@@ -173,6 +165,7 @@ function ProfileController($scope, $rootScope, $timeout, $state, $focus, $transl
         else {
           delete $scope.walletData.avatar;
         }
+        $scope.walletData.profile = formData;
       }
     };
 
@@ -248,12 +241,78 @@ function ProfileController($scope, $rootScope, $timeout, $state, $focus, $transl
   };
 
   $scope.close = function() {
-    if ($ionicHistory.backView()) {
-      return $ionicHistory.goBack();
+    return $state.go('app.view_wallet');
+  };
+
+  $scope.showAvatarModal = function() {
+    if (Device.enable) {
+      return Device.camera.getPicture()
+        .then(function(imageData) {
+          $scope.avatar = {src: "data:image/png;base64," + imageData};
+          $scope.dirty = true;
+        })
+        .catch(UIUtils.onError('ERROR.TAKE_PICTURE_FAILED'));
     }
     else {
-      return $scope.showHome();
+      return ModalUtils.show('plugins/es/templates/user/modal_edit_avatar.html','AvatarModalCtrl',
+        {})
+        .then(function(imageData) {
+          if (!imageData) return;
+          $scope.avatar = {src: imageData};
+          $scope.dirty = true;
+        });
     }
   };
 }
 
+
+function AvatarModalController($scope) {
+
+  $scope.openFileSelector = function() {
+    var fileInput = angular.element(document.querySelector('.modal-avatar #fileInput'));
+    if (fileInput && fileInput.length > 0) {
+      fileInput[0].click();
+    }
+  };
+
+  $scope.fileChanged = function(e) {
+
+    var files = e.target.files;
+
+    var fileReader = new FileReader();
+    fileReader.readAsDataURL(files[0]);
+
+    fileReader.onload = function(e) {
+      $scope.imgSrc = this.result;
+      $scope.$apply();
+    };
+
+  };
+
+
+  /*$scope.fileChanged = function(event) {
+    return UIUtils.loading.show()
+      .then(function() {
+        var file = event.target.files[0];
+        return UIUtils.image.resizeFile(file, true);
+      })
+      .then(function(imageData) {
+        $scope.avatar = {src: imageData};
+        $scope.dirty = true;
+        return UIUtils.loading.hide(10);
+      })
+      .catch(UIUtils.onError('PROFILE.ERROR.IMAGE_RESIZE_FAILED'));
+  };*/
+
+  $scope.doCrop = function() {
+    $scope.initCrop = true;
+  };
+
+  $scope.clear = function() {
+    $scope.imageCropStep = 1;
+    delete $scope.imgSrc;
+    delete $scope.result;
+    delete $scope.resultBlob;
+  };
+
+}
diff --git a/www/plugins/es/js/services/http-services.js b/www/plugins/es/js/services/http-services.js
index 43f61f71153774cafe19358d6440608ba3946693..c32d58f5949cafaa56a28c3e9972469a38c5d467 100644
--- a/www/plugins/es/js/services/http-services.js
+++ b/www/plugins/es/js/services/http-services.js
@@ -229,7 +229,6 @@ angular.module('cesium.es.http.services', ['ngResource', 'cesium.services', 'ces
     }
 
     that = {
-      copy: copy,
       get: get,
       post: post,
       getUrl : csHttp.getUrl,
diff --git a/www/plugins/es/js/services/message-services.js b/www/plugins/es/js/services/message-services.js
index db8a43bbcd0095d36835a1c7a48c708d85af35a9..819037935d54e7efec99691fd98e9ae8c1725bec 100644
--- a/www/plugins/es/js/services/message-services.js
+++ b/www/plugins/es/js/services/message-services.js
@@ -336,7 +336,8 @@ angular.module('cesium.es.message.services', ['ngResource', 'cesium.services', '
                 message.title = title;
               })
               .catch(function(err){
-                console.warn('[ES] [message] invalid cypher title');
+                console.error(err);
+                console.warn('[ES] [message] may have invalid cypher title');
                 message.valid = false;
               }),
 
@@ -346,7 +347,8 @@ angular.module('cesium.es.message.services', ['ngResource', 'cesium.services', '
                   message.content = content;
                 })
                 .catch(function(err){
-                  console.warn('[ES] [message] invalid cypher content');
+                  console.error(err);
+                  console.warn('[ES] [message] may have invalid cypher content');
                   message.valid = false;
                 })
               );
diff --git a/www/plugins/es/js/services/user-services.js b/www/plugins/es/js/services/user-services.js
index 7939c0a68c4d9dba795e4002e0fcf98026251646..183fb4f777bc1efc610153a0c1b0715762d30d65 100644
--- a/www/plugins/es/js/services/user-services.js
+++ b/www/plugins/es/js/services/user-services.js
@@ -204,6 +204,7 @@ angular.module('cesium.es.user.services', ['cesium.services', 'cesium.es.http.se
       // Waiting to load crypto libs
       if (!CryptoUtils.isLoaded()) {
         console.debug('[ES] [user] Waiting crypto lib loading...');
+        //throw 'stop';
         $timeout(function() {
           onWalletLogin(data, deferred);
         }, 50);
diff --git a/www/plugins/es/templates/user/edit_profile.html b/www/plugins/es/templates/user/edit_profile.html
index e2ccca819ecbcfb47ed9abd15d50eff3463186e6..d8ad9dede6e7b9f30b17f9e87ca7f9cbd9655e88 100644
--- a/www/plugins/es/templates/user/edit_profile.html
+++ b/www/plugins/es/templates/user/edit_profile.html
@@ -15,9 +15,11 @@
         <i class="avatar"
            style="background-image: url('{{avatar.src}}')"
            ng-class="{'avatar-wallet': !loading && !avatar && walletData && !walletData.isMember, 'avatar-member': !loading && !avatar && walletData.isMember}">
-          <button class="button button-positive button-avatar button-large button-clear flat icon ion-camera hidden-no-device" ng-click="openPicturePopup()"></button>
-          <button class="button button-positive button-avatar button-large button-clear flat icon ion-camera hidden-device"
-                  onclick="angular.element(document.querySelector('form #avatarFile'))[0].click();"></button>
+          <button class="button button-positive button-large button-clear flat icon ion-camera visible-xs visible-sm"
+                  style="display: inline-block;"
+                  ng-click="showAvatarModal()"></button>
+          <button class="button button-positive button-large button-clear icon ion-camera hidden-xs hidden-sm"
+                  ng-click="showAvatarModal()"></button>
         </i>
         <div ng-if="!loading">
           <h3 class="light" ng-if="!formData.title && walletData && walletData.isMember">{{walletData.uid}}</h3>
@@ -41,9 +43,6 @@
       <div class="col">
         <form name="profileForm" novalidate="" ng-submit="saveAndClose()">
 
-          <input type="file" id="avatarFile" accept=".png,.jpeg,.jpg" onchange="angular.element(this).scope().fileChanged(event)"
-                 style="visibility:hidden; position:absolute;"/>
-
           <ion-list class="animate-ripple item-text-wrap" ng-init="setForm(profileForm)">
 
             <!-- Public info -->
@@ -134,7 +133,9 @@
               <button class="button button-clear button-dark ink"
                       ng-click="cancel()" type="button" translate>COMMON.BTN_CANCEL
               </button>
-              <button class="button button-assertive ink" type="submit">
+              <button class="button button-calm ink"
+                      ng-class="{'button-assertive': dirty}"
+                      type="submit">
                 {{'COMMON.BTN_SAVE' | translate}}
               </button>
             </div>
diff --git a/www/plugins/es/templates/user/modal_edit_avatar.html b/www/plugins/es/templates/user/modal_edit_avatar.html
new file mode 100644
index 0000000000000000000000000000000000000000..611fe591e1a22382d83e28400240475bcd843802
--- /dev/null
+++ b/www/plugins/es/templates/user/modal_edit_avatar.html
@@ -0,0 +1,87 @@
+<ion-modal-view>
+  <ion-header-bar class="bar-positive">
+    <button class="button button-clear visible-xs visible-sm" ng-click="closeModal()" translate>COMMON.BTN_CANCEL</button>
+    <h1 class="title" translate>PROFILE.MODAL_AVATAR.TITLE</h1>
+    <button class="button button-icon button-clear ion-android-done visible-xs visible-sm" ng-click="closeModal(result)">
+    </button>
+  </ion-header-bar>
+
+  <ion-content class="modal-avatar padding">
+
+
+    <div ng-show="imageCropStep == 1">
+      <p translate>PROFILE.MODAL_AVATAR.SELECT_FILE_HELP</p>
+
+      <!-- Add picture button -->
+      <div class="item card text-center padding ink"
+           ng-click="openFileSelector()">
+        <i class="ion-image stable" style="font-size:150px"></i>
+        <b class="ion-plus gray" style="position:relative; font-size:80px; top:-51px; right: 19px;"></b>
+        <p translate>PROFILE.MODAL_AVATAR.BTN_SELECT_FILE</p>
+      </div>
+
+      <input type="file" name="fileInput" accept=".png,.jpeg,.jpg" id="fileInput" onchange="angular.element(this).scope().fileChanged(event)"
+             accept=".png,.jpeg,.jpg"
+             style="visibility:hidden; position:absolute;"/>
+    </div>
+
+    <div ng-show="imageCropStep == 2">
+      <p translate>PROFILE.MODAL_AVATAR.RESIZE_HELP</p>
+
+      <!-- <image-crop
+             data-height="200" //shape's height
+             data-width="150" //shape's width
+             data-shape="square" //the shape.. square or circle
+             data-step="imageCropStep"//scope variable that will contain the current step of the crop (1. Waiting for source image; 2. Image loaded, waiting for crop; 3. Crop done)
+             src="imgSrc" //scope variable that will be the source image for the crop (may be a Blob or base64 string)
+             data-result-blob="result" //scope variable that will contain the Blob information
+             data-result="resultDataUrl" //scope variable that will contain the image's base64 string representation
+             crop="initCrop" //scope variable that must be set to true when the image is ready to be cropped
+             padding="250" //space, in pixels, rounding the shape
+             max-size="1024" //max of the image, in pixels
+           ></image-crop> -->
+
+      <div class="item card text-center padding ink">
+        <image-crop
+          data-height="150"
+          data-width="150"
+          data-shape="circle"
+          data-step="imageCropStep"
+          src="imgSrc"
+          data-result="result"
+          data-result-blob="resultBlob"
+          crop="initCrop"
+          padding="150"
+          max-size="1024"
+        ></image-crop>
+      </div>
+    </div>
+
+    <div ng-show="imageCropStep == 3">
+      <p translate>PROFILE.MODAL_AVATAR.RESULT_HELP</p>
+
+      <div class="item card padding hero" style="height: 110px;">
+        <div class="content">
+          <img class="avatar" ng-src="{{result}}" style="height: 88px; width: 88px;">
+        </div>
+      </div>
+    </div>
+
+    <!-- buttons bar -->
+    <div class="padding hidden-xs text-right">
+      <button class="button button-clear button-dark ink" ng-click="closeModal()" type="button" translate>
+        COMMON.BTN_CANCEL
+      </button>
+      <button class="button button-calm icon-right ion-chevron-right ink" ng-click="doCrop()" translate
+              ng-disabled="imageCropStep == 1"
+              ng-if="imageCropStep <= 2">
+        COMMON.BTN_NEXT
+      </button>
+      <button class="button button-positive ink" ng-click="closeModal(result)" translate
+              ng-if="imageCropStep == 3">
+        COMMON.BTN_CONTINUE
+      </button>
+    </div>
+
+  </ion-content>
+</ion-modal-view>
diff --git a/www/templates/home/home.html b/www/templates/home/home.html
index 96e8526434de71f54ecbcd82d47ec3f94d8afe52..e85e4e1f99e780b615e5db04b1137fa2f1f96cac 100644
--- a/www/templates/home/home.html
+++ b/www/templates/home/home.html
@@ -33,9 +33,14 @@
       <button type="button"
               class="button button-block button-positive button-raised icon icon-left ion-locked ink-dark"
               ng-click="loginAndGo('app.view_wallet')" ng-show="!login" translate>COMMON.BTN_LOGIN</button>
+
+      <button type="button"
+              class="button button-block button-positive button-raised icon icon-left ion-person ink-dark"
+              ui-sref="app.view_wallet" ng-show="login" translate>MENU.ACCOUNT</button>
+
       <button type="button"
-              class="button button-block button-positive button-raised icon icon-left ion-card ink-dark"
-              ui-sref="app.view_wallet" ng-show="login" translate>HOME.BTN_ACCOUNT</button>
+              class="button button-block button-positive button-raised icon icon-left ion-card ink-dark visible-xs"
+              ui-sref="app.view_wallet_tx" ng-show="login" translate>MENU.TRANSACTIONS</button>
 
       <br class="visible-xs visible-sm"/>
       <div class="text-center no-padding "