How to create custom camera plugin?

Hi leob,

There no harm in both is there ? Does your starter app cover users selecting a picture and cropping as profile pic? Pls what’s the link to your starter app on Gitub?

The starter app is here, but it doesn’t contain image management yet:

However it would be quite easy to add this (I have the code already, in another app).
I’ll have a look tomorrow or the day after.

What’s your idea where you would store the images, localStorage, Amazon S3, something else?

In my client app (not the starter) I used Amazon S3 but maybe that’s a bit overkill for the starter app.
(it makes config/setup a lot more complicated)

HI Leob,

LocalStorage is cool. My emphasis really is on the part where users can pick a picture either an existing one or via the phone camera, crop and use that picture as a an ionic circular profile pix.

will be looking forward to it tomorrow or whenever you have the time. Am sure this would help a whole lot of other ppl.

Okay cool, I’ll do it.

Hi leob,

How is your index.html according to the sample above? Can you please share yours?

Best Regards

The index.html doesn’t contain anything special or exciting related to the above code samples.
Just a bunch of standard javascript includes for Ionic and cordova.js.

What kind of knowledge would you seek to be gaining from the index.html ?

I am trying to understand what method to call from html side.On my working sample, I have take picture and get picture as follows:

  <ion-content ng-controller="ExampleController" padding="true">
        <button class="button button-full button-assertive" ng-click="takePhoto()">
            Take Photo
        </button>
        <button class="button button-full button-assertive" ng-click="choosePhoto()">
            Choose Photo
        </button>
        <img ng-show="imgURI !== undefined" ng-src="{{imgURI}}" style="text-align: center">
    </ion-content>

Hi Leob. When i run I do not see any index.html file in the www directory. Hence the error “Error: ENOENT: no such file or directory, open 'C:\nff\www\index.html’”

on a browser, Ionic Lab and an actual android device. Is there something i am missing?

Yes sure, in the “ng-click” you call methods that are defined in your controller.
And those controller methods then call the service methods which then actually take (or choose) the photo.

So, view --> controller --> service.

Does that help or not?

You see no index.html, what are you running then, how did you create the app?

Hi leob,

So for your sample js, should I only use addImage function in my ng-click?

The addImage function from my sample JS calls the Cordova camera plugin to take or select a photo. So if that’s what you want to do, yes then you call that from your ng-click.

HI Leob,

I followed the instructions on the page.

git clone https://github.com/leob/ionic-quickstarter
mv ionic-quickstarter myapp
cd myapp

Hm that’s weird.

Yes sometimes the process is smooth and sometimes you’d run into errors, the software dependencies can be complex.

What I’ll do is I’ll download and install the starter myself and see if it’s still running okay, it’s a long time ago since I did that myself. Some (global) dependencies may have changed and broken stuff, e.g. the Ionic CLI (installed globally) is at a way higher version now.

Stuff does break in ways you don’t expect.

Also I never tested this out on Windows (which I see you are using). I believe this Ionic stuff (with all of its nodejs/npm dependencies) can be problematic on Windows. Did you hear about “Ionic Box” ? I’ve heard it’s good for setting up a dev environment on Windows.

And did you try to run “ionic serve” with the “-c” command so that it echoes its logging to the console/terminal? Then I think it will diplay some errors that will give you a hint what’s going wrong.

Regarding “index.html” and the location of the templates and so on:

The “html” templates are located in subdirectories of “src/js/app”, they’re not in “www/templates” as the basic starter apps are doing, because I don’t like that (only good for simple apps).

The file/directory structure that I’m using is based on best practices recommended by people like John Papa and is better suited to more complex apps. It’s all explained in a section of the README, please take a few minutes to read that section because then it will all become clear.

Now regarding the “index.html”. First you have to know that for “ionic serve” you need to look under “src”, not under “www”. This is because it’s configured that way in the “ionic.project” file (or it should be, please double-check your ionic.project file).

Then you need to know that initially there is NO index.html file, only an “index-template.html” file. The reason is that there is a gulp process (called “gulp-inject”) which will take this index-template.html file, inject all of the script-dependencies into it, and produce an “index.html” out of that.

