Сказ о том, как Android-разработчика спамеры задолбали, и что и из этого вышло

в 12:49, , рубрики: android, жэк, Разработка под android, спамеры, счетчики

Привет.
Экономить воду нужно, это знают все. Дело это полезное. И чтобы граждан простимулировать в этом добром деле, у нас повсеместно ставят водосчетчики.
Причем без счетчика платишь в несколько раз больше.
Сам процесс установки выглядит так: вызываете мастера из сертифицированной конторы, он устанавливает счетчик, опечатывает его, расписывается и ставит печать в бумагах, с которыми идёте в единый расчетный центр, и ваш счетчик ставится на учет.
Далее через сайт, либо по телефону вы передаете показания.
Но как обычно, ко всякому благому делу присасываются кровопийцы и мошенники разных мастей. Чтобы облегчить себе жизнь, и усложнить её спамерам и мошенникам я написал приложение «Журнал счетчиков».
Сказ о том, как Android-разработчика спамеры задолбали, и что и из этого вышло - 1
Под катом я расскажу вам о размышлениях, творческих метаниях, и процессе разработки, в шести частях, с антрактом, лирическими отступлениями и техническими подробностями.

Часть первая — Воинственная

Вот поставили вы себе счётчики, опломбировали и подали информацию в службу Единого Окна. Живёте себе спокойно, передаёте данные по показаниям, и в ус не дуете. И тут через год или два раздаётся звонок, от Единого Расчетного Центра/Управы/Городского Центра Метрологии/Самой Управляющей Компанией (нужное подчеркнуть). В трубке воинственная барышня/юноша сообщают, что срок службы счётчика вашего истёк — и быстрее ставьте новый – и когда наш мастер приедет?
Мошенники ведут себя агрессивно, вываливают поток слов, угрожают переводом на повышенный тариф без счетчиков, штрафами и санкциями. Так как звонят в середине рабочего дня, когда все на работе, и дома в основном пенсионеры, то в основном рассчитано на них, как наиболее доверчивых людей.
Общая рекомендация – не стесняйтесь, и посылайте этих нехороших людишек в далёкие страны. А информацию о сроках поверки, вы можете посмотреть в паспорте на свой счетчик.
Но порой найти вы в упор не помните, когда и что меняли и устанавливали, и куда паспорт от прибора запихали.

Диалог со спамером - У меня есть сосед
(С) -спамер, (Я) — Я
(С) – (и даже не поздоровался, хамло) РосКосмосВодоНаноКанал имени Святой Метрологии. Вы должны поменять счетчики на воду! У вас заканчивается срок службы!
(Я) – (в голосе паника) Ой! Что же делать!
(С) – (довольный, рыбка-то попалась) Мы пришлем вам мастера. Когда сможете принять?
(Я) – А что мастер сделает?
(С) – Он поменяет вам счетчики на новые!
(Я) – А зачем менять то?
(С) – (сердится) Потому, что так положено! Говорите адрес!
(Я) – Погодите. У меня сосед, он профессиональный строитель. Он мне сказал, что детальку меняют, и не надо счетчик снимать!
(С) – (уже с душой) Ваш сосед ничего не понимает! Это же метрология!
(Я) — (праведный гнев) Вы обманываете меня! Мой сосед ПРОФЕССИОНАЛЬНЫЙ строитель!!! Он лучше всех знает!!!
(С) – (раздражение и злость, смешалось в этом голоске) Ах так!!! Ну тогда вам пришлют письменное извещение!!! И платить вы будете по повышенному тарифу!!! (гудки....)

Часть вторая – Размышления.

Итак, чтобы не искать информацию по счетчикам, необходимо иметь её под рукою. Может записать её в тетрадочку и положить на стол? А заодно в эту тетрадочку буду записывать и ежемесячные показания счётчиков.
Решение простое, и эффективное. Осложняется тем, что у меня трое весёлых малышей, для которых любая бумага в пределах доступности (включая документы и обои) – потенциальный альбом, который нужно изрисовать или порвать на мелкие кусочки. По той же причине дома порой не найдёшь нормально пишущей ручки или карандаша. И приходится писать показания на огрызках салфеток ( манжетах/глиняных табличках).
Хорошо, заведу-ка я таблицу в Excel, табличку и буду записывать всё там. Но опять же, не потащу я компьютер в ванную и туалет, чтобы записать показания. Иначе мы опять возвращаемся к бумажке и ручке.
ОК. Буду использовать планшет. Но всё что мог на тот момент предоставить Маркет – либо платное, либо не отвечающее моему представлению о прекрасном. Да и в конце-то концов я Android – разработчик, или где?

Диалог со спамером - Подумай о душе, Мария

