Здравствуйте! Данная статья является продолжением цикла статей, посвященных разработке приложений для мобильной платформы Sailfish OS. На этот раз речь пойдет о приложении для ведения заметок, позволяющее пользователю хранить записи, помечать их тэгами, добавлять к ним изображения, фотографии, напоминания, а так же синхронизировать с учетной записью Evernote.
Начнем статью с обширного описания пользовательского интерфейса приложения, а потом перейдем к техническим деталям.
Описание приложения
Главный экран приложения представляет собой список записей пользователя, где на каждом элементе списка отображается заголовок заметки, ее описание (или его часть, если описание не умещается полностью), а так же изображение, если оно добавлено. Элементы списка так же имеют контекстное меню, позволяющее удалить заметку или открыть диалог для ее редактирования. А по нажатию на элемент списка открывается экран показывающий всю информацию о данной заметке.
Поговорим детально о пользовательском интерфейсе приложения. Главный экран приложения представляет собой список записей пользователя, где на каждом элементе списка отображается заголовок заметки, ее описание (или его часть, если описание не умещается полностью), а так же изображение, если оно добавлено. Элементы списка так же имеют контекстное меню, позволяющее удалить заметку или открыть диалог для ее редактирования. А по нажатию на элемент списка открывается экран показывающий всю информацию о данной заметке.
Главный экран приложения так же содержит элементы PullDownMenu и PushUpMenu. В PullDownMenu находится лишь один пункт меню, открывающий диалог для добавления новой заметки. В PushUpMenu находится два пункта: первый открывает окно со списком всех тэгов, второй — окно настроек.
Диалог для добавления/редактирования заметки содержит текстовые поля для ввода тэгов, заголовка и описания, а так же кнопки для добавления изображения, фото и напоминания. По нажатию на кнопку «Add a picture» открывается диалог, позволяющий пользователю нарисовать или написать что-либо на экране и добавить это изображение к заметке. А по нажатию на кнопку «Add a photo» открывается камера устройства и по нажатию на экран фотография так же, как и изображение, добавляется к заметке.
Кнопка для добавления напоминания открывает диалог, позволяющий настроить дату и время напоминания, используя стандартные компоненты DatePickerDialog и TimePickerDialog.
Экран «Tags» представляет собой список всех тэгов, добавленных к заметкам. По нажатию на тэг в списке мы попадем на экран, содержащий только записи, помеченные данным тэгом. Все манипуляции с заметками (просмотр информации, редактирование, удаление и добавление новых) доступны нам и с этого экрана.
Экран настроек содержит содержит один пункт «Login via Evernote», который после выполнения авторизации меняется на два пункта: «Logout from Evernote» и «Synchronize data». Первый позволяет выйти из аккаунта Evernote и отключить синхронизацию данных, а второй — запустить процесс синхронизации вручную. Так же синхронизация запускается автоматически при изменении данных.
Nemo QML Plugin Notifications
В данной статье было решено акцентировать внимание на работе с уведомлениями в Sailfish OS. Для работы с уведомлениями Sailfish SDK предоставляет плагин Nemo QML Plugin Notifications. Плагин содержит два класса:
- QML класс Notification для создания уведомлений внутри QML кода;
- C++ класс Notification для создания уведомлений внутри C++ кода.
Класс Notification позволяет создавать экземпляры уведомлений, которые могут быть использованы для связи с Lipstick Notification Manager с помощью D-Bus. О том, что такое D-Bus и как с ним работать мы уже писали в одной из предыдущих статей. Обязательно ознакомьтесь с ней, если еще не сделали этого.
Уведомления формируются с помощью некоторых параметров. Перечислим основные:
- appIcon — путь к иконке приложения, которая будет отображена вместе с самим уведомлением;
- appName — имя приложения, помимо иконки уведомление может отображать и его;
- summary — заголовок уведомления, отображаемый на панели уведомлений;
- previewSummary — заголовок уведомления, отображаемый в баннере уведомлений сверху экрана;
- body — «тело» уведомления, его описание, отображаемое на панели уведомлений;
- previewBody — описание уведомления, отображаемое в баннере уведомлений;
- itemCount — количество уведомлений, отображаемых одним элементом. Например, одно уведомление может отображать до 4-х пропущенных звонков, если параметр itemCount имеет значение 4;
- timestamp — метка времени события, с которым связано уведомление, никак не влияет на создание самого уведомления и не является временем, когда уведомление будет показано;
- remoteActions — список объектов со свойствами «name», «service», «path», «iface», «method», «displayName», «icon» и «arguments», определяет возможные действия по нажатию на созданное уведомление. Подробнее о remoteActions поговорим ниже.
О всех параметрах уведомлений можно прочитать в официальной документации, а ниже приведем пример создания уведомления в QML.
Button {
Notification {
id: notification
appName: "Example App"
appIcon: "/usr/share/example-app/icon-l-application"
summary: "Notification summary"
body: "Notification body"
previewSummary: "Notification preview summary"
previewBody: "Notification preview body"
itemCount: 5
timestamp: "2013-02-20 18:21:00"
remoteActions: [{
"name": "default",
"service": "com.example.service",
"path": "/com/example/service",
"iface": "com.example.service",
"method": "trigger"
"arguments": [ "argument 1" ]
}]
}
onClicked: notification.publish()
}
По коду несложно понять, что по клику на описанную кнопку происходит вызов метода publish(). Метод publish() публикует наше уведомление в Notification Manager и отображает на экране устройства.
Как говорилось выше, мы можем настраивать действия связанные с уведомлением. Пример, напрашивающийся сам — открывать приложение по нажатию на уведомление. Уведомления работают через D-Bus, поэтому первое, что нам нужно — создать собственный сервис D-Bus. Для этого сначала добавим в корень проекта директорию dbus и создадим там файл с расширением *.service со следующим содержанием:
[D-BUS Service]
Interface=/com/example/service
Name=com.example.service
Exec=/usr/bin/invoker --type=silica-qt5 --desktop-file=example.desktop -s /usr/bin/example
Советуем использовать одно имя для имени сервиса (параметр Name) и имени самого файла, чтобы избежать путаницы в дальнейшем. Так же обратите внимание на то, что в параметре Exec используются пути до *.desktop файла вашего проекта и самого приложения на устройстве, здесь вместо «example» Вы должны использовать имя проекта.
Далее необходимо прописать пути до сервиса D-Bus в *.pro файле.
...
dbus.files = dbus/com.example.service.service
dbus.path = /usr/share/dbus-1/services/
INSTALLS += dbus
...
А также в *.spec файле.
...
%files
%{_datadir}/dbus-1/services
...
Чтобы иметь возможность связать действие над уведомлением с приложением, необходимо создать DBusAdaptor. DBusAdaptor — объект, предоставляющий возможность взаимодействовать с D-Bus сервисом.
DBusAdaptor {
service: 'com.example.service'
iface: 'com.example.service'
path: '/com/example/service'
xml: ' <interface name="com.example.service">n' +
' <method name="trigger">n' +
' <arg name="param" type="s" access="readwrite"/>n"' +
' </method">n' +
' </interface>n'
function trigger(param) {
console.log('param:', param);
__silica_applicationwindow_instance.activate();
}
}
Свойства service и iface являются именем зарегистрированного нами D-Bus сервиса, а свойство path — путь до объекта сервиса в файловой системе устройства. Особый интерес представляет свойство xml. Оно описывает содержимое сервиса, а именно имя метода, который может быть вызван, и его аргументы. Здесь мы используем в качестве метода сервиса функцию trigger(), которая принимает на вход строку и выводит ее в консоль, а так же открывает приложение вызовом метода activate() на объекте ApplicationWindow.
Теперь нам необходимо связать наше действие с созданным уведомлением. В этом нам поможет свойство remoteActions класса Notification.
Button {
Notification {
...
remoteActions: [{
"name": "default",
"service": "com.example.service",
"path": "/com/example/service",
"iface": "com.example.service",
"method": "trigger"
"arguments": [ "argument 1" ]
}]
}
onClicked: notification.publish()
}
В remoteActions описываем параметры service, path и iface для связи с D-Bus сервисом. Параметр method есть имя метода сервиса, а в свойстве arguments передаем список параметров для метода. И на этом все. Теперь по нажатию на уведомление будет вызываться метод D-Bus сервиса, открывающий приложение.
Одной особенностью работы с уведомлениями является то, что для их отображения при закрытом приложении потребуется активный демон, управляющий сервисом, зарегистрированным в D-Bus. Поскольку после закрытия сервис разрегистрируется. Об этом написано и в Sailfish FAQ.
Работа с уведомлениями в приложении
Для реализации работы с уведомлениями в приложении мы использовали С++ класс Notification. Уведомления в приложении состоят из заголовка и описания заметки, к которой добавлено напоминание, поэтому нас интересуют только следующие свойства класса: summary, body, previewSummary и previewBody. Само собой нас так же интересует метод publish().
Для управления уведомлениями нами был создан класс NotificationManager, содержащий два метода publishNotification() и removeNotification(). Первый необходим для создания и отображения напоминания, второй — для удаления напоминания.
Стоит отметить, что класс Notification не предоставляет возможности установить время показа уведомления, метод publish() отображает уведомление ровно в тот момент, когда он (метод) был вызван. Эта проблему мы решили использованием таймера (класса QTimer) для управления временем показа уведомления. А так как заметок с напоминаниями может быть несколько, то и таких таймеров тоже должно быть несколько. Поэтому в классе NotificationManager был создан хэш (QHash), ключом которого является id заметки в базе данных, а значением — QTimer.
class NotificationManager : public QObject {
Q_OBJECT
public:
explicit NotificationManager(QObject *parent = 0);
Q_INVOKABLE void publishNotification(const int noteId, const QString &summary,
const QString &body, QDateTime dateTime);
Q_INVOKABLE void removeNotification(const int noteId);
private:
QHash<int, QTimer*> timers;
};
Рассмотрим подробнее реализацию метода publishNotification(). В качестве аргументов он принимает id заметки в базе данных, заголовок и описание уведомления, а так же дату и время, когда уведомление должно быть показано.
Первым делом метод создает новый таймер и связывает его сигнал timeout() со слотом, описанным лямбда-функцией. В лямбда-функции происходит создание и настройка уведомления, а так же вызов метода publish(). После того, как уведомление было показано, мы останавливаем наш таймер. Так же метод publishNotification() проверяет был ли таймер с таким id записи уже добавлен в наш хэш, и если это так, то удаляет его из хэша. Далее запускаем таймер и устанавливаем, через какое время (в миллисекундах) он должен остановиться и добавляем новый таймер в хэш.
void NotificationManager::publishNotification(const int noteId, const QString &summary,
const QString &body, QDateTime dateTime) {
QTimer *timer = new QTimer();
connect(timer, &QTimer::timeout, [summary, body, timer](){
Notification notification;
notification.setSummary(summary);
notification.setBody(body);
notification.setPreviewSummary(summary);
notification.setPreviewBody(body);
notification.publish();
timer->stop();
});
if (this->timers.contains(noteId)) removeNotification(noteId);
timer->start(QDateTime::currentDateTime().secsTo(dateTime) * 1000);
this->timers[noteId] = timer;
}
Метод removeNotification() выглядит гораздо проще. В качестве параметра он принимает только id заметки, для которой нужно удалить уведомление. Метод проверяет, что таймер с данным id уже есть в хэше с таймерами, и если это так, то останавливает таймер и удаляет его из хэша.
void NotificationManager::removeNotification(const int noteId) {
if (this->timers.contains(noteId)) {
this->timers.value(noteId)->stop();
this->timers.remove(noteId);
}
}
Заключение
В результате было создано приложение с широким функционалом, позволяющее хранить заметки с изображениями, тэгами и напоминаниями и синхронизировать их с Evernote. Приложение было опубликовано в магазине приложений Jolla Harbour под названием SailNotes и доступно для скачивания всем желающим. Исходники приложения доступны на GitHub.
Технические вопросы можно также обсудить на канале русскоязычного сообщества Sailfish OS в Telegram или группе ВКонтакте.
Автор: Иван Щитов
Автор: FRUCT