Pull to refresh

Google tasks для Ubuntu Touch

Reading time 6 min
Views 12K
image

После новостей о добавлении в дистрибутив Ubuntu Touch приложений и Qt 5 для Android решил посмотреть что представляет собой Ubuntu SDK и написать простое приложение. Выбор пал на google tasks, сейчас приложение проходит авторизацию oauth и получает задачи из выбранного списка. Код приложения доступен на github. Знакомство с QML значительно упростит понимание приведенного кода, некоторые ссылки собраны на этой странице.

Установка


На текущий момент официально доступны пакеты для Ubuntu начиная от 12.04.
После установки будет доступен Qt Creator с дополнительными инструментами разработки от Ubuntu и набор компонентов для интерфейса. Первоначальное ознакомление рекомендую начать с примера на сайте.
Я создал новый проект Qt Quick 2 Application, для большей гибкости. Можно скопировать код из примера в main.qml и запустить приложение. Вывод в консоли QQmlComponent: Component is not ready — это небольшой баг, но и SDK пока в статусе preview.

Авторизация


Для работы с API необходимо зарегистрировать приложение в консоли. Подробное описание в Step 2: Register Your Application
Нам потребуется показать пользователю страницу авторизации приложения и получить токен. QML существует уже длительное время и за основу я взял код из проекта qml-google-tasks. Авторизацию реализуют два файла GoogleOAuth.qml — GUI и google_oauth.js — логика.
Изменяем GoogleOAuth.qml для работы с QtQuick 2 и интеграции с компонентами Ubuntu.
Код
import QtQuick 2.0
import QtWebKit 3.0
import Ubuntu.Components 0.1
import "google_oauth.js" as OAuth

Page {
    id: google_oauth
    title: i18n.tr("Login")
    anchors.fill: parent

    property string oauth_link: "https://accounts.google.com/o/oauth2/auth?" +
                                "client_id=" + OAuth.client_id +
                                "&redirect_uri=" + OAuth.redirect_uri +
                                "&response_type=code" +
                                "&scope=https://www.googleapis.com/auth/tasks" +
                                "&access_type=offline" +
                                "&approval_prompt=force"

    property bool authorized: accessToken != ""
    property string accessToken: ""
    property string refreshToken: ""
    signal loginDone();

    onAccessTokenChanged: {
        console.log('onAccessTokenChanged');
        if(accessToken != ''){
            console.log("accessToken = ", accessToken)
            loginDone();
        }
    }

    function login(){
        loginView.url = oauth_link
    }

    function refreshAccessToken(refresh_token){
        OAuth.refreshAccessToken(refresh_token)
    }

    Flickable {
        id: web_view_window

        property bool loading:  false
        anchors.fill: parent
       
        WebView {
            id: loginView
            anchors.fill: parent

            onUrlChanged: OAuth.urlChanged(url)
        }
    }
}

В google_oauth.js меняем client_id client_secret и redirect_uri на полученные в консоле.
Проверить работу мы можем немного изменив код GoogleOAuth.qml:
Код
import QtQuick 2.0
import QtWebKit 3.0
import Ubuntu.Components 0.1
import "google_oauth.js" as OAuth

Page {
    id: google_oauth
    title: i18n.tr("Login")
    anchors.fill: parent

    property string oauth_link: "https://accounts.google.com/o/oauth2/auth?" +
                                "client_id=" + OAuth.client_id +
                                "&redirect_uri=" + OAuth.redirect_uri +
                                "&response_type=code" +
                                "&scope=https://www.googleapis.com/auth/tasks" +
                                "&access_type=offline" +
                                "&approval_prompt=force"

    property bool authorized: accessToken != ""
    property string accessToken: ""
    property string refreshToken: ""
    signal loginDone();

    onAccessTokenChanged: {
        console.log('onAccessTokenChanged');
        if(accessToken != ''){
            console.log("accessToken = ", accessToken)
            loginDone();
        }
    }

    function login(){
        loginView.url = oauth_link
    }

    function refreshAccessToken(refresh_token){
        OAuth.refreshAccessToken(refresh_token)
    }

    Flickable {
        id: web_view_window

        property bool loading:  false
        //anchors.fill: parent
        //Для тестирования
        width:  800
        height: 800

        WebView {
            id: loginView
            anchors.fill: parent

            onUrlChanged: OAuth.urlChanged(url)
        }
    }

    //Для тестирования
    Component.onCompleted: {
        console.log("onCompleted")
        login()
    }
}

и запустив утилитой qmlscene файл GoogleOAuth.qml (В Qt Creator доступна в меню: Tools > External > Qt Quick > Qt Quick 2 Preview, запускает открытый в текущий момент файл)
После авторизации в логе должны появиться строки:
onAccessTokenChanged
accessToken = xxxx.xxxxxxxxxxxxxxxxxxxxxxxx


Навигация

