- PVSM.RU - https://www.pvsm.ru -
Что можно успеть за новогодние праздники? Как я смог выяснить, очень много. Даже если у тебя двое детей и куча родни, которую хочется навестить. Не получается только закончить статью для хабра. Она, зараза, растягивается на весь январь.
5ого числа я оказался с ноутбуком и 2мя свободными днями. Прикинув что можно сделать полезного, я решил сделать какое-нибудь мобильное приложение быстрее, чем его создатели. Поскольку 2 дня это не то, чтобы много, нужно было найти небольшое, популярное приложение с разговорчивыми авторами.
В App Store нашлось приложение "Транжира [1]". Это небольшая программа для записи своих трат и доходов. В конце месяца по ним можно сделать вывод почему опять не хватило до зарплаты. На 5ое число оно входило в Топ-10 в разделе «Финансы». На iphones.ru нашлась dev-story [2].
В своей статье ребята пишут: «После сдачи проекта у нашей команды обычно есть 3-4 полусвободных дня (таковы правила компании), и уже через час над приложением нашего дизайнера работали 4 человека. Первым в “бой” ринулся product manager, подсказав несколько ключевых моментов по позиционированию приложения. За ним программисты и даже office manager.». Это меня обнадежило и я стал думать как уложиться в 2 дня.
UPD 1: приложение обновилось во второй половине января. Сравнивая с моим приложением стоит ориентироваться на скриншоты и функционал из dev-story.
UPD 2: иконка «Транжиры» удивительно похожа на иконку платных постов из «4 правил автора» на хабре:
картинка [3].
У меня уже был опыт мобильной разработки [4] на C# и Cocoa. Посколько время тратил личное, хотелось использовать его с максимально пользой. Даже если успеть приложение не получиться, то хотя бы «вкусить» нового фреймворка или языка.
Я работал в DevExpress с 2006 по 2011 года и с тех пор следил за их анонсами. От них до сих пор приходит What's New на мою почту. Соблазнить на покупку что-ли хотят? У них появился мобильный js-фреймворк [5] на базе Cordova/PhoneGap. Писать его начали уже после моего увольнения, поэтому что это за зверь я не знал и было интересно попробовать.
Исследовательская компания Gartner пишет [6], что по состоянию на август 2013 большинство корпоративных мобильных приложений было написано на PhoneGap или продуктах на его основе (вроде Kony [7]). Мой личный, потребительнский опыт подсказывает что это не так. Но вдруг я заблуждаюсь?
С JavaScript и HTML я знаком понаслышке. Верстаю со stackoverflow подмышкой, умею писать простенькие jQuery селекторы. Знаю, где находится её документация. В общем, это огромная дыра в моих знаниях. Очень хотелось если не закрыть её, то хотя бы получить какой-то опыт.
В итоге у меня должно было получиться кросс-платформенное приложение. Это позволило бы мне получить преимущество над разработчиками Транжиры и произвести больше добавленной пользы за доступное время. У меня была всего одна пара рук и использовать её хотелось наиболее эффективно. На одной чаше весов была потенциальная эффективность фреймворка, на другой — отсутствие опыта в js-разработке у меня. Я понадеялся, что фреймворк перевесит и решил попробовать.
Мне удобно писать с VCS, поэтому сейчас я попытаюсь восстановить и описать по часам мой прогресс.
Готовые приложения вы можете скачать здесь:
iOS версия на ревью
Не уверен, что могу выложить в публичный доступ репо. В нем лежат фото, купленные на стоке и сторонние библиотеки каждая со своей лицензией. Написанный мною код, вы можете увидеть ниже, либо на свой риск заглянув внутрь бандла приложения.
+20 минут Ушло на установку node.js и Cordova CLI
+10 минут Скачал шаблон приложения от Cordova. Добавил шаблон из PhoneJS. Создал Git-репозиторий, зарегистрировал его в WebStorm. Добавил запись в httpd.conf, чтобы была возможность отлаживать и проверять работу приложения в браузере.
+38 минут Поменял неймспейс приложения на io.nikitin.ThriftBox. Настроил навигацию. PhoneJS является MVC-фреймворком. Каждый экран приложения представлен набором из html разметки (вью) и функцией-фабрикой вью-модели. В простейшем случае, это
<div data-options="dxView : { name: 'home', title: 'Home' } ">
<div data-options="dxContent : { targetPlaceholder: 'content' } ">
// Содержимое вью
</div>
</div>
плюс
ThriftBox.home = function (params) { // Параметры запроса из uri
return {}; // Объект вью-модели
};
В дальнейшем, одно с другим увязывается с помощью knockout-биндингов [8]. Времени в притык, поэтому оставляю два экрана: ввод трат и отчет трат по месяцам.
+4 часа 20 минут Случился первый «затык». Не получилось быстро сделать разметку цифровых кнопок для ввода трат.
В исходном приложении на главном экране огромная клавиатура, похожая на калькулятор или приложение-звонилку.
Оказалось, что даже «в лоб», с помощью тега table сделать такую клавиатуру не получается. На retina-iphone бордюры в 1 пиксел между кнопками меняли свой цвет после нажатия кнопок. Разница в цвете линий на хорошем экране телефона была очень заметна. Пришлось думать, как победить.
Изучал вопрос и опробовал вариант верстки на div'ах. С ними не получилось добиться, чтобы рамка была ровно 1 пиксел шириной и все кнопки были одинакового размера на экранах разного разрешения. Спустя 3 часа баг был оставлен как есть и я двинулся дальше.
+28 минут Убран индикатор нажатия ссылки на iOS. iOS отрисовывает серый индикатор нажатия вокруг ссылок и объектов с обработчиком onclick. Поскольку у меня был свой индикатор нажатия (кнопка становилась темнее), этот серый индикатор мне был не нужен. Проблема решилась с помощью события dxAction [9]:
было:
<td data-bind="click: function() { buttonPress('1') }">1</td>
стало:
<td data-bind="dxAction: function() { buttonPress('1') }">1</td>
Это событие — расширенная вариация события click: в качестве обработчика оно поддерживает uri навигации между вью и корректно отрабатывает в скроллируемой область.
+14 минут Обработчик buttonPress из предыдущего примера, валидация введенного числа.
var number = ko.observable(null);
var isValidNumber = ko.computed(function() {
return number() && parseFloat(number()) > 0;
});
......
function buttonPress(button) {
if (button) {
if (number())
number(number() + button);
else
number(button);
} else if (number())
number(number().substr(0, number().length - 1));
}
var viewModel = {
number: number,
isValidNumber: isValidNumber,
viewShowing: viewShowing,
buttonPress: buttonPress
};
.....
+8 минут Добавил FastClick.js [10], который убирает задержку перед срабатыванием события click на телефонах. По умолчанию, мобильный браузер откладывает вызов обработчика click, чтобы убедиться, что не будет double-tap. Внешне это проявляется как подтормаживающее приложение. Вы быстро нажимаете кнопками, а они реагируют с ощутимой задержкой на нажатия. Авторы FastClick.js вешаются на touchstart и выстраивают свою логику обработки нажатий.
Забегая вперед скажу, что добавление этой библиотеки было ошибкой. Почему — читайте далее.
+4 минуты Ввел ограничение на длину числа, которое может ввести пользователь. Подкорректировал размер шрифтов, чтобы все отлично выглядело и не вылезало за границы области ввода.
+58 минут Выбор категории траты. Под областью ввода добавил прокручивающуюся область с кнопками доступных категорий. Видео [11].
Заняло меньше, чем могло бы. Внутри PhoneJS нашел подходящий компонент dxTileView [12]. Из коробки получил нужный мне внешний вид и кинетик-прокрутку. С последней пришлось бы помучится, если делать её самому. Особенно здорово, что ребята из DevExpress включили её только для iOS, где она — системная, но выключили для Android, где такой прокрутки нет.
<div class="category" data-bind="dxTileView: {
dataSource: categories,
itemMargin: 0,
baseItemHeight: 72,
baseItemWidth: 72,
listHeight: 72
}">
<div class="tile"
data-options="dxTemplate: { name:'item' }"
data-bind="css: { selected: $parent.category() == $data },
click: function() { $parent.category($data) }">
<div><img data-bind="attr: { src: image }"/></div>
<div data-bind="text: name"></div>
</div>
</div>
Было уже 19:40 и я решил закончить.
+3 часа 9 минут Хранение данных на диске. В PhoneJS входят классы для работы с данными: выборка, фильтрация, сортировка, группировка. Есть несколько вариантов хранения данных: OData и LocalStorage. Делать серверную часть для бесплатного приложения мне совершенно не хотелось, поэтому обратил внимание на LocalStorage. Довольно быстро выяснилось, что хранить в LocalStorage не самая здравая идея. Например, при обновлении на iOS 5.1 терлись пользовательские данные [13], другие жалуются, что LocalStorage трется раз в неделю на Android, третьи, что трется при выключении питания. Я решил не рисковать пользовательскими данными и использовать File API из Cordova/PhoneGap.
Документация [14] утверждает, что это API основано на W3C File API. По факту это означает, что это API — разное в Chrome и Safari на Mac OS, реализации в Cordova для iOS и реализации для Android. Реализации для iOS и Android — разные. Функции себя ведут по разному и разный набор классов и констант. Например, в реализации для Android отсутствует класс Blob и константа window.PERMANENT. Зато есть класс LocalFileSystem с классом LocalFileSystem.PERSISTENT. В браузере на ноутбуке есть дополнительное API для запроса квоты на хранение данных, которое отсутствует на мобильных телефонах.
Дополнительных проблем создает доступная в сети документация по этому API. Я читал несколько статей, которые я смог найти по запросу html5 file api. Мне ни разу не встретилась статья, которая дала бы мне ответы на все мои вопросы. В итоге, методом проб и ошибок, родился класс для работы с File API, поддерживающий Cordova 3.3 на iOS и Android, Chrome 32 на Mac OS и Windows 8. Вы можете найти его здесь: https://github.com/chebum/fileStorage-for-Phone.JS/blob/master/fileStorage.js [15]
Использовать его можно так:
// В этом примере создастся файл data/records в папке Documents приложения.
FS.initFileAPI(1000000, true)
.then(function () {
var records = new FS.FileArrayStore({ key: "id", fileName: "records" });
return records.insert({ customer: "Петя" })
})
.then(function () {
alert("Запись добавлена!");
});
// Или использовать низкоуровневое API:
FS.initFileAPI(100000, true)
.then(function() {
return FS.writeFile("file1", "Содержимое файла")
})
.then(function() {
alert("Файл записан!");
});
+33 минуты Сохранение введенных записей в сторедж. Список категорий вынесен в ArrayStore [16], чтобы упростить выборки.
+26 минут Собственный layout для вьюшек приложения. PhoneJS предлагает набор Layout [17], которые являются своего рода обрамлением для вью, которые они содержат. Поскольку в моем приложении главная страница не укладывалась ни в один из доступных layout'ов, я выбрал EmptyLayout. У него оказался минус: отсутствовала анимация переходов между экранами приложения. Я скопировал код EmptyLayout и вставил аттрибут, добавляющий анимацию при переключении экранов.
+1 час 51 минута Экран About из шаблона приложения переделан в экран отчетов, пока пустой. Сделана viewModel с выборкой данных текущего месяца. Добавлено локализованное форматированные даты для заголовка экрана.
+59 минут Группировка и вывод трат по группам в текущем месяце.
+28 минут Открытие списка выбора месяца для отчета по нажатию на заголовок экрана.
+1 час 20 минут Добавил Cordova-плугин Statusbar, который отказался работать «из коробки». Выяснилось, что в шаблоне приложения от PhoneJS по какой-то причине была закомментирована ссылка на cordova.js:
<!--<script type="text/javascript" src="cordova.js"></script>-->
В результате нативная часть приложения работала некорректно.
+39 минут На экране с отчетами верхняя часть заменена на dxToolbar [18].
+22 минут Выяснял почему не работает обработчик нажатия dxButton. Убирание FastClick.js решило проблему, но появилась ощутимая задержка при нажатии кнопок клавиатуры. Подписка на dxAction переделана на touchstart.
+25 минут Форматирование вывода строк в отчете (были косяки).
Всю ночь мне снились дрянные кнопки на главном экране приложения.
У меня был ранний рейс в Будапешт из Внуково, поэтому что не успел днем доделывал ночью в аэропорту. Спать, сидя на стуле в кафе неудобно, а программировать — вполне.
+2 часа 5 минут Утром, еще дома, решил разделить кнопки, чтобы избавиться от границ между ними. В качестве образца взял клавиатуру набора номера из iOS7.
Клавиатуры получилось три. В зависимости от габаритов экрана меняется размер кнопок: для 3.5'', 4'' и 5'' телефонов. В каждую ячейку таблицы был положен div и настроено выравнивание.
Из-за отсутствия недоделаного выравнивание текста по вертикали в HTML, получился достаточно сложный css-стиль для кнопок:
.home-view .buttons td div {
color: #4a5360;
border: 1px solid #4a5360;
text-align: center;
position: absolute;
left: 50%;
/* Small buttons - default */
font-size: 26px;
padding: 13px 0 13px 0;
width: 52px;
line-height: 26px;
border-radius: 26px;
margin-left: -27px;
margin-top: -27px;
}
+1 час 50 минут Купил на Fotolia набор векторных иконок. Вырезал нужные и конвертировал в PNG формат. Возможно, так долго получилось из-за того, что было пол второго ночи:)
+1 час 10 минут Сплеш-скрин загрузки приложения
+36 минут Иконка приложения в 3х размерах. Локализация названия для iOS.
+20 минут Скрывание сплеш-скрина после полной загрузки приложения.
+2 часа Фикс многочисленных багов
+2 часа Экраны для Play Store
+30 минут Экраны для App Store
+30 минут Описание приложения для 2х магазинов
+1 час 30 минут Сабмит в App Store — возникли непонятные проблемы с подписью приложения.
Сложим потраченное время и разобъем его по категориям.
Разработка: 21 час 37 минут
Графика и тексты: 8 часов 26 минут
Всего: 30 часов 3 минуты
В итоге за это время получилось минимально работающее приложение, которое сильно уступает последней версии «Транжиры». Я не успел разбиение трат по дням, ввод доходов. В интерфейсе программы срезано несколько «углов».
Если проанализировать их трудозатраты, то получается следующее. В своей статье [2] они пишут про 3-4 дня 4 человек. Это 96-128 человеко-часов. У меня получилось чуть больше 30 часов и приложение под 3 платформы. iOS и Android уже в магазинах. Windows 8 потребует переделки интерфейса: текущий очень чужероден.
Можно гордиться собой.
Автор: ivann
Источник [19]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/news/53884
Ссылки в тексте:
[1] Транжира: https://itunes.apple.com/ru/app/tranzira-kontrol-budzeta/id637562972
[2] dev-story: http://www.iphones.ru/iNotes/299602
[3] картинка: http://habrastorage.org/storage3/41c/027/c54/41c027c543b0d11b45800edcce7c100c.jpg
[4] мобильной разработки: http://macradar.ru/interview/kak-dobitsya-uspexa-v-app-store-intervyu-s-razrabotchikami-prilozheniya-avoska-rozygrysh-kodov/
[5] мобильный js-фреймворк: http://phonejs.devexpress.com
[6] пишет: http://www.sap.com/pc/tech/mobile/featured/offers/gartner/reports/madp.html
[7] Kony: http://www.kony.com
[8] knockout-биндингов: http://knockoutjs.com/documentation/introduction.html
[9] dxAction: http://phonejs.devexpress.com/Documentation/Tutorial/Getting_Started/Data-Bound_Application#Add_Actions
[10] FastClick.js: https://github.com/ftlabs/fastclick
[11] Видео: http://vimeo.com/85335639
[12] dxTileView: http://phonejs.devexpress.com/Documentation/ApiReference/Widgets/dxTileView
[13] пользовательские данные: http://stackoverflow.com/questions/9664392/phonegap-ios-5-1-and-localstorage
[14] Документация: http://docs.phonegap.com/en/3.3.0/cordova_file_file.md.html#File
[15] https://github.com/chebum/fileStorage-for-Phone.JS/blob/master/fileStorage.js: https://github.com/chebum/fileStorage-for-Phone.JS/blob/master/fileStorage.js
[16] ArrayStore: http://phonejs.devexpress.com/Documentation/ApiReference/Data/ArrayStore
[17] Layout: http://phonejs.devexpress.com/Documentation/Howto/Views_and_Layouts
[18] dxToolbar: http://phonejs.devexpress.com/Documentation/ApiReference/Widgets/dxToolbar
[19] Источник: http://habrahabr.ru/post/210686/
Нажмите здесь для печати.