(С) – Добрый день, мы хотим предложить вам перейти на новый интернет, от лучшего провайдера. У нас самый выгодный…
(Я) – Простите, представьтесь пожалуйста.
(С) – Ээээ… Мария. Так вот…
(Я) – (торжественно) Мария! Я хочу поговорить с вами о Боге!
(С) — …
(Я) – Вы хотите поговорить со мной о Боге? Мария, я не слышу вас!
(С) — … Мы вообще-то…
(делает попытку вернуть все в русло первоначальной беседы)
(С) – Послушайте, мы хотим предложить вам выгодные условия…
(Я) – (добавив, щепотку праведного гнева) Мария! О чем вы?!!! Что может быть важнее разговора о Боге?!!!
(С) – Как понимаю, в не хотите разговаривать. (гудки...)

Часть третья – Подготовительная

Итак, составим план, в котором перечислив всё, что нам нужно.
Первое – иметь под рукой все данные по счётчикам. А именно: номер, модель, дата замены, лицевой счёт, контакты обслуживающей компании и мастер-установщик.
Второе – удобный инструмент для занесения ежемесячных показаний, а также какой-то простейший инструмент анализа.
Третье – иметь возможность завести несколько квартир, например чтобы вести статистику по квартире родителей.
Четвёртое – если понравится в использовании, не быть жадиной и поделиться с миром.

Диалог со спамером - Ориентируемся на личность
(С) – Добрый день (и называет, гад, мое имя). Меня зовут Виталий. Я хочу предложить вам выгодное предложение, по управлению вашими капиталами.
(Я) – Виталька!!! Это ты?!!! Сколько лет!!! Сколько зим!!!
(С) – (слегка обалдев) Я…
(Я) – Блин!!! Я ж твоего звонка ждал!!! Как сам?!!! Как жена?! Как дети?!!!
(С) – (охреневая глубже) Я вообще-то не женат…
(Я) – Ну что ж ты балбес. Мужик без жены, как гусеница без дерева!
(С) – Ну, я как понимаю вас достали со звонками. До свидания…

Часть четвёртая – Эксперименты.

Первый вариант был прост и бесхитростен.
Всё построено на ListView. Настроек нет. Зато ввиду моего тогдашнего увлечения пиксель-артом, значки нарисовал сам.
Сказ о том, как Android-разработчика спамеры задолбали, и что и из этого вышло - 2
Старый дизайн
Главное, что приложение работало. Велась статистика (кстати пригодилось, когда пришлось пободаться с Мосэнерго).
Ввиду того, что меня приложение устраивало – создал аккаунт разработчика в Маркете, и выложил на суд общественности. Параллельно написал пост на Пикабу.
Основная критика касалась непритязательного внешнего вида, полного отсутствия Материал Дизайна, и значков которые «словно вернули в 90-е». Но в целом отзывы были положительными.
Тем временем я написал еще одно приложение, о чем рассказал на Хабре: https://habrahabr.ru/post/312672/. Совершенно неожиданно для меня, количество скачиваний именно «Журнала счётчиков» резко скакануло вверх.
Чувствуя ответственность перед более 5000 пользователей, я решил переписать приложение.
Для начала сел с блокнотом(чудом спасённым от моих индейцев), проанализировал комментарии, и выписал наиболее интересные предложения. Причём некоторые был весьма неожиданными, но реально добавляющие удобства пользователю. Например, добавить возможность включить фонарик, при занесении показаний счетчика.
Переписывать пришлось очень много. А с учетом работы и семьи, работать приходилось ночами, и переделка заняла чуть ли не больше времени, чем написание первой версии.
Поменял полностью дизайн
Сказ о том, как Android-разработчика спамеры задолбали, и что и из этого вышло - 3
Новый дизайн
Добавил настройки, и возможность сохранить/загрузить данные в/из файла
Сказ о том, как Android-разработчика спамеры задолбали, и что и из этого вышло - 4
Перерисовал иконки в векторе, исправил кучу глюков и ошибок, добавил счётчик тепла, и написал инструкцию. В общем, почти с нуля переделал.

Диалог со спамером - Говорящий с Богами

(С) – Добрый день. Я говорю с (мое имя). Мы из банка-партнёра от банка «Тинкофф». Вы хотите взять кредит?
(Я) – Нет
(С) – Можно узнать почему?
(Я) – Боги запретили мне делать это.
(С) – (пауза....) Простите, что?
(Я) – Боги запретили мне брать кредиты.
(С) – А может всё-таки…
(Я) – (возмущённо) Вы хотите поспорить с Богами?!!!
(С) – Ээээ… нет… До свидания…

Часть 5. Трюки разработчика

При разработке приложения столкнулся с кучей интересных моментов.
Например, если вы хотите создать ListView, внутри которого будут не просто данные, а еще и управляющие элементы (чекбоксы, редактируемые поля) вы можете столкнуться с тем, что информация неверно отображается, или передаётся в родительскую активность.
Например, в моём случае это был ListView и его адаптер, заполняемый при внесении показаний.
Сказ о том, как Android-разработчика спамеры задолбали, и что и из этого вышло - 5
Диалог для ввода данных
Всё было нормально для обычных, однотарифных счётчиков, но для многотарифных началась ерунда.
Вот так я победил это.
Заводим адаптер (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

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js