image

По результатам опроса из первого топика продолжаю серию статей об Ionic Framework (далее IF). Сегодня поговорим о работе с камерой устройства и в дополнение рассмотрим работу с localStorage. В качестве основы возьмем приложение из прошлого топика и добавим нужную функциональность.



Добавление ngCordova


Для работы установленных плагинов необходимо добавить модуль ngCordova. Ставится он просто (для начала обновим наши инструменты, ведь прошло много времени с момента публикации первой статьи...):

sudo npm i -g cordova ionic bower
bower install ngCordova

Далее ngCordova подключается в index.html:

<script src="lib/ngCordova/dist/ng-cordova.min.js"></script>

и в зависимости приложения (app.js):

angular.module('starter', ['ionic', 'ngCordova', 'starter.controllers', 'starter.services'])

иначе паровозик не сможет… Желательно также подключить еще пару плагинов для работы с ФС устройства:

ionic plugin add cordova-plugin-file
ionic plugin add cordova-plugin-file-transfer

По идее можно было бы подключить только file-transfer, т.к. вместе с собой он также тянет и file, но иногда такой способ чреват появлением непонятных багов…

Подключение плагина для работы с камерой


Для того чтобы IF смог получить доступ к камере устройства нужно добавить в проект Cordova плагин реализующий этот функционал:

ionic plugin add cordova-plugin-camera

Плагин предоставляет единственный метод:

navigator.camera.getPicture(SuccessCallback, ErrorCallback, Options);

Где SuccessCallback и ErrorCallback это наши функции-обработчики в которых мы вольны писать все что вздумается, а вот объект Options гораздо интереснее для рассмотрения:

Настройки для камеры
  • quality (Number): Качество сохраняемого изображения, в интервале от 0 до 100, где 100 — режим без сжатия. По умолчанию значение 50.
  • destinationType(Number): Формат возвращаемого значения. По умолчанию FILEURI. Настройка определяется типами navigator.camera.DestinationType:
    Camera.DestinationType = {
          DATA_URL : 0,      // Строка в формате base64
          FILE_URI : 1,      // URI файла
          NATIVE_URI : 2     // URI пути зависящий от платформы (assets-library:// для iOS и content:// для Android)
    };
    
  • sourceType (Number): Источник изображения. По умолчанию CAMERA. Настройка определяется типами navigator.camera.PictureSourceType:
    Camera.PictureSourceType = {
          PHOTOLIBRARY : 0,
          CAMERA : 1,
          SAVEDPHOTOALBUM : 2
    };
    
  • allowEdit (Boolean): Включает режим простого редактирования перед передачей в функцию.
  • encodingType (Number): Формат файла. По умолчанию «у вас JPEG». Настройка определяется типами navigator.camera.EncodingType:
    Camera.EncodingType = {
          JPEG : 0,               // JPEG
          PNG : 1                 // PNG
    };
    
  • targetWidth (Number): Ширина. Без комментариев. Для масштабирования. Лучше использовать в связке с targetHeight с коэффициентом.
  • targetHeight (Number): Высота.
  • mediaType (Number): Тип мультимедиа контента. По умолчанию PICTURE. Работает при PictureSourceType равным PHOTOLIBRARY или SAVEDPHOTOALBUM. Настройка определяется типами nagivator.camera.MediaType:
    Camera.MediaType = {
          PICTURE: 0,    // Только изображения.
          VIDEO: 1,      // Только видео (ВСЕГДА ВОЗВРАЩАЕТ FILE_URI)
          ALLMEDIA : 2   // Все типы
    };
    
  • correctOrientation (Boolean): Поворот изображения для компенсации ориентации устройства во время съемки.
  • saveToPhotoAlbum (Boolean): Сохранение в альбом после съемки.
  • popoverOptions: iOS-only параметр определяющий местонахождение popover на iPad. Настройка определяется в CameraPopoverOptions.
  • cameraDirection (Number): Камера для использования (фронтальная или задняя). По умолчанию BACK. Настройка определяется типами navigator.camera.Direction:
    Camera.Direction = {
          BACK : 0,      // Use the back-facing camera
          FRONT : 1      // Use the front-facing camera
    };
    


Вот в принципе и все что можно рассказать…


Создание сервиса для работы с камерой


Давайте для нашего удобства сделаем службу чтобы работа с камерой была комфортной. Для этого предлагаю реализовать фабрику с объектом promise который будет обрабатывать события камеры с помощью наших callback'ов. Откройте файл services.js (в прошлом топике мы уже сделали там фабрику городов) и приведите его к такому виду:

services.js
angular.module('starter.services', [])
.factory('Cities', function() {
    // ...
}).factory('Cam', function($q) {
  return {
    getPic: function(opt) {
      var q = $q.defer();
      navigator.camera.getPicture(function(res) {
        q.resolve(res);
      }, function(err) {
        q.reject(err);
      }, opt);
      return q.promise;
    }
  };
});


Итак, в этой настройке мы, используя Angular'овский $q.defer(), возвращаем promise, которому впоследствии передадим обработчики событий с камеры (SuccessCallback, ErrorCallback) и объект настройки (Options). Не закрывайте файл, мы к нему еще вернемся!

Редактирование шаблонов


Откройте файл tabs.html и добавьте еще один tab. У меня получился такой:

  <ion-tab title="Options" icon-off="ion-gear-a" icon-on="ion-gear-a" href="#/tab/opt">
    <ion-nav-view name="tab-opt"></ion-nav-view>
  </ion-tab>

Теперь в папке с шаблонами создадим еще один шаблон (например tab-opt.html) и сделаем небольшую форму настройки пользователя приложения, например такую:

tab-opt.html
<ion-view view-title="Настройки">
	<ion-content class="padding">
		<div class="list-item">
			<img class="full-image" ng-src="{{user.ava || './img/ionic.png'}}">
			<label class="item item-input">
				<i class="icon ion-person placeholder-icon"></i>
				<input type="text" placeholder="Имя пользователя" ng-model="user.name" ng-keyup="setUser();" />
			</label>
			<label class="item item-input item-select">
				<div class="input-label">
					Город
				</div>
				<select ng-model="user.city" ng-change="setUser();">
					<option ng-repeat="city in cities" ng-value="city.id">{{city.name}}</option>
				</select>
			</label>
			<button class="button button-full button-positive" ng-click="getPic(1);">
				С камеры
			</button>
			<button class="button button-full button-calm" ng-click="getPic(0);">
				Из библиотеки
			</button>
		</div>
	</ion-content>
</ion-view>


Как видно, к полям мы привязали модель пользователя и при изменении значения поля html, в модели будет изменяться соответствующее поле объекта. Так же на события когда клавиша отпущена (кстати, тут можно использовать debounce) и на изменение выбора города, была сделана привязка обработчика setUser, это все мы опишем на следующем шаге. Не терпится запустить и посмотреть? Но мы еще не настроили маршрутизацию, контроллеры и работу с localStorage, так что придется еще немного подождать…

Добавление маршрутов, контроллеров и фабрик


Займемся для начала маршрутами. Откройте app.js и добавьте маршрутизацию для нашего нового таба:

Подсказка
.state('tab.opt', {  
    url: '/opt',
    views: {
      'tab-opt': {
        templateUrl: 'templates/tab-opt.html',
        controller: 'OptCtrl'
      }
    }
})


Отлично, теперь перейдем в controllers.js и добавим нужный контроллер:

OptCtrl
.controller('OptCtrl', function($scope, $ionicPopup, Cities, Cam, LS) {
	$scope.cities = Cities.all();
	$scope.user = LS.getIt('user') || {};
	$scope.showAlert = function(title, text) {
		$ionicPopup.alert({
			title: title,
			template: text
		});
	};
	$scope.setUser = function() {
		LS.setIt('user', $scope.user);
	};
	$scope.getPic = function(source) {
		var opt = {
			sourceType: source
		};
		Cam.getPic(opt).then(function(res) {
			$scope.user.ava = res;
			$scope.setUser();
		}, function(err) {
			$scope.showAlert("Ошибка", err);
		});
	}
})


Опа, что за LS в зависимостях контроллера!? Ничего не бойтесь, это всего лишь метод для работы с localStorage, и настало время его реализовать (services.js):

LS
.factory('LS', function() {
  return {
    getIt: function(item) {
      var it = false;
      try {
        it = JSON.parse(localStorage.getItem(item));
        return it;
      } catch(e) {
        console.err(e);
        return it;
      }
    },
    setIt: function(item, obj) {
      try {
        localStorage.setItem(item, JSON.stringify(obj));
        return true;
      } catch(e) {
        console.err(e);
        return false;
      }
    }
  };
})


И надо бы добавить еще городов, добавьте свой:

{
    id: 555312,
    name: 'Иваново',
    desc: 'Город невест',
    emblem: 'https://upload.wikimedia.org/wikipedia/commons/8/8f/Coat_of_Arms_of_Ivanovo_oblast.png'
}

Вы сейчас наверное думаете «к чему столько телодвижений?». А представьте себе, что пользователь настроил город (автоматически сохранив в localStorage) и при повторном запуске приложения его будет сразу перекидывать на погоду в родном городе, круто же! Финальный штрих, изменим два наших контроллера, написанных в прошлой статье:

CityCtrl, CityDetailCtrl
.controller('CityCtrl', function($scope, Cities, LS) {
	$scope.cities = Cities.all();
	$scope.user = LS.getIt('user') || {};
	if ($scope.user.city) { window.location.href = '#/tab/city/'+$scope.user.city; }
})
.controller('CityDetailCtrl', function($scope, $http, $stateParams, $ionicPopup, $ionicLoading) {
	$scope.data = {};
	$scope.id = $stateParams.id;
	$scope.showAlert = function(title, text) {
		$ionicPopup.alert({
			title: title,
			template: text
		});
	};
	$scope.refresh = function() {
		$ionicLoading.show();
		$http.get('http://api.openweathermap.org/data/2.5/forecast/daily?id='+$scope.id+'&appid=2de143494c0b295cca9337e1e96b00e0')
		.success(function(data, status, headers, config) {
			$scope.data = data;
			$ionicLoading.hide();
			$scope.$broadcast('scroll.refreshComplete');
		})
		.error(function(data, status, headers, config) {
			$ionicLoading.hide();
			$scope.showAlert(status, data);
			$scope.$broadcast('scroll.refreshComplete');
		});
	};
	$scope.refresh();
})


Мы добавили условие, чтобы пользователя перекидывало на настроенный город, а также незаметно указали в зависимостях компонент $ionicLoading для показа окна ожидания во время запроса. Надеюсь ничего не пропустил, все готово, вы знаете что делать (ionic serve --lab, если забыли):

It works!



Заключение


В принципе на текущий момент работать с помощью IF становится всё удобнее и проще, поэтому я смело могу сказать что это один из лучших фреймворков для прототипирования, и даже для production приложений. Дерзайте! В следующей статье хочу описать создание чата с помощью socket.io и работу с ещё кое-чем интересным. Если у вас появились трудности, вопросы или предложения для следующих статей — милости прошу в ЛС!

Комментарии (20)


  1. john_samilin
    17.11.2015 18:15
    -1

    А чем он лучше, скажем, Titanium?


    1. m0sk1t
      17.11.2015 18:17

      Не знаю, так и не удалось запустить Titanium с полгода-год назад…


      1. john_samilin
        17.11.2015 18:20
        -1

        Я вам скажу чем хуже. Прежде всего, он не умеет работать с интентами, сервисами, создавать активити, меню, а так же не знает ничего о системных единицах измерений (dp, dpi, sp)


        1. m0sk1t
          17.11.2015 22:20

          Знайте что я, как и любой другой пользователь хабра, с удовольствием бы прочитал введение в Titanium аналогичное моему по IF. Не затруднит ли вас написать tutorial? Интересно узнать как JS общается с Native элементами!

          P.S. Прочитав тут 30 статей про Titanium я, к сожалению, увидел много боли в постах и комментариях… Например тут


          1. john_samilin
            18.11.2015 10:40
            +1

            Конечно не затруднит, я как раз систематизирую свои знания по нему, однако не могу обнадежить вас по поводу сроков.
            В приведенной ссылке сравнивается большое с холодным, ведь js-фреймворк ни за что не сравнится с нативной разработкой; кроме того, многие отрицательные моменты оттуда уже не актуальны:
            * interface builder есть давно, это alloy, и там есть даже data binding
            * цвет placeholder'ов имеется в 5 релизе
            * память после уничтожения activity освобождается в том же 5 релизе

            мне самому был очень интересен IF еще с первого релиза, и у них есть много классных фич вроде удаленной сборки ipa без мака, ionic push/ionic creator, но вот реально, это все для разработчика хорошо, а надо чтобы было хорошо для клиента


            1. torrie
              18.11.2015 11:00
              +1

              Грустно, что за титаниум надо платить. Сперва платим за титаниум, потом за гугл, потом за ios.
              Плюс ещё надо тратить деньги на обучение сотрудников тому же титаниуму(хоть там и js, но это же не стандартные функции). Чего нет в IF, где Angular един, а IF выступает мобильным «бутстрапом» с кучей примеров, тулзов, фич и коммюнити.


              1. john_samilin
                18.11.2015 11:39
                +1

                не надо если не хотите аналитикой пользоваться. сам SDK бесплатный
                а обучение ангуляру стоит тех же самых временных и, как следствие, финансовых затрат


                1. torrie
                  18.11.2015 13:51
                  +1

                  Angular продвигает сам большой брат.


                  1. john_samilin
                    18.11.2015 14:02

                    Но это же не аргумент. До сих пор я ни в одном гибридном фреймворке, за исключением IF, его не встречал


            1. BIanF
              19.11.2015 06:24
              +1

              А я вот буквально сегодня выбирал между IF и Phonegap + Onsen UI. И мне бы хотелось сравнение этих фреймворков. Как я понял, по факту это форки Кордовы, просто IF реализует ещё некоторые контроллы? Остановился я на Phonegap. Не смог найти инфу про удалённую сборку и WP в IF.


              1. m0sk1t
                19.11.2015 10:10

                Скорее не форки а обертки, ибо можно писать как

                cordova plugin add
                

                так и

                ionic plugin add
                

                Эффект тот же. Просто у IF немного другая архитектура JS проекта, шаблоны и т.д. (Angular же)
                Windows Phone and FirefoxOS support is on our roadmap.

                Т.е. в скором времени можно ожидать)


        1. torrie
          18.11.2015 10:46
          +1

          cordova всё это умеет и даже большее


  1. Sergiy
    17.11.2015 18:16
    +1

    С камерой главное помнить — не надо выводить в консоль base64 фотки, при этом либо нет ошибки и ничего не происходит, либо все зависает насмерть


    1. m0sk1t
      17.11.2015 18:18

      это да, но здесь base64 скорее в качестве примера, нежели боевой код)


  1. Dimd13
    17.11.2015 19:21

    Друг мой, продолжайте писать! Тема IF очень интересна.


    1. m0sk1t
      17.11.2015 22:20

      okay)


  1. Blumfontein
    18.11.2015 08:08
    +1

    Полгода назад пробовали писать приложение на IF. Получилось не очень. Лаги андроида даже с Crosswalk не побороли. На iOS заметно подлагивали переходы между $state (хотя тут мы грешим на частое использование SVG) и не работали пуши в iOS8. На WinPhone и вовсе было слайдшоу. Все-таки Ангуляр тяжеловат для мобил.


    1. m0sk1t
      18.11.2015 09:58

      Лаги и баги, к сожалению — вечные спутники гибридных приложений… Поэтому я и акцентировал внимание на то что IF прекрасно подойдёт для прототипирования)


    1. torrie
      18.11.2015 10:50
      +1

      Последние версии IF на последних мобилках ведут себя очень вкусно. Но стоит пересесть на тот же iphone 4 как всё вдруг становится грустным.
      IF привлекает в первую очередь скоростью и простотой разработки, а также боевым комплектом готового стафа(+поддержка cordova конечно же).
      Я бы предложил вместо IF посмотреть в сторону несколько более сложных фреймворков, которые делают нативный гибрид. Например, nativescript и его аналоги.
      Конечно, на базе таких представителей не сделать приложение с суперкрутым дизайном, но для типовых проектов/прототипов/первых версий пойдет на ура.


      1. john_samilin
        18.11.2015 11:40
        +1

        или AppGyver Steroids