Pull to refresh

Разработка для Sailfish OS: работа с уведомлениями на примере приложения для ведения заметок

Reading time8 min
Views4.7K
Здравствуйте! Данная статья является продолжением цикла статей, посвященных разработке приложений для мобильной платформы 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 или группе ВКонтакте.

Автор: Иван Щитов
Tags:
Hubs:
+12
Comments1

Articles