Структурно интерфейс представляет собой MainView с набором Page, аналогично fragments в android. Для простоты Page я делал в отдельных файлах, в main.qml остались PageStack, отвечающий за навигацию между страницами, и код для переключения страниц.
Код
import QtQuick 2.0
import Ubuntu.Components 0.1

import "tasks_data_manager.js" as TasksDataManager

MainView {
    objectName: "mainView"
    applicationName: "UTasks"
    id: root

    width: units.gu(60)
    height: units.gu(80)

    PageStack {
        id: pageStack
        Component.onCompleted: push(taskLists)

        //Списки задач пользователя
        TaskLists {
            id: taskLists
            visible: false

            onItemClicked: {
                var item = taskLists.curItem
                console.log("onItemClicked: ", item)
                tasks.title = item["title"]
                TasksDataManager.getMyTasks(item["id"])
                pageStack.push(tasks)
            }
        }

        //Задачи в одном списке
        Tasks {
            id: tasks
            visible: false
        }

        //Авторизация и получение токена
        GoogleOAuth {
            id: google_oauth
            visible: false

            onLoginDone: {
                pageStack.clear()
                pageStack.push(taskLists)
                console.log("Login Done")
                //tasks.refreshToken = refreshToken

                settings.setValueFor("accessToken", accessToken)
                settings.setValueFor("refreshToken", refreshToken)

                TasksDataManager.getMyTaskLists()
            }
        }
    }

    //По окончанию запуска компонента проверяем наличие токена
    Component.onCompleted: {
        console.log("onCompleted")
        if (settings.getValueFor("refreshToken") === "") {
            pageStack.push(google_oauth)
            console.log("google_oauth")
            google_oauth.login()
        } else {
            pageStack.push(taskLists)
            google_oauth.refreshAccessToken(settings.getValueFor("refreshToken"))
        }
    }
}



Сохранение

Чтобы не запрашивать авторизацию у пользователя каждый раз необходимо сохранить токен. В QML 2 доступна работа с SQLite, но ради одной строки не хотелось использовать SQL. Я написал небольшой с++ класс TasksSettings — обертку над QSettings. Для работы из QML с объектами с++ необходимо отметить вызываемые методы как Q_INVOKABLE и добавить объект в контекст:
viewer.rootContext()->setContextProperty("settings", &settings);

После этого в QML можно использовать методы getValueFor для получения и setValueFor для установки значений:
settings.setValueFor("refreshToken", refreshToken)
if (settings.getValueFor("refreshToken") === "") {


Списки задач


Интерфейс — TaskLists.qml.
Простой ListView с использованием ListItems из Ubuntu
Код
import QtQuick 2.0
import QtQuick.XmlListModel 2.0
import Ubuntu.Components 0.1
import Ubuntu.Components.ListItems 0.1
import Ubuntu.Components.Popups 0.1

Page {
    id: taskLists
    title: i18n.tr("Lists")
    anchors.fill: parent

    ListModel {
        id: taskListsModel
        ListElement {
            title: "My"
        }
    }

    Flickable {
        id: flickable
        anchors.fill: parent

        ListView {//лист
            id: taskListsView
            model: taskListsModel
            anchors.fill: parent

            delegate: Standard {
                text: title //от куда берется название
                progression: true //стрелка справа
                onClicked: {
                    console.log("index: ", index);
                    taskListsView.currentIndex = index
                    curItem = taskListsModel.get(index)
                    itemClicked()
                }
            }
        }
    }
}

Логика реализована в tasks_data_manager.js, нам необходима функция getMyTaskLists(). Изменяем содержимое onload на
taskLists.itemsList = result["items"];

В TaskLists.qml добавляем список элементов и обрабатываем его изменение:
    property variant itemsList;
    signal itemClicked();

    onItemsListChanged:
    {
        taskListsModel.clear()
        if(itemsList === undefined)
            return

        for(var i = 0; i < itemsList.length; ++i)
        {
            console.log("append:", itemsList[i]["title"], itemsList[i]["id"]);
            var item = itemsList[i]
            taskListsModel.append( item ); //добавляем элемент в модель
        }
    }

И в конце добавляем обработчик нажатия на элемент списка
property variant curItem;
onClicked: {
                   console.log("index: ", index);
                   taskListsView.currentIndex = index
                   curItem = taskListsModel.get(index)
                   itemClicked()
}

В main.qml реализуем переключение на список при нажатии элемента
onItemClicked: {
               var item = taskLists.curItem
               console.log("onItemClicked: ", item)
               tasks.title = item["title"]
               TasksDataManager.getMyTasks(item["id"])
               pageStack.push(tasks)
}


Загрузка списка задач сделана аналогично загрузке списков. Код можно посмотреть в репозитории.

Скриншоты с настольной версии:





Продолжение следует…
Tags:
Hubs:
+19
Comments 4
Comments Comments 4

Articles