После новостей о добавлении в дистрибутив 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/
Добавить комментарий