...

суббота, 15 сентября 2018 г.

iOS CSS of death

image

На github опубликован код, приводящий к force restart iOS (11/12 GM) устройств при посещении html-страницы. Также приводит к зависанию Mac OS High Sierra/Mohave при использовании Safari.

Код, приводящий к force restart представляет из себя html-код с большим количеством вложенных div'ов и "сумашедшей" функцией размытия заднего плана:

 div {
            backdrop-filter: blur(10px);
            -webkit-backdrop-filter: blur(10px);
          width:10000px; height:10000px;
        }
    </style>
    </head>
    <body>
<div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div><div>

Также выложен PoC html-страница, реализующая данный баг (пользователи Safari переходят на свой страх и риск).

Есть предположение, что баг закрался на уровне ниже, нежели webkit и может привести к более серьезным последствиям. Также, использование данного бага может быть применимо в социотехнических компаниях и дурацких розыгрышах, так что советую крайне подозрительно относится ко всем ссылкам, тем более что на iOS все браузеры, по сути, это надстройка над Сафари.

Let's block ads! (Why?)

Тест-драйв VW e-Golf, Nissan Leaf и Tesla Model 3

В этом сентябре в США, Канаде и Новой Зеландии организация Drive Electric организовала события, нацеленные на популяризацию электромобилей.
В моём городе событие носило имя Drive Electric Victoria и проходило на территории University of Victoria и рядом с ним.

Но всё это я узнал потом. На самом деле, на «фестиваль» я попал случайно — просто проезжали с друзьями мимо университета и я заметил указатели вдоль дороги, на которых был изображён зелёный автомобиль и зелёная розетка. Сложив два плюс два я сказал друзьям, что догоню их позже и отправился вслед за флажками.

Мероприятие представляло из себя пару палаток, где можно было зарегистрироваться на тест-драйв и перекусить. Кроме этого на университетской парковке номер 10 стояло несколько десятков электромобилей, их владельцы и желающие прокатится. Конечно, всё это было бесплатно.

Я уже ездил на некоторых из EV и даже писал статью про одну из таких поездок на Tesla Model X. В этот раз я выбрал для теста Volkswagen e-Golf, Nissan Leaf нового поколения и Tesla Model 3.

Я коротко расскажу о том, что пишет об автомобиле производитель (данные по США), о том, что говорит о нём владелец и о своих личных ощущениях и субъективных оценках.

Volkswagen e-Golf


Данные производителя


Цена от $30,495
Запас хода 201 км

Рассказ владельца


Владелец — пожилой мужчина.
Это второй автомобиль в семье — для поездок по городу. В обслуживании не нуждается и не ломается — автомобиль надёжный, так как по сути это просто улучшеный Golf, а эта модель прошла очень длительную проверку временем и постоянно совершенствовалась. Кроме него в семье ещё есть SUV гибрид (я не запомнил модель) с очень маленьким расходом топлива. Рекуперация сильно увеличивает пробег, но жена предпочитает её отключать. Педалью тормоза можно не пользоваться. Пробег сопоставим с Hyundai Ioniq.

Мои комментарии


Внешне выглядит как обычный автомобиль — не привлекает внимания, но и не отталкивает. Салон — просто отличный. Хорошие материалы, удобные сиденья, логичные и понятные элементы управления. Одним словом — консервативный немец.

С «включением» возникли какие-то проблемы. Т.е. нужно было нажать кнопку Start, при этом автомобиль «просыпался», но, почему-то не всегда ехал. Нужно было нажать что-то ещё (педаль тормоза?). Я всё делал, вроде бы так же, как говорил владелец, но автомобиль поехал только с третьей попытки — что-то не так или с логикой e-Golf или с моей.

Едет в целом очень хорошо. Неплохо держит дорогу и не пробуксовывает на резком ускорении, несмотря на мокрый асфальт. Есть некоторый запас по мощности. Шума мотора не слышно и вообще, можно сказать, что шумоизоляция хорошая. Плавный ход. Ощущения очень похожи на Hyundai Ioniq (когда-то я делал его тест-драйв и даже собирался купить).

Очень удобно меняются режимы рекуперации — рычагом переключения «скоростей». В положении «D» можно качать его влево или вправо и выбирать таким образом один из трёх режимов, либо просто потянуть его ещё дальше назад, что автоматически включит самый сильный режим — это работает как своеобразный тормоз.

Кроме этого, кнопкой можно выбрать один из трёх режимов езды — от самого энергосберегающего, до «спортивного».

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

Nissal Leaf


Цена от $29,990
Запас хода 243 км

Рассказ владельца


Владелец мужчина средних лет.
Хотели купить Kia Kona Electric или Tesla Model 3 со стандартной батареей, но не дождались и решили купить этот Nissan. Очень удобный режим «одной педали» — педалью тормоза можно не пользоваться даже на большом уклоне (автомобиль не катится под горку после остановки). Жене режим рекуперации сначала не понравился, но потом привыкла. Это дорогая комплектация, у которой даже есть помощник вождению (активный круиз контроль и удержание в полосе). Есть не только камера заднего вида, но и круговой обзор. Нет необходимости заряжать его только до 80-90% и не разряжать меньше 10-20% (как это делают владельца Tesla), так как этот «буфер» уже заложен производителем (т.е. его на самом деле нельзя зарядить на 100% или разрядить ниже какого-то минимума). Из недостатков — отсутствие зарядки USB (или хотя бы прикуривателя) для заднего ряда сидений и очень плохое (глючное) приложение для телефона.

Мои комментарии


Если предыдущее поколение Nissan Leaf нелепое, но при этом оригинальное, то тут дизайн кузова просто нелепый. Что может быть хуже? Хуже может быть его салон. Он кошмарен. Очень плохие материалы. Везде только жёсткий и самый дешёвый пластик. На моих фотографиях этого не видно, и даже, возможно, кажется, что он такой же как у e-Golf или даже лучше (если посмотреть на руль). Но это ошибка. Он значительно хуже не только, чем e-Golf, но и, пожалуй, хуже любого другого автомобиля на которых я ездил за рулём последние лет 15.

Элементы управления раскиданы по салону как попало — обратите внимание на кнопку подогрева задних сидений, расположенную сбоку пассажирского! «Шайба» — рычаг переключения режимов бессмысленный, излишне сложный, конечно, очень дешёвый и неудобный. Режим парковки, зачем-то включается кнопкой сверху на этой «шайбе», а режим Drive движением налево и вниз. Панель приборов такая, что на неё не хочется смотреть.

Едет неплохо. Относительно плавный ход. Также как и e-Golf не пробуксовывает на старте. Разгоняется вяло. Мотора не слышно, но шумоизоляция плохая. Плюс, я уверен, что через некоторое время салон начнёт скрипеть и «кряхтеть».

Итого. Небольшой, недорогой, автомобиль с небольшим пробегом и очень плохим салоном, в который не хочется возвращаться. У меня есть подозрение, что его сделали намеренно таким плохим, чтобы не покупали и не пришлось удовлетворять повышенный спрос. Не вижу других причин почему он настолько плох и снаружи и внутри.

Tesla Model 3


Цена от $49,000
Запас хода 500 км

Рассказ владельца


Владелец — девушка.

Влюбилась в Tesla с первого взгляда и долго упрашивала мужа купить. Они совсем не богаты, но очень хотелось. «Воевала с мужем больше часа» убеждая его. Просто обожает этот автомобиль. Приезжает на эти ежегодные мероприятия, чтобы показать его всем и ещё раз пережить это чувство, когда человек видит Model 3 впервые. Педалью тормоза почти не пользуется. Заряжает примерно, раз в неделю. Их счёт за электричество увеличился на $15 в месяц, а раньше они тратили на заправку пару сотен. Автомобили меняют редко — на прошлом проездили 10 лет, на этом хотят проездить даже больше, если получится. Не ломается. Обслуживать не нужно. Я спросил про то, как работает ключ (Model 3 сама открывается, когда приближаешься к автомобилю, с включенным Bluetooth на телефоне). У неё iPhone, приложение Tesla она никогда не выключает и проблем нет, а у мужа Android и бывают проблемы (выключает приложение). Автоматическим «датчиком дождя» (на самом деле там камера используется) она не пользуется — ей не нравится как он работает. Зато автопилотом пользуется активно (они его не покупали, и сейчас он работает в режиме ознакомления). Просила не пользоваться механическими кнопками при открывании дверей, так как есть электрические.

Мои комментарии


Внешне прекрасен. Вообще, я уже писал про него. Внутри — ещё лучше. Отличные, премиальные материалы везде — очень дорогой и качественный мягкий пластик (единственное место, где жёсткий — рулевая колонка), кожа (точнее кож.зам — на самом деле сейчас многие используют не настоящую кожу — жалеют зверей), шпон. Сиденья не понравились — нет вентиляции и опять потела спина (как в Model X). Стеклянная крыша. Двери открываются по кнопке — это очень удобно, но всем незнакомым нужно объяснять как правильно это делать.

Едет отлично. С одной стороны очень «цепкая» на дороге, с другой стороны идёт мягко (я ожидал, что будет гораздо жёстче). Молниеносно быстрая — даже при движении около 40 км в час после нажатия на педаль «газа» мгновенно ускоряется и сильно вжимает в кресло. Хорошая шумоизоляция (примерно, как у e-Golf). Очень сильная рекуперация. Настраивается через меню и имеет всего пару положений, что не удобно — было бы не плохо иметь возможность настраивать её отдельной кнопкой. Со стороны передней подвески при езде по неровностям был слышен почти незаметный стук. Не совсем понятно — это родной звук или неисправность.

Итого. Очень красивый, премиальный, экстремально быстрый автомобиль с очень большим запасом хода.

Итого


Субъективно я бы сказал, что e-Golf — отличный, городской автомобиль. Leaf настолько плох, насколько хороша Model 3.

Tesla — вне конкуренции и вообще из какой-то другой, недостижимой в данный момент ни немцами ни японцами лиги. Когда едешь на Model 3 такой впечатление, что ты как-то переместился на пару десятилетий вперёд и сейчас находишься там, где все остальные автопроизводители будут не скоро.

Let's block ads! (Why?)

[Из песочницы] Пример простого версионирования записей PostgreSQL

Когда то видел реализацию «истории записей» — версионирования, на стороне программы, работающей с SQL базой. Перед изменением записи, из базы получалась старая версия, записывалась в XML и полученная строка XML записывалась в отдельную таблицу версий.

Изначально, в своей программе планировал версионирование сделать когда нибудь потом, какой то срочной необходимости не было. Помню, было желание где-то использовать тип данных jsonb, как только додумался до простой и лаконичной реализации версионирования на стороне SQL, не сделать не смог. Всего лишь одна таблица версий с 5 колонками и одна триггерная функция в 3 строчки кода.

Для описания реализации одной таблицы версий мало, поэтому придется описать еще несколько таблиц для примера.

Практически во всех базах данных, за редким исключением, есть таблица users – пользователи. Историю изменений — версий пользователя полезно хранить, например, для возможности откатится на старую версию, силами самого пользователя.

Пример таблицы пользователей:

image

Последние два поля на картинке, нужны для таблицы версий, также их можно назвать «автор версии» и «дата версии», но, при желании, можно обойтись без них.
Таблица версий:

Триггерная функция для сохранения версий:

Первые два поля заполняются из сохраняемой записи OLD.changestamp и OLD.userid.
В таблице версий, могут храниться не только записи таблицы users, третье поле MD5 хеш имени версионируемой таблицы, преобразованный в uuid.

В примерах ранее описана очень простая структура, но, как правило, у различных справочных данных могут быть дополнительные таблицы, с отношением «один ко многим».

Например, таблица «Группы пользователей».

И вторая таблица «Пользователи групп», состав группы — пользователи входящие в группу.

Для того, чтобы не усложнять простой механизм версионирования можно сделать небольшое дублирование данных в таблице групп, добавить jsonb поле, повторяющее структуру таблицы «Пользователи групп».

Для упрощения работы с задублированными данными, можно сделать дополнительную триггерную функцию, при INSERT или UPDATE, заполняющее таблицу «Пользователи групп» из jsonb поля.

Описанное выше дублирование, нужно только, когда требуется часто и максимально быстро получить данные из таблицы. Например, если часто делается запрос к таблице «Пользователи групп» для определения входит ли пользователь в группу Администраторы. В остальных случаях, данные можно получать запросом прямо из поля jsonb, и не использовать дублирующую таблицу.

Полный код примера по ссылке

Let's block ads! (Why?)

Интеграция 3CX с 1С: Управление торговлей

По многочисленным просьбам партнеров и клиентов мы выпустили серверную интеграцию 3CX v15.5 с популярной системой 1С: Предприятие.

Интеграция представляет собой расширение типовой конфигурации  «Управление торговлей, редакция 11 (11.4.3.167) для России» (файл .cfe), которое устанавливается в конфигурацию стандартным способом (см. ниже). Со стороны сервера 3CX загружается CRM-шаблон для взаимодействия с веб-сервисом опубликованной конфигурации 1С.

Расширение конфигурации 1С: Управление торговлей реализует REST API, который сопоставляет номера входящих вызовов с номерами контрагентов в 1С. Когда в 3CX поступает вызов, она через REST запрашивает у 1С данные контрагента, связанные Caller ID абонента. Если контрагент найден, его данные добавляются в Контакты 3CX и отображаются в веб-клиенте 3CX (имя и фамилия).

Если в 3CX включено журналирование вызовов (Enable call journaling), 1С будет вести журнал вызовов в 1С.

Требования к конфигурации 1С: Предприятие


Для использования расширения необходима версия платформы 1С: Предприятие не ниже 8.3.11. Типовая конфигурация «Управление торговлей, редакция 11» (11.4.3.167) поставляется в режиме совместимости с платформой 8.3.10. Для использования расширения 3СХ необходимо установить режим совместимости конфигурации в значение Не использовать. Если конфигурация находится на поддержке, то следует включить возможность изменения конфигурации.

Продемонстрируем это на скриншотах:

В Конфигураторе 1C перейдите в раздел Конфигурация — Поддержка — Настройка поддержки.

Нажмите кнопку Включить возможность изменения и во всплывающем окне подтвердите выбор.

В окне Настройка правил поддержки выберите Объекты с правилом «Изменения разрешены» — Объект поставщика редактируется с сохранением поддержки и Объекты с правилом «Изменения не рекомендуются» — Объект поставщика не редактируется.

Кликните правой кнопкой мыши по корневому разделу Управление Торговлей, выберите Свойства и установите Режим совместимости — Не использовать.

Выберите Конфигурация — Обновить конфигурацию базы данных.

И нажмите Принять для завершения изменений.

Подключение расширения для взаимодействия с 3CX


Закройте Конфигуратор и запустите конфигурацию 1С в режиме Предприятие. В верхнем левом меню выберите Все функции…

Выберите пункт Управление расширениями конфигурации.

Убедитесь, что установлена Область действия при установке расширения конфигурации — Информационная база, нажмите кнопку Добавить и добавьте расширение, указав файл 3cx1cextension.cfe из архива.

После установки расширения, вкладка Управление расширениями конфигурации должна выглядеть, как показано ниже.

Публикация HTTP-сервиса расширения 1С: Предприятие на веб-сервере


Публикация конфигурации 1С на веб-сервере производится стандартным способом, с учетом особенностей программного окружения.
Выполняя публикацию, установите опцию Публиковать HTTP-сервисы расширений по умолчанию.

Если в конфигурации есть пользователи (в редких случаях используются конфигурации без пользователей), необходимо изменить строку подключения к базе 1С: Предприятие.

Рассмотрим это на примере веб-сервера IIS:

В каталоге публикации найдите файл default.vrd.

Измените в нем строку подключения, добавив учетные данные пользователя, от имени которого база данных будет отвечать на HTTP-запросы. Строка подключения должна выглядеть примерно так.

Установка XML-шаблона CRM 1С: Предприятие в 3CX


Откройте интерфейс управления 3CX и перейдите в раздел Параметры — CRM-интеграция — вкладка На стороне сервера.

В разделе Доступные CRM интеграции выберите 1С и нажмите Загрузить выбранное.

Затем в  поле Domain Part укажите URL опубликованного HTTP-сервиса конфигурации 1С.
Для учета вызовов в 1С включите опцию Enable call journaling.


Возможности интеграции 1С: Предприятие c 3CX


Как было сказано выше, расширение сопоставляет входящие Caller ID с номерами контрагентами в базе 1С. Если совпадение найдено, веб-клиент 3CX покажет имя и фамилию вызывающего абонента.

Учет вызовов в 1С


Если в CRM-шаблоне включено журналирование вызовов, 1С будет вести журнал входящих и исходящих вызовов со следующими параметрами:
  • Дата вызова
  • Тип вызова: входящий / исходящий / пропущен внутренним абонентом / нет ответа внешнего абонента
  • Длительность вызова
  • Номер внешнего абонента (Caller ID)
  • Номер внутреннего абонента (добавочного номера 3CX)
  • Контрагент 1С (если найдено соответствие)

В журнале вызовов вы можете задавать условия отбора и сортировки по интересующим вас столбцам, чтобы быстро анализировать ситуацию пропущенными или несостоявшимися вызовами в организации.

Технический журнал взаимодействия 3CX и 1С


Технический журнал 3CX ведет лог всех обращений 3CX к 1С через REST API.

Этот журнал может использоваться для диагностики администратором или программистом 1С.

Загрузки и документация


Часто задаваемые вопросы


  1. Что происходит, если клиент не найден? В данный момент платформа 1С не имеет возможности вызывать форму добавления нового клиента по HTTP-ссылке.
  2. Если контакт отображается в веб-клиенте, можно ли открыть его полную карточку в 1С? В данный момент платформа 1С не имеет возможности вызывать карточку клиента по HTTP-ссылке.
  3. Можно ли сделать исходящий вызов из тонкого или толстого клиента 1С? Это возможно, но для этого должна быть реализована соответствующая поддержка со стороны 3CX REST API.
  4. Можно ли переадресовать вызов из 1С или реализовать связку «клиент — закрепленный менеджер»? Это возможно, но для этого должна быть реализована соответствующая поддержка со стороны 3CX REST API.
  5. Можно ли реализовать панель оперативного мониторинга (панель телефонии) вызовов в 1С? В данный момент при входящем вызове 3CX передает только Caller ID абонента без вспомогательной служебной информации (например, занятости конкретной линии, добавочного номера получателя вызова и т.п.). Дополнительная служебная информация передается уже после завершения вызова. Кроме того, панель мониторинга предполагает взаимодействие пользователя с вызовами, но для этого требуется поддержка со стороны 3CX REST API.
  6. Можно ли увидеть пропущенные вызовы в карточке контрагента? Используйте Журнал вызовов в 1С, который реализует расширение. В нем вы можете быстро отобрать интересующих вас контрагентов.

Let's block ads! (Why?)

Из Dribbble в Android Motion

На просторах интернета много интересных анимированных шаблонов с красивыми пользовательскими интерфейсами мобильных приложений, но не так много примеров с реализацией этих интерфейсов. Несмотря на обилие различных встроенных фреймворков и инструментов в Android SDK, начинающему разработчику зачастую не просто реализовать красивый пользовательский интерфейс, в том числе и при наличии готового шаблона.

В этой статье мы поробуем реализовать пользовательский интерфейс, разработанный Иваном Парфеновым для студии PLΛTES.

Для начала создадим два фрагмента: RecyclerFragment и DetailsFragment.

Android Transition framework?


Android Transition framework работает неплохо, но есть некоторые ньюансы. Во-первых, мы хотим, чтобы у нас все работало хотя бы на API 19, а во-вторых, нам необходимо анимировать несколько пользовательских элементов одновременно и некоторые из них присутствуют только в одном фрагменте. Поэтому анимацию переходного элемента (shared element transition) мы реализуем вручную с использованием ViewPropertyAnimator.

Все по порядку


  1. Вычисляем конечные координаты выбранного элемента из списка (его координаты в DerailsFragment), список — это RecyclerView;
  2. Сохраняем текущие координаты (координаты в RecyclerFragment) и передаем их в DetailsFragment (это нужно для обратной анимации при API < 21);
  3. Создаем копию выбранного из списка элемента;
  4. Делаем выбранный элемент невидимым (не копию, а сам элемент);
  5. Добавляем созданную в п. 3 копию в корневой layout родительского фрагмента, в нашем случае это RecyclerFragment;
  6. Запускаем анимацию остальных элементов интерфейса и перемещаем созданную копию в конечные координаты из п. 1;
  7. Когда анимация закончится, создаем транзакцию и показываем DetailsFragment;
  8. Запускаем анимаци элементов интерфейса в DetailsFragment.

Анимация элементов UI


Для анимации Toolbar мы создадим дополнительную View в RecyclerFragment и разместим ее за экраном сверху. Эта View будет анимироваться в Toolbar контейнер в DetailsFragment (голубой цвет на gif) с использованием ViewPropertyAnimator.
<View
      android:id="@+id/details_toolbar_helper"
      android:layout_width="wrap_content"
      android:layout_height="@dimen/details_toolbar_container_height"
      android:background="@color/colorPrimary"
      app:layout_constraintTop_toTopOf="parent"/>

// In RecyclerFragment
details_toolbar_helper.translationY = -details_toolbar_helper.height
image

Анимация BottomNavigationView и RecyclerView также реализована с помощью ViewPropertyAnimator, ничего сложного (изменение прозрачности и перемещение).

Немножко из Transition framework


Если простыми словами, то android transition framework, когда начинает анимацию переходного элемента, создает копию контента этого переходного элемента (что-то типа print screen), делает из этой копии ImageView, затем добавляет эту картинку в дополнителный слой корневой разметки (overlay layer) в вызываемом фрагменте и запускает анимацию.

Нам android transition framework не совсем подходит, т.к. когда начинается анимация переходного элемента, то все остальные элементы пользовательского интерфейса в фрагменте уничтожаются и мы не можем их анимировать. Т.е. когды мы в RecyclerFragment кликаем на элемент списка для открытия DetailsFragment и стартуем переходную анимацию, то все остальные элементы интерфейса в RecyclerFragment уничтожаются без анимации.

Чтобы получить желаемый результат, мы будем вручную создавать копию выбранного из списка элемента, добавлять его в overlay слой и затем анимировать. Но здесь появляется небольшая проблема, в документации к методу ViewGroupOverlay add(view: View) написано:

If the view has a parent, the view will be removed from that parent before being added to the overlay.

Но для RecyclerView это не работает, выбранный элемент не удаляется из RecyclerView после его добавления в overlay слой.

Вот что получается когда добавляем выбранный элемент в overlay слой:

А нам нужно так:

Поэтому overlay слой мы использовать не будем, а копию будем добавлять сразу в корневой layout. Создадим копию контента выбранного элемента, добавим ее в ImageView и установим координаты:

fun View.copyViewImage(): View {
    val copy = ImageView(context)

    val bitmap = drawToBitmap()
    copy.setImageBitmap(bitmap)

    // В pre-Lollipop при создании копии, тень от card view тоже копируется, и нам не нужна дополнительная card view

    return (if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        CardView(context).apply {
            cardElevation = resources.getDimension(R.dimen.card_elevation)
            radius = resources.getDimension(R.dimen.card_corner_radius)
            addView(copy)
        }
    } else {
        copy
    }).apply {
        layoutParams = this@copyViewImage.layoutParams
        layoutParams.height = this@copyViewImage.height
        layoutParams.width = this@copyViewImage.width
        x = this@copyViewImage.x
        y = this@copyViewImage.y
    }
}

Зачем создавать копию, если можно просто анимировать непосредственно выбранный из списка элемент?

Потому что сам RecyclerView тоже будет анимироваться и соответсвтенно все его элементы, включая и выбранный, который мы хотим анимировать отдельно.

После этого добавляем копию в корневую разметку и начинаем анимацию.

override fun onClick(view: View) {
    val fragmentTransaction = initFragmentTransaction(view)
    val copy = view.createCopyView()
    root.addView(copy)
    view.visibility = View.INVISIBLE
    startAnimation(copy, fragmentTransaction)
}

И вот, что у нас получилось:

Финишная прямая


Анимация на gif выше происходит в RecyclerFragment, а после ее завершения нам необходимо показать DetailsFragment.
.withEndAction {
    fragmentTransaction?.commitAllowingStateLoss()
}

Почему мы используем commitAllowingStateLoss?

Если его не использовать и в момент анимации будет, например смена ориентации экрана, то мы получим IllegalStateExсeption. Вот здесь хорошо про это написано.

Далее запускаем анимацию необходимых элементов пользовательского интерфейса в DetailsFragment.

Запустим все вместе


Не совсем так, как на оригинале, но выглядит похоже.

Примеры


Исходный код доступен на GitHub, пример приложения с похожим дизайном можно скачать из Google Play, также статья доступна на английском языке. Спасибо за внимание!

GitHub Google Play Medium

Let's block ads! (Why?)