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

Автор: Иван Щитов
Поделиться с друзьями
-->

Комментарии (1)


  1. wholeman
    28.12.2016 21:16

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