Development for Sailfish OS: working with notifications using an example note-taking application

    Hello! This article is a continuation of a series of articles on application development for the Sailfish OS mobile platform. This time we will focus on the application for taking notes, which allows the user to store notes, tag them, add images, photos, reminders to them, as well as synchronize them with an Evernote account.

    We start the article with an extensive description of the user interface of the application , and then move on to the technical details .

    Application description


    The main screen of the application is a list of user entries, where on each element of the list is displayed the title of the note, its description (or its part, if the description does not fit completely), as well as the image if it is added. List items also have a context menu that allows you to delete a note or open a dialog for editing it. And by clicking on the list item, a screen opens showing all the information about this note.
    Let's talk in detail about the user interface of the application. The main screen of the application is a list of user entries, where on each element of the list is displayed the title of the note, its description (or its part, if the description does not fit completely), as well as the image if it is added. List items also have a context menu that allows you to delete a note or open a dialog for editing it. And by clicking on the list item, a screen opens showing all the information about this note.



    The main screen of the application also contains the PullDownMenu and PushUpMenu elements . In PullDownMenu there is only one menu item that opens a dialog for adding a new note. There are two items in PushUpMenu : the first opens a window with a list of all tags, the second - the settings window.



    The dialog for adding / editing notes contains text fields for entering tags, title and description, as well as buttons for adding images, photos and reminders. By clicking on the “Add a picture” button, a dialog opens allowing the user to draw or write something on the screen and add this image to the note. And by pressing the “Add a photo” button, the device’s camera opens and by clicking on the screen, the photo, like the image, is added to the note.



    The button for adding a reminder opens a dialog allowing you to set the date and time of the reminder using the standard components DatePickerDialog and TimePickerDialog .


    The Tags screen is a list of all tags added to notes. By clicking on a tag in the list, we will be taken to a screen containing only entries marked with this tag. All manipulations with notes (viewing information, editing, deleting and adding new ones) are available to us from this screen.



    The settings screen contains one “Login via Evernote” item, which after authorization changes to two items: “Logout from Evernote” and “Synchronize data”. The first allows you to log out of your Evernote account and disable data synchronization, and the second allows you to start the synchronization process manually. Also, synchronization starts automatically when data changes.



    Nemo QML Plugin Notifications


    In this article, it was decided to focus on working with notifications in Sailfish OS. To work with notifications, the Sailfish SDK provides the Nemo QML Plugin Notifications plugin . The plugin contains two classes:

    • QML class Notification for creating notifications inside QML code;
    • C ++ Notification class for creating notifications inside C ++ code.

    The Notification class allows you to instantiate notifications that can be used to communicate with the Lipstick Notification Manager using D-Bus. We already wrote about what D-Bus is and how to work with it in one of the previous articles . Be sure to check it out if you have not already done so.

    Notifications are generated using some parameters. We list the main ones:

    • appIcon - path to the application icon that will be displayed along with the notification itself;
    • appName - the name of the application, in addition to the notification icon, it can also display it;
    • summary - notification title displayed in the notification panel;
    • previewSummary - notification title displayed in the notification banner at the top of the screen;
    • body - the “body” of the notification, its description displayed on the notification panel;
    • previewBody - notification description displayed in the notification banner;
    • itemCount - the number of notifications displayed by one item. For example, one notification can display up to 4 missed calls, if itemCount is 4;
    • timestamp - timestamp of the event with which the notification is associated does not affect the creation of the notification itself and is not the time when the notification will be shown;
    • remoteActions - a list of objects with the properties "name", "service", "path", "iface", "method", "displayName", "icon" and "arguments", determines the possible actions by clicking on the created notification. We 'll talk more about remoteActions below.

    You can read about all notification parameters in the official documentation , and below is an example of creating a notification in 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()
    }
    

    From the code it’s easy to understand that by clicking on the described button, the publish () method is called . The publish () method publishes our notification to Notification Manager and displays it on the device screen.

    As mentioned above, we can customize the actions associated with the notification. An example that suggests itself is to open the application by clicking on the notification. Notifications work through D-Bus, so the first thing we need is to create our own D-Bus service. To do this, first add the dbus directory to the project root and create a file with the * .service extension with the following contents:

    [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
    

    We recommend that you use the same name for the service name ( Name parameter ) and the name of the file itself, to avoid any further confusion. Also, pay attention to the fact that the Exec parameter uses the paths to the * .desktop file of your project and the application on the device, here instead of “example” you should use the name of the project.

    Next, you need to register the paths to the D-Bus service in the * .pro file.

    ...
    dbus.files = dbus/com.example.service.service
    dbus.path = /usr/share/dbus-1/services/
    INSTALLS += dbus
    ...
    

    And also in the * .spec file.

    ...
    %files
    %{_datadir}/dbus-1/services
    ...
    

    To be able to associate a notification action with an application, you need to create a DBusAdaptor . DBusAdaptor - an object that provides the ability to interact with the D-Bus service.

    DBusAdaptor {
        service: 'com.example.service'
        iface: 'com.example.service'
        path: '/com/example/service'
        xml: '  \n' +
             '    \n' +
             '      \n"' +
             '    \n' +
             '  \n'
        function trigger(param) {
            console.log('param:', param);
            __silica_applicationwindow_instance.activate();
        }
    }
    

    The service and iface properties are the name of the D-Bus service that we registered, and the path property is the path to the service object in the device file system. Of particular interest is the xml property . It describes the contents of the service, namely the name of the method that can be called, and its arguments. Here we use the trigger () function as a service method , which takes a string as input and displays it in the console, and also opens the application by calling the activate () method on the ApplicationWindow object .

    Now we need to associate our action with the created notification. The remoteActions property of the class will help us in this.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()
    }
    

    In remoteActions we describe the service , path and iface parameters for communication with the D-Bus service. The method parameter is the name of the service method, and in the arguments property, we pass a list of parameters for the method. And that is all. Now, by clicking on the notification, the D-Bus service method will be called, which opens the application.

    One feature of working with notifications is that to display them when the application is closed, you will need an active daemon that manages the service registered in D-Bus. Because after closing the service will be unregistered. This is also written in the Sailfish FAQ .

    Work with notifications in the application


    To implement work with notifications in the application, we used the C ++ Notification class . Notifications in the application consist of the title and description of the note, to which a reminder is added, so we are only interested in the following class properties: summary , body , previewSummary and previewBody . Of course, we are also interested in the publish () method .



    To manage notifications, we created the NotificationManager class , which contains two publishNotification () and removeNotification () methods . The first is necessary to create and display a reminder, the second to delete a reminder.

    It is worth noting that the Notification class does not provide the ability to set the time for displaying the notification; the publish () method displays the notification exactly at the moment when it (the method) was called. We solved this problem by using a timer ( QTimer class ) to control the time the notification was displayed. And since there may be several notes with reminders, there should also be several such timers. Therefore, in the class NotificationManagera hash ( QHash ) was created , the key of which is the note id in the database, and the value is 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 timers;
    };
    

    Let us consider in more detail the implementation of the publishNotification () method . As arguments, it takes the id notes in the database, the title and description of the notification, as well as the date and time when the notification should be shown.

    First of all, the method creates a new timer and associates its timeout () signal with the slot described by the lambda function. In the lambda function, the notification is created and configured, as well as the publish () method is called . After the notification has been shown, we stop our timer. Also publishNotification () methodchecks if a timer with such a record id has already been added to our hash, and if so, removes it from the hash. Next, start the timer and set the time after which (in milliseconds) it should stop and add a new timer to the hash.

    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;
    }
    

    The removeNotification () method looks much simpler. As a parameter, it accepts only the id of the note for which you want to delete the notification. The method checks that the timer with the given id is already in the hash with timers, and if so, it stops the timer and removes it from the hash.

    void NotificationManager::removeNotification(const int noteId) {
        if (this->timers.contains(noteId)) {
            this->timers.value(noteId)->stop();
            this->timers.remove(noteId);
        }
    }
    

    Conclusion


    As a result, an application with wide functionality was created that allows you to store notes with images, tags and reminders and synchronize them with Evernote. The app was published in the Jolla Harbor app store under the name SailNotes and is available for download to everyone. Application sources are available on GitHub .

    Technical issues can also be discussed on the channel of the Russian-speaking community Sailfish OS in Telegram or the VKontakte group .

    Author: Ivan Shchitov

    Also popular now: