HomeWorkAnd (Block 1)
Задача Code Like a Pro
✔️ При выполнении задачи используется GitHub Actions для сборки приложения в apk-файл (и последующего тестирования) при каждом пуше.
Проект выводит на экран текстовую надпись NMedia! вместо Hello, World
При создании проекта использовались следующие настройки:
applicationId: ru.netology.nmedia versionName: 1.0 minSdk (минимальная версия Android): 23 (Android 6.0)
Задача Launcher Icon
Заменена стандартная иконка приложения Android на кастомную
Для создания иконок используется Image Asset Studio, который входит в состав Android Studio и позволяет выбрать изображение и сам разместит необходимые файлы в каталогах res/mipmap.
➡️ Начиная с Android 8.0, применяется подход адаптивных иконок запуска, которые разделяют подложку иконки - background и непосредственно foreground - часть (чаще всего логотип), позволяя в зависимости от устройства менять форму подложки.
❓ Если интересно - 💡 можно прочесть
Иконка указывается в манифесте: (атрибуты android:icon и android:roundIcon)
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
Эти значения ведут на файлы mipmap/ic_launcher и (mipmap/ic_launcher_round) соответственно. В зависимости от версии платформы это будут либо сгенерированные изображения в формате png, либо xml, в которых стоят ссылки на foreground и background ресурсы.
Задача Translations
Добавление перевода на русский язык (для поддержания мультиязычности).
Переводиться должны:
- Название приложения (пусть на русском будет "НМедиа")
- Текст (пусть на русском будет "НМедиа!")
Задача Layout
Вёрстка для получения приложения следующего вида
Реализована разметка в соответствии с заданием (при увеличении чисел изменяется величина строки). Все иконки взяты из стандартного набора.
Like, Share
Добавлен следующий функционал приложения:
- При клике на like меняется не только картинка, но и число рядом с ней: like - увеличивается на +1, dislike - уменьшается на -1
- При клике на share увеличиваться число рядом (10 раз нажали на share - +10)
- Добавлена логика с тысячами и миллионами: если количество лайков, share или просмотров перевалило за 999, то должно отображается 1K и т.д., а не 1000
❗❗❗ Attention : heavy_exclamation_mark::heavy_exclamation_mark::heavy_exclamation_mark:
1.1К отображается по достижении 1100
После 10К сотни перестают отображаться
После 1M сотни тысяч отображаются в формате 1.3M
Логика по расчёту и преобразованию вынесена как отдельный объект
Задача MVVM
Проект переделан согласно архитектуре MVVM.
That's all 🛠️ , but it's not easy
Задача RecyclerView
В проект добавлена реализацию отображения списков на базе RecyclerView и ListAdapter.
По аналогии с лекцией к OnLikeListener, добавлен OnShareListener.
Задача Задача CRUD и отмена редактирования
-
В проект приложения добавлена реализация CRUD.
-
Реализована отмена редактирования (по аналогии с Telegram)
Для этого с помощью ConstraintLayout сформирована соответствующую структура над полем ввода поста. View объединены в виртуальную группу .
Во ViewModel выставляются нужные значения для сокрытия и отображения панели:
group.visibility = View.GONE // сокрытие
group.visibility = View.VISIBLE // отображение
Задача Кнопки
Стилизованы кнопки Like, Share, Menu, а также View в виде Button, согласно документации на компоненты : open_book: Buttons.
Текст задан через атрибуты кнопки (кол-во лайков, шаринга, просмотров).
Создан и назначен кнопкам отдельный стиль styles.xml.
Задача Editing
Реализованы создание поста и функция редактирования поста в отдельных Activity.
Задача YouTube Video
На Intent'ах в Android строится большая часть взаимодействия между приложениями, в частности, задействуются другие приложения для отображения нужного контента/выполнения действий и т.д. (Самые распространённые Intent'ы)
В разметку поста добавлен отдельный блок, который отображается при наличии ссылки на видео, при нажатии на который запускается неявный Intent со ссылкой. Далее сиситема его обрабатывает и отображает пользователю видео в браузере или в приложении YouTube.
📌 Реализация
Вместо обложки видео установлена картинка-заглушка и кнопка Play.
Для запуска Intent'а можно кликать и на кнопке, и на обложке (т.е. пользователю не обязательно попадать в саму кнопку).
Для открытия внешнего приложения:
- используется URL'а вида: "https://www.youtube.com/watch?v=WhWc3b3KhnY";
- передаётся этот URL в Uri.parse: Intent(Intent.ACTION_VIEW, Uri.parse('url'));
- стартуется Activity с созданным Intent'ом.
Задача Хранение данных
Сделана альтернативная реализация репозитория, которая работает с JSON-файлом в качестве постоянного хранилища вместо In Memory.
Задача Details
Приведение проекта к фрагментам.
Добавлен следующий функционал:
- при нажатии на элемент списка - открывается фрагмент с конкретным постом;
- работу с кнопками like, share и menu (редактировать, удалить) также можно проводить и во фрагменте с выбранным постом.
С этого выбранного фрагмента можно попасть:
Если нажать на кнопку изменить, то на фрагмент редактирования.
Если нажать на кнопку назад (системную), то на фрагмент со списком всех постов.
Если нажать на кнопку удалить, то на фрагмент со списком всех постов.
Задача SQL
Произведена миграция проекта на SQLite, с сохранением работоспособности приложения.
Задача Room
Произведена миграция проекта на библиотеку ROOM, с сохранением работоспособности приложения.
Задача Exceptions
Добавлен обработчик ситуации, если в приложение придёт Notification, у которого поле action не соответствует ни одному значению из Enum'а Action.
Задача New Post
Реализовано получение уведомления о новом посте.
Уведомления о новых постах отображаются в формате:
<имя пользователя> опубликовал новый пост:
Текст поста... (на несколько строк)
HomeWorkAndIn (Block 2)
Задача Likes
Предоставлены описания API для реализации:
-
Добавление лайка:
POST /api/posts/{id}/likes
-
Удаление лайка:
DELETE /api/posts/{id}/likes
Где {id} - это идентификатор поста.
В ответ на оба запроса сервер присылает JSON обновленного поста, который можно использовать для отображения измененного поста в ленте.
В проекте реализвана функциональность простановки/снятия лайка. Для этого используется код сервера с лекции.
После выполнения запроса список постов обновляется, для отображения пользователю актуального количества лайков.
Задача Swipe to Refresh*
Реализована функциональность Swipe To Refresh
в списках:
- Пользователь "тянет" сверху вниз список (или любое другое View)
- Появляется иконка обновления
- Список обновляется
Для этого:
- Добавлена необходимая зависимость в build.gradle
- RecyclerView завёрнут
в androidx.swiperefreshlayout.widget.SwipeRefreshLayout
OnRefreshListener
заново запрашивает все посты с сервера
Задача OkHttp enqueue
Перевод функциональности проекта с использования функции thread на enqueue из OkHttp. После выполнения запроса список постов обновляется, для отображения пользователю актуального количества лайков.
Задача Glide
Реализовано отображение аватарок в приложении с использованием проекта сервера.
В качестве библиотеки для загрузки изображений использована библиотека Glide.
Задача Rounded
Для реализации круглых аватарок, среди методов трансформации был выбран наиболее подходящий класс CircleCrop.
Задача Attachments*
Для тех постов, у которых есть вложения (типа - IMAGE) attachment на сервере, реализовано отображение соответствующей картинки в посте :
Задача Buggy Server
Рассмотрен альтернативный (но очень частый) сценарий - сервер периодически (а именно в 50% случаев) присылает не 2xx коды ответа. С его использованием реализована обработка подобного рода ошибок методом вывода на экран пользователя Snackbar'а
Задача remove & likes
Реализована функциональность удаления и проставления лайков.
Логика работы:
- Сначала была модифицирована запись в локальной БД
- Затем отправляется соответствующий запрос в API (HTTP)
Также произведена обработка ошибок и кнопка
Retry
, в случае, если запрос в API завершился с ошибкой (в том числе в случае отсутствия сетевого соединения*).
Примечание*: для этого не обязательно перезапускать сервер, достаточно отключить сеть в шторке телефона/эмулятора.
Задача New Posts
В проекте реализован следующий функционал:
- Посты, загружаемые в фоне (через
getNewer
), не отображаются сразу вRecyclerView
, вместо этого выводится "плашка" как в Vk:
- При нажатии на "плашку" производится плавный скролл
RecyclerView
к самому верху и отображаются загруженные посты (сама "плашка" после этого удаляется).
Задача Photo
На примере загрузки изображений, реализовано их отображение. В качестве аналога взято приложение ВКонтакте.
Если в посте есть картинка, то она отображается внутри этого поста. Если кликнуть на картинку, она открывается на весь экран:
Задача реализована через фрагменты, т.е. при клике на картинку открывается новый фрагмент, на котором изображение выводится на весь экран.
Задача Аутентификация
При нажатии на пункт меню «Sign in» реализована следующая последовательность действий:
1. Открывается фрагмент с полями для ввода логина и пароля и кнопкой «Войти». Для этого фрагмента создана собственная ViewModel
.
2. Происходит отправка запроса пары логин / пароль с получаемым ответом в виде JSON.
3. Далее сохраняется в AppAuth
.
Sign In to ... и Are you sure?*
Реализован следующий функционал:
1. Когда на экране находится лента постов в PostViewModel
, проходит проверка, аутентифицирован ли пользователь. Проверка проходит при:
- добавлении поста (нажатие на
addPost
(+); - лайке поста.
Если пользователь не аутентифицирован, появляется диалоговое окно с предложением пройти аутентификацию. Пользователь перенаправляется на фрагмент аутентификации.
2. При создании поста возникает диалоговое окно с подтверждением выхода, если пользователь в ActionBar
выбрал пункт меню Sign Out
.
Если пользователь подтвердил выход, то перенаправляется на предыдущий фрагмент.
Регистрация*
При нажатии на пункт меню «Sign Up» реализована следующая последовательность действий:
1. Открывается фрагмент с 4 полями для ввода имени, логина, пароля и подтверждения пароля и кнопкой «Зарегистрироваться». Для этого фрагмента создана собственная ViewModel
.
2. Отправляется запрос и в ответ получен JSON.
3. Происходит сохранение в AppAuth
.
Задача RecipientId
Реализована проверка recipientId
при получении push-уведомления с сервера.
Для тестирования при помощи Postman отправлялся запрос вида:
POST http://localhost:9999/api/pushes?token=<--- Used TOKEN is set here --->
Content-Type: application/json
{
"recipientId": null,
"content": "Hello !!!"
}
HomeWorkAndAd (Block 3)
Задача Dagger Hilt
Призведена миграция проекта на Dagger Hilt.
Задача Singletons
В приложении с лекции в MainActivity
используются следующие конструкции:
FirebaseMessaging.getInstance().token.addOnCompleteListener { task ->
...
}
with(GoogleApiAvailability.getInstance()) {
...
}
Заменены вызовы getInstance
на Dependency Injection.
Задача Refresh on Login/Logout
Внесены изменения для реализации запросов с сервера заново при произведении login/logout
.
Задача Refresh to Prepend
Произведены следующие изменения:
- Автоматический PREPEND отключен, т. е. при scroll к первому сверху элементу данные автоматически не подгружаются.
- REFRESH не затирает предыдущий кеш, а добавляет данные сверху, учитывая ID последнего поста сверху.
- APPEND работает в обычном режиме.
Задача Paging Refresh, Prepend & Append
Использовав примеры с лекции и Codelab, посвящённую Paging, в код проекта добавлена, поддержка PREPEND, APPEND и REFRESH, следующего поведения:
- Refreshing SwipeRefreshLayout отображается только при REFRESH.
- При PREPEND первый элемент в RecyclerView - элемент с загрузкой.
- При APPEND последний элемент в RecyclerView - элемент с загрузкой.