Привет.
Экономить воду нужно, это знают все. Дело это полезное. И чтобы граждан простимулировать в этом добром деле, у нас повсеместно ставят водосчетчики.
Причем без счетчика платишь в несколько раз больше.
Сам процесс установки выглядит так: вызываете мастера из сертифицированной конторы, он устанавливает счетчик, опечатывает его, расписывается и ставит печать в бумагах, с которыми идёте в единый расчетный центр, и ваш счетчик ставится на учет.
Далее через сайт, либо по телефону вы передаете показания.
Но как обычно, ко всякому благому делу присасываются кровопийцы и мошенники разных мастей. Чтобы облегчить себе жизнь, и усложнить её спамерам и мошенникам я написал приложение «Журнал счетчиков».
Под катом я расскажу вам о размышлениях, творческих метаниях, и процессе разработки, в шести частях, с антрактом, лирическими отступлениями и техническими подробностями.
Часть первая — Воинственная
Вот поставили вы себе счётчики, опломбировали и подали информацию в службу Единого Окна. Живёте себе спокойно, передаёте данные по показаниям, и в ус не дуете. И тут через год или два раздаётся звонок, от Единого Расчетного Центра/Управы/Городского Центра Метрологии/Самой Управляющей Компанией (нужное подчеркнуть). В трубке воинственная барышня/юноша сообщают, что срок службы счётчика вашего истёк — и быстрее ставьте новый – и когда наш мастер приедет?
Мошенники ведут себя агрессивно, вываливают поток слов, угрожают переводом на повышенный тариф без счетчиков, штрафами и санкциями. Так как звонят в середине рабочего дня, когда все на работе, и дома в основном пенсионеры, то в основном рассчитано на них, как наиболее доверчивых людей.
Общая рекомендация – не стесняйтесь, и посылайте этих нехороших людишек в далёкие страны. А информацию о сроках поверки, вы можете посмотреть в паспорте на свой счетчик.
Но порой найти вы в упор не помните, когда и что меняли и устанавливали, и куда паспорт от прибора запихали.
(С) – (и даже не поздоровался, хамло) РосКосмосВодоНаноКанал имени Святой Метрологии. Вы должны поменять счетчики на воду! У вас заканчивается срок службы!
(Я) – (в голосе паника) Ой! Что же делать!
(С) – (довольный, рыбка-то попалась) Мы пришлем вам мастера. Когда сможете принять?
(Я) – А что мастер сделает?
(С) – Он поменяет вам счетчики на новые!
(Я) – А зачем менять то?
(С) – (сердится) Потому, что так положено! Говорите адрес!
(Я) – Погодите. У меня сосед, он профессиональный строитель. Он мне сказал, что детальку меняют, и не надо счетчик снимать!
(С) – (уже с душой) Ваш сосед ничего не понимает! Это же метрология!
(Я) — (праведный гнев) Вы обманываете меня! Мой сосед ПРОФЕССИОНАЛЬНЫЙ строитель!!! Он лучше всех знает!!!
(С) – (раздражение и злость, смешалось в этом голоске) Ах так!!! Ну тогда вам пришлют письменное извещение!!! И платить вы будете по повышенному тарифу!!! (гудки....)
Часть вторая – Размышления.
Итак, чтобы не искать информацию по счетчикам, необходимо иметь её под рукою. Может записать её в тетрадочку и положить на стол? А заодно в эту тетрадочку буду записывать и ежемесячные показания счётчиков.
Решение простое, и эффективное. Осложняется тем, что у меня трое весёлых малышей, для которых любая бумага в пределах доступности (включая документы и обои) – потенциальный альбом, который нужно изрисовать или порвать на мелкие кусочки. По той же причине дома порой не найдёшь нормально пишущей ручки или карандаша. И приходится писать показания на огрызках салфеток ( манжетах/глиняных табличках).
Хорошо, заведу-ка я таблицу в Excel, табличку и буду записывать всё там. Но опять же, не потащу я компьютер в ванную и туалет, чтобы записать показания. Иначе мы опять возвращаемся к бумажке и ручке.
ОК. Буду использовать планшет. Но всё что мог на тот момент предоставить Маркет – либо платное, либо не отвечающее моему представлению о прекрасном. Да и в конце-то концов я Android – разработчик, или где?
(Я) – Простите, представьтесь пожалуйста.
(С) – Ээээ… Мария. Так вот…
(Я) – (торжественно) Мария! Я хочу поговорить с вами о Боге!
(С) — …
(Я) – Вы хотите поговорить со мной о Боге? Мария, я не слышу вас!
(С) — … Мы вообще-то…
(делает попытку вернуть все в русло первоначальной беседы)
(С) – Послушайте, мы хотим предложить вам выгодные условия…
(Я) – (добавив, щепотку праведного гнева) Мария! О чем вы?!!! Что может быть важнее разговора о Боге?!!!
(С) – Как понимаю, в не хотите разговаривать. (гудки...)
Часть третья – Подготовительная
Итак, составим план, в котором перечислив всё, что нам нужно.
Первое – иметь под рукой все данные по счётчикам. А именно: номер, модель, дата замены, лицевой счёт, контакты обслуживающей компании и мастер-установщик.
Второе – удобный инструмент для занесения ежемесячных показаний, а также какой-то простейший инструмент анализа.
Третье – иметь возможность завести несколько квартир, например чтобы вести статистику по квартире родителей.
Четвёртое – если понравится в использовании, не быть жадиной и поделиться с миром.
(Я) – Виталька!!! Это ты?!!! Сколько лет!!! Сколько зим!!!
(С) – (слегка обалдев) Я…
(Я) – Блин!!! Я ж твоего звонка ждал!!! Как сам?!!! Как жена?! Как дети?!!!
(С) – (охреневая глубже) Я вообще-то не женат…
(Я) – Ну что ж ты балбес. Мужик без жены, как гусеница без дерева!
(С) – Ну, я как понимаю вас достали со звонками. До свидания…
Часть четвёртая – Эксперименты.
Первый вариант был прост и бесхитростен.
Всё построено на ListView. Настроек нет. Зато ввиду моего тогдашнего увлечения пиксель-артом, значки нарисовал сам.
Старый дизайн
Главное, что приложение работало. Велась статистика (кстати пригодилось, когда пришлось пободаться с Мосэнерго).
Ввиду того, что меня приложение устраивало – создал аккаунт разработчика в Маркете, и выложил на суд общественности. Параллельно написал пост на Пикабу.
Основная критика касалась непритязательного внешнего вида, полного отсутствия Материал Дизайна, и значков которые «словно вернули в 90-е». Но в целом отзывы были положительными.
Тем временем я написал еще одно приложение, о чем рассказал на Хабре: https://habrahabr.ru/post/312672/. Совершенно неожиданно для меня, количество скачиваний именно «Журнала счётчиков» резко скакануло вверх.
Чувствуя ответственность перед более 5000 пользователей, я решил переписать приложение.
Для начала сел с блокнотом(чудом спасённым от моих индейцев), проанализировал комментарии, и выписал наиболее интересные предложения. Причём некоторые был весьма неожиданными, но реально добавляющие удобства пользователю. Например, добавить возможность включить фонарик, при занесении показаний счетчика.
Переписывать пришлось очень много. А с учетом работы и семьи, работать приходилось ночами, и переделка заняла чуть ли не больше времени, чем написание первой версии.
Поменял полностью дизайн
Новый дизайн
Добавил настройки, и возможность сохранить/загрузить данные в/из файла
Перерисовал иконки в векторе, исправил кучу глюков и ошибок, добавил счётчик тепла, и написал инструкцию. В общем, почти с нуля переделал.
(Я) – Нет
(С) – Можно узнать почему?
(Я) – Боги запретили мне делать это.
(С) – (пауза....) Простите, что?
(Я) – Боги запретили мне брать кредиты.
(С) – А может всё-таки…
(Я) – (возмущённо) Вы хотите поспорить с Богами?!!!
(С) – Ээээ… нет… До свидания…
Часть 5. Трюки разработчика
При разработке приложения столкнулся с кучей интересных моментов.
Например, если вы хотите создать ListView, внутри которого будут не просто данные, а еще и управляющие элементы (чекбоксы, редактируемые поля) вы можете столкнуться с тем, что информация неверно отображается, или передаётся в родительскую активность.
Например, в моём случае это был ListView и его адаптер, заполняемый при внесении показаний.
Диалог для ввода данных
Всё было нормально для обычных, однотарифных счётчиков, но для многотарифных началась ерунда.
Вот так я победил это.
Заводим адаптер (amountList)
ListView lvAmounts = (ListView)mView.findViewById(R.id.lv_newAmount_Amount); insertAmountAdapter insertAmountAdapter = new insertAmountAdapter(getActivity(), amountList);
lvAmounts.setAdapter(insertAmountAdapter);
Класс адаптера (также есть класс следящий за введёнными показаниями AmountTextWatcher):
public class insertAmountAdapter extends BaseAdapter {
Context mContext;
ArrayList<Amount> amountList;
LayoutInflater mLayoutInflater;
EditText etAmount, etTariff;
TextView tvTariffTitle;
public insertAmountAdapter(Context mContext, ArrayList<Amount> amountList) {
this.mContext = mContext;
this.amountList = amountList;
mLayoutInflater = (LayoutInflater)mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
@Override
public int getCount() {
return amountList.size();
}
@Override
public Object getItem(int position) {
return amountList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = convertView;
if(view == null){
view = mLayoutInflater.inflate(R.layout.item_insertamount, parent, false);
etTariff = (EditText)view.findViewById(R.id.et_amount_tariffInfo);
etTariff.addTextChangedListener(new AmountTextWatcher(view, etTariff, amountList));
etAmount = (EditText)view.findViewById(R.id.et_amount_Amount);
etAmount.addTextChangedListener(new AmountTextWatcher(view, etAmount, amountList));
}
etAmount.setTag(position);
etAmount.setText(String.valueOf(amountList.get(position).getAmount()));
etAmount.setFilters(new InputFilter[]{new DigitalFilter(3)});
tvTariffTitle = (TextView)view.findViewById(R.id.tv_amount_tariffTitle);
tvTariffTitle.setText(amountList.get(position).getTariffTitle()+", "+amountList.get(position).getUnit());
etTariff.setTag(position);
etTariff.setText(amountList.get(position).getTariffValue());
etTariff.setFilters(new InputFilter[]{new DigitalFilter(2)});
return view;
}
}
class AmountTextWatcher implements TextWatcher {
private View view;
private EditText editText;
private ArrayList<Amount> amountList;
public AmountTextWatcher(View view, EditText editText, ArrayList<Amount> amountList) {
this.view = view;
this.editText = editText;
this.amountList = amountList;
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
if(editText.equals(view.findViewById(R.id.et_amount_Amount))){
amountList.get(Integer.parseInt(editText.getTag().toString())).setAmount(Double.valueOf(s.toString().isEmpty()?"0":s.toString()));
} else if(editText.equals(view.findViewById(R.id.et_amount_tariffInfo))){
amountList.get(Integer.parseInt(editText.getTag().toString())).setTariffValue(s.toString().isEmpty()?"0":s.toString());
}
return;
}
}
Как видите, отслеживание рабочей строки идёт, через setTag и getTag.
Также, я захотел сделать инструкцию не просто с текстом, а с иллюстрациями. Для этого я решил использовать html-разметку. Но при работе с ней, есть некоторые нюансы.
Создаём инструкцию в strings.xml:
<string name="instructionText" formatted = "false">
<![CDATA[
<h3>Управляющие кнопки:</h3>
<p><img src="add_btn_instruction"> <b>Добавить элемент</b></p>
<p><img src="open_btn_instruction"> <b>Открыть информацию</b></p>
<h3>Инструкция:</h3>
<p>Чтобы добавить новый элемент (квартира, счетчик, показания) - нажмите на кнопку <b>Добавить элемент</b>, и введите необходимую информацию.</p>
<p>Чтобы увидеть дополнительную информацию (лицевой счет, компания и т.д.) - нажмите на кнопку <b>Открыть информацию</b>.</p>
<p>Чтобы открыть интересующий вас элемент - нажмите на него, и удерживайте около двух секунд. Затем выберите интересующий вас пункт меню.</p>
<h3>УПРАВЛЯЮЩЕЕ МЕНЮ:</h3>
<h6>(значок шестерёнки в правом верхнем углу, на экране со списком квартир)</h6>
<p><b>Валюта</b> можно поменять денежные единицы.</p>
<p><b>Инструкция</b> ознакомиться с данной инструкцией, и настроить её показ.</p>
<p><b>Разрешения</b> настроить разрешенные действия для приложения.</p>
<p>1)<b>Разрешение на работу с камерой/фонариком</b> - нужно для работы с фонариком.</p>
<p>2)<b>Разрешение на работу с файлами</b> - нужно для сохранения данных.</p>
<p><b>Сохранение/загрузка в файл</b>:</p>
<p>1)<b>Сохранение в XML-файл</b> - сохранит данные в файл <b>MLLDataOut.xml</b>, в папке <b>MeterListLog</b></p>
<p>2)<b>Загрузка из XML-файла</b> - разместите файл с данными <b>MLLDataIn.xml</b>, сформированными по подобию <b>MLLDataOut.xml</b>, в папке <b>MeterListLog</b>.</p>
<p><b>Внимание!!! Даное действие уничтожит все данные в программе, и перезапишет данными из файла.</b></p>
<h3>Пункты меню:</h3>
<p><b>Открыть</b> - войти в выбранный элемент, на следующий уровень(пример: квартира - счётчики).</p>
<p><b>Редактировать</b> - изменить данные по выбранному элементу.</p>
<p><b>Удалить</b> - удалить выбранный элемент.</p>
]]>
</string>
Добавляем картинки, под такими же именами, которые прописаны в инструкции, в папку drawables (у меня это: add_btn_instruction.png и open_btn_instruction.png).
В требуемом месте создаём строку, TextView и ImageGetter:
String instructionHTML = getString(R.string.instructionText);
TextView tvInstruction = (TextView)findViewById(R.id.tv_instruction);
tvInstruction.setText(Html.fromHtml(instructionHTML, htmlImageGetter, null));
Html.ImageGetter htmlImageGetter = new Html.ImageGetter() {
public Drawable getDrawable(String source) {
int resId = getResources().getIdentifier(source, "drawable", getPackageName());
Drawable ret = InstructionActivity.this.getResources().getDrawable(resId);
ret.setBounds(0, 0, ret.getIntrinsicWidth(), ret.getIntrinsicHeight());
return ret;
}
};
(Я) – В чем вопрос?
(С) – Мы проводим социологический опрос…
(Я) – Женщина, вы вообще на часы смотрите? Время уже десять вечера!
(С) – (праведное возмущение) Ну если вы спать хотите, то выключать телефон надо! (гудки...)
Часть 6. Реклама и все-все-все.
Как уже упоминал, изначально приложение писал для себя. И никаких вложений в рекламу не делал. Единственное – это статьи на пикабу, и косвенная на Хабре. Перевёл на английский, так на всякий случай.
Монетизации, нет никакой. Приложение бесплатно, и без рекламы. Как говорил уже раньше, в другой статье «Никакой материальной выгоды, кроме морального удовлетворения (и немного, для удовлетворения мании величия), и желания поделиться полезным инструментом с сообществом, автор не получает.»
В планах на следующую версию добавить напоминалку, расширить список счётчиков (счетчики с тарификацией, водоотведение), перевести еще на несколько языков (немецкий, белорусский, украинский, быть может казахский).
Вот собственно и вся история написания моего самого первого приложения.
(Я) – Зови меня, Добрый Человек, Добрый Человек.
(гудки....)
Засим позвольте откланяться. Надеюсь кому-то это окажется полезным. Если у кого-то есть замечания или подсказки, с большим удовольствием их выслушаю.
Автор: Snakecatcher