После новостей о добавлении в дистрибутив 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)
}
Загрузка списка задач сделана аналогично загрузке списков. Код можно посмотреть в репозитории.
Продолжение следует…
Автор: gsdstr