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

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

    Автор: Иван Щитов
    • +12
    • 2,6k
    • 1
    Поделиться публикацией
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама
    Комментарии 1
    • 0
      А не подскажете ли, может ли приложение для Sailfish или MeeGo просматривать уведомления других приложений? Я тут развлекаюсь с альтернативным подключением Gear S по Bluetooth (не LE), и подумал, что при желании эти часы можно и к другим платформам подключать. Только надо как-то победить ограничение на спаривание только с одним устройством и не совсем понятно, что при этом будет с батарейкой.

      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

      Интересные публикации