If this “gulp-inject” process fails (for instance because the NPM module failed to install), then the index.html file will not be created, which would explain your error.

If you use the “-c” argument with “ionic serve” and look at the terminal then I expect that the cause of the error will become evident.

hi leob,

Whats is the name of your controller to use in ng-controller?

var batchModule = angular.module('starter', ['ionic', 'ngCordova'])

var ImageController = function ($scope, $q, ImageService, FileManager) {
    function addImage() {
        var type = 1;   // 1 = camera, 2 = photo library
        var fileName = 'image -' + Date.now() + '.jpg';
        // Set image options (quality, height/width)
        var imageOpts = getImageOpts(type);
        var targetDir = cordova.file.dataDirectory + '/LottoPictures';  // target directory on the native file system
        var fileUrl = null;       // file URL on the native file system
        //
        // Now execute all the steps of the "pipeline" via Promise chaining:
        //
        ImageService.getPicture(type, imageOpts.pictureQuality, imageOpts.targetSize).then(function (imageUrl) {
            $log.debug("ImageService#getPicture imageUrl = '" + imageUrl + "'");
            return FileManager.downloadFile(imageUrl, targetDir, 'uncropped-' + fileName);
        }).then(function (result) {
            $log.debug("FileManager#downloadFile uncropped result = " + JSON.stringify(result));
            fileUrl = result.nativeURL;
            // image file downloaded to the native file system, clean up the temp files
            ImageService.cleanup();
        }).then(function (ignore) {
            $log.debug("Done");
        }).catch(function (error) {
            $log.debug("Error in addImage: " + JSON.stringify(error));
        });
    }
};

batchModule.factory('ImageService', function ($cordovaCamera, $q, $log) {

    function optionsForType(type, quality, targetSize) {
        var source;
        switch (type) {
            case 0:
                source = Camera.PictureSourceType.CAMERA;
                break;
            case 1:
                source = Camera.PictureSourceType.PHOTOLIBRARY;
                break;
        }
        return {
            quality: quality, // e.g. 75,
            destinationType: Camera.DestinationType.FILE_URI,
            sourceType: Camera.PictureSourceType.CAMERA,
            allowEdit: true, // PASS TRUE HERE TO ALLOW CROPPING AND SO ON
            encodingType: Camera.EncodingType.JPEG,
            popoverOptions: CameraPopoverOptions,
            saveToPhotoAlbum: true, //false,
            targetWidth: 500, // e.g. 500,
            targetHeight: 500, // e.g. 500,
            correctOrientation: true // SEE: simonmacdonald.blogspot.ca/2012/07/change-to-camera-code-in-phonegap-190.html
        };
    }

    var getPicture = function (type, quality, targetSize) {
        return $q(function (resolve, reject) {
            var options = optionsForType(type, quality, targetSize);

            $cordovaCamera.getPicture(options).then(function (imageUrl) {

                $log.debug('ImageService#getPicture, $cordovaCamera.getPicture imageUrl = ' + imageUrl);

                resolve(imageUrl);

            }, function (error) {
                $log.debug('ImageService#getPicture, $cordovaCamera.getPicture error = ' + JSON.stringify(error));

                reject(error);
            });

        });
    };

    var cleanup = function () {
        // Cleanup temp files from the camera's picture taking process. Only needed for Camera.DestinationType.FILE_URI.
        // Returns a promise the result of which is probably ignored.
        return $cordovaCamera.cleanup();
    };

    return {
        getPicture: getPicture,
        cleanup: cleanup
    };
});

//
// https://github.com/apache/cordova-plugin-file
// http://www.raymondcamden.com/2014/08/18/PhoneGapCordova-Example-Getting-File-Metadata-and-an-update-to-the-FAQ
// http://www.html5rocks.com/en/tutorials/file/filesystem/
// http://community.phonegap.com/nitobi/topics/dataurl_to_png
//

batchModule.factory('FileManager', function ($q, $log, $cordovaFile, $cordovaFileTransfer, $cordovaFileOpener2) {

    var downloadFile = function(sourceURI, targetDir, targetFile) {
        var deferred = $q.defer();

        $log.debug("FileManager#downloadFile source (original): '" + sourceURI + "'");
        sourceURI = decodeURI(sourceURI);

        var targetPath = targetDir + targetFile;
        var trustHosts = true;
        var options = {};

        $cordovaFileTransfer.download(sourceURI, targetPath, options, trustHosts).then(
          function(result) {
              deferred.resolve(result);
          }, function(error) {
              $log.debug("error: " + JSON.stringify(error));
              deferred.reject(error);
          }, function (progress) {
              //$timeout(function () {
              //  $scope.downloadProgress = (progress.loaded / progress.total) * 100;
              //})
          });

        return deferred.promise;
    };

    var removeFile = function (baseDir, filePath) {
        $log.debug("FileManager#removeFile baseDir = '" + baseDir + "', filePath = '" + filePath + "'");

        return $cordovaFile.removeFile(baseDir, filePath);
    };

    return {
        downloadFile: downloadFile,
        removeFile: removeFile
    };
});

Regarding the ENOENT, do you have the problem when viewing the app in Ionic View after doing “ionic upload” ? That’s a known issue which is discussed here:

https://github.com/leob/ionic-quickstarter/issues/24

The solution/workaround is also discussed there and documented here:

https://github.com/leob/ionic-quickstarter#a-note-about-ionic-upload-and-the-ionic-view-app

HI Leob, There’s no Index.html file in the www folder when i build. That is the issue. Am on a windows machine.

Yes I understood that. If you’re doing “ionic upload” and using Ionic View then the reason is explained in my previous post. Otherwise (if you’re doing “ionic run” or “ionic build”) then you can have a look at the hints from my earlier post:

How to create custom camera plugin?

If you have console logging enabled then I’m quite sure there will be some error message that explains what is going wrong.

P.S. by the way if you’re struggling to get the CLI and all that stuff working on Windows, maybe this helps:

http://mosalem.blogspot.nl/2015/10/using-taco-to-create-ionic-projects-for_19.html

This is a command line tool called “taco” which Microsoft just released, works with Ionic too, according to the article.

Hey I’ve finished my code containing the image stuff (Cordova camera, cropping, displaying) and uploaded it to my Github repo:

There’s a controller file “userProfile.ctrl.js” (and a template userProfile.html) which demonstrates how to take a picture, crop it and display it (images are stored in local storage, for simplicity).

Now there are a few things you need to know to get it working:

It works only on a device, because it needs Cordova, and camera hardware obviously So, you need to follow the steps in the README (see https://github.com/leob/ionic-quickstarter#gulp-build) to build and install on a device:

gulp build
ionic run

Now this is easy enough but there is one ‘gotcha’. When you do gulp build it will use the configuration settings from config-prod.json (https://github.com/leob/ionic-quickstarter/blob/master/src/js/config/config-prod.json).

But those settings, by default, will cause your app to run with login/auth based on Parse.com. So this will only work if you’ve got Parse.com set up.

If you don’t want this, so if you just want to run with the “mock” (fake) auth service, and still be able to use the image functionality (on a device), then simply change the value of the “devMode” from ‘false’ to “true” in config-prod.json.

So, in src/js/config/config-prod.json you get this:

"devMode": true

instead of “devMode”: false.

Now you can build and run the app on a device and it will just go to the start page or to the login page. If it goes to the login page you just put in a fake email address like a@b.com and for the password put in “password”. You then end up on the home page with 3 tabs.

Now either click on the “gear” icon in the top right corner, or (alternatively) select “User Profile” from the side menu.

You then see the 'User Profile" page where you can enter your (fake) user profile data, e,g. name and so on.

One other thing you can do there (which is what it’s all about now) is upload a profile pic. Click the image placeholder and you should get an ‘action menu’ where you can take or choose a pic, and then you can crop it and see how it gets displayed.