Google tasks для Ubuntu Touch

от автора

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

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

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



Продолжение следует…

ссылка на оригинал статьи http://habrahabr.ru/post/177971/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *