Scope是一組數據的定製視圖,可以使用定製的佈局、顯示和品牌建立選項。從RSS新聞推送到天氣數據和搜索引擎結果,Scope的靈活性使您可以使用其他OS提供簡單、明確且一致的體驗。Scope也可與系統範圍內的賬戶集成(電子郵件、社交網絡…),將您的內容分爲多個類別並在各個類別 中進行集合(例如,「shopping」Scope集合了多個商店Scope的結果)。web
在本教程中,您將瞭解如何使用Ubuntu SDK編寫SouldCloud的C++Scope。在本示例中,只須要很是少的C++知識,將其根據暴露JSON API其餘服務來調整也很是簡單。json
注意:本教程也適用於Ubuntu 14.04及更高版本。若是您但願使用Scope佈局工具,則至少要使用Ubuntu 14.10。ubuntu
SDK提供適用於多種不一樣應用程序類型的多種模板。C++Scope有本身的模板,這也是咱們將使用的模板。單擊「New Project」按鈕來建立新Scope項目。系統將要求您填入一些值來生成該項目。api
若是您須要得到更多有關SDK入門指南的幫助,請查看SDK設置文章。安全
注意:即便您要使用平臺的安全策略,您還須要瞭解有關Scope的另外一件事:若是您在某個時刻須要使用網絡,您將沒法訪問用戶數據。這是一項合理的隱私政策,以免在未獲得明確許可的狀況下提取用戶數據。網絡
在本教程的任一點,您均可按下SDK側欄上的Play按鈕來測試您手機上或仿真器上的Scope。等待幾秒,項目的生成並上傳到設備後,項目應會自動打開。數據結構
您可經過運行如下項目得到本教程的源代碼app
$ bzr branch lp:~davidc3/ubuntu-sdk-tutorials/scope-tutorial-soundcloud-qjson編輯器
生成的項目包含至關多的文件,咱們將討論其中最重要的一些文件。須要注意的一點是:模板已提供了一個正在使用的Scope:使用openweathermap.org的天氣Scope。咱們將對其進行更改,使其從SoundCloud中提取結果。ide
其能容將由生成系統用於生成點擊數據包,您可以在Ubuntu Store內安裝和發佈該點擊數據包。大多數狀況下,您可保留從開發人員環境中提取的默認值。
您的Scope使用的安全策略組。咱們的示例中爲無,由於咱們使用的「ubuntu-scope-network」模板已經許可網絡調用。瞭解更多安全策略組。
一個很是重要的文件,將容許您自定義和推廣您的Scope(圖標、背景圖像、顏色…)。咱們稍後將看到相關狀況。
咱們的HTTP配置:用戶代理和基礎API URL。讓咱們更改SoundCloud API URL的apiroot,完成首個更改。
15
|
|
其餘URL參數稍後將經過net-cpp庫添加。
咱們的C++標頭的其他部分。以下所示,更改client.h標頭,以匹配SoundCloud API的數據結構。您可保留標頭的其他部分不變。
這是個人Client類如今的外觀。您可經過將教程文件的內容粘貼到您本身的文件中進行嘗試:
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
|
class
Client {
public
:
/**
* Our Artist object.
*/
struct
Artist {
unsigned
int
id;
std::string username;
std::string avatar_url;
};
/**
* Track info, including the artist.
*/
struct
Track {
unsigned
int
id;
std::string title;
std::string uri;
std::string artwork_url;
std::string stream_url;
std::string description;
std::string genre;
Artist artist;
};
/**
* A list of Track objects.
*/
typedef
std::deque<Track> TrackList;
/**
* Track results.
*/
struct
TrackRes {
TrackList tracks;
};
Client(Config::Ptr config);
virtual
~Client() =
default
;
/**
* Get the track list for a query
*/
virtual
TrackRes tracks(
const
std::string &query);
/**
* Cancel any pending queries (this method can be called from a different thread)
*/
virtual
void
cancel();
virtual
Config::Ptr config();
protected
:
void
get(
const
core::net::Uri::Path &path,
const
core::net::Uri::QueryParameters ¶meters,
QJsonDocument &root);
/**
* Progress callback that allows the query to cancel pending HTTP requests.
*/
core::net::http::Request::Progress::Next progress_report(
const
core::net::http::Request::Progress& progress);
/**
* Hang onto the configuration information
*/
Config::Ptr config_;
/**
* Thread-safe cancelled flag
*/
std::atomic<
bool
> cancelled_;
};
|
咱們的API客戶端。它提供Scope代碼和HTTP API訪問之間的隔離。其惟一的做用是檢索SoundCloud中的數據。
該文件定義類型類unity::scopes::ScopeBase,該類型類提供客戶端用於與Scope互動的輸入點API。
它實行啓動和中止方法。不少Scope都保持這些方法處於不修改的狀態,本示例也同樣。
它還實行兩個關鍵方法:搜索和預覽。這些方法通常不須要修改,本示例也未修改。可是,以下所述,它們調用每一個Scope須要實行的關鍵方法。
注意:您可能會發如今相應的標頭文件:include/scope/scope.h中檢查ScopeBase類聲明(其API)頗有用。該標頭文件是瞭解C++類的絕佳方法,由於它們的API無需其餘任何實行代碼便可聲明,理解很是容易。
提示:若是您但願深刻了解各類特定類,請在本教程期間查看Unity 8 Scope API參考文件。
咱們在該位置發送查詢到API客戶端,傳輸返回的結果到結果卡,聲明將託管這些卡及其佈局的類別。
該文件定義一個類型類unity::scopes::SearchQueryBase。
該類從客戶端提供的查詢字符串生成搜索結果,並將其返回做爲對客戶端的回覆:
接收來自客戶端的查詢字符串
接收來自客戶端的回覆對象
發送查詢到API客戶端
建立搜索結果類別(對於有不一樣佈局的示例:grid/carousel)
將每一個搜索結果與其類別結合(建立CategorisedResult對象)
推送分類結果到回覆對象中,由客戶端進行顯示
在運行方法中完成了大量的編碼規則,咱們在此處只需完成最少的變更。
檢查相應標頭文件:include/scope/query.h中的SearchQueryBase類聲明(其API)。
該關鍵文件定義一個類型類unity::scopes::PreviewQueryBase。
該類定義預覽階段每一個搜索結果使用的小工具和佈局。它:
定義預覽中使用的小工具
每一個結果中針對數據字段的Maps小工具字段
定義有不一樣列數的佈局——取決於顯示大小的不一樣,僅由客戶端在顯示時間瞭解。
分配小工具到每一個佈局的各列
接收回復對象,推送由客戶端使用的小工具和佈局到對象上
檢查相應標頭文件:include/scope/preview.h中的SearchPreviewBase類聲明(其API)。
對於預覽小工具列表和文檔,請參閱本頁。
當咱們深刻了解咱們的示例Scope並詳細說明一些代碼,從查詢開始。
在src/scope/query.cpp中,您可輕鬆看到Scope的哪一個位置在接收用戶查詢。在Scope打開後,該查詢爲空白,您將爲本示例提供一些數據。這是呈現特點類容或最新/流行項目的好機會。
在這裏,我剛剛觸發了一個有關字符串「blur cover」的搜索,該搜索將推送至API客戶端,由於SoundCloud爲本身的歌曲設置了美觀的封面。您可能但願看到更明確的解釋,但就本示例來看,讓咱們假設這是咱們用戶的一個好起點。修改Query::run方法,使其如此處所示,或者只需將教程文件的內容粘貼到您本身的方法中:
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
|
void
Query::run(sc::SearchReplyProxy
const
& reply) {
try
{
// Start by getting information about the query
const
sc::CannedQuery &query(sc::SearchQueryBase::query());
// Trim the query string of whitespace
string query_string = alg::trim_copy(query.query_string());
Client::TrackRes trackslist;
if
(query_string.empty()) {
// If the string is empty, provide a specific one
trackslist = client_.tracks(
"blur cover"
);
}
else
{
// otherwise, use the query string
trackslist = client_.tracks(query_string);
}
(...)
|
讓咱們移至api/client.cpp,獲取來自SoundCloud的一些結果…
net-cpp是一個咱們將用於查詢API的簡單聯網庫。可是,您能夠用替換他,並使用其餘任何知足您目的的聯網庫。模板已提供一個使用net- cpp處理HTTP標題和錯誤的get方法,解析回覆並返回一個JSON對象,該操做很方便,將執行大多數JSON API的工做。只需粘貼教程文件中的內容到本身的方法中,或執行如下步驟,便可嘗試該方法。
基礎URL來自咱們的配置標頭,咱們只需添加咱們的路徑和參數其他部分便可:
60
61
|
get( {
"tracks.json"
}, { {
"client_id"
,
"apigee"
}, {
"q"
, query } }, root);
|
註釋client_id:若是您但願分發SoundCloudScope,您將須要在SoundCloud Developers中註冊本身的API鍵(免費,只需花費 5分鐘)。在上述例子中,我使用示例鍵。
而後,咱們須要迭代根JSON中顯示的每一個結果,而後提取咱們須要的結果。如下是咱們的完整方法:
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
|
Client::TrackRes Client::tracks(
const
string& query) {
QJsonDocument root;
// Build a URI and get the contents.
// The fist parameter forms the path part of the URI.
// The second parameter forms the CGI parameters.
get( {
"tracks.json"
}, { {
"client_id"
,
"apigee"
}, {
"q"
, query } }, root);
// My 「list of tracks」 object (as seen in the corresponding header file)
TrackRes result;
QVariantList variant = root.toVariant().toList();
for
(
const
QVariant &i : variant) {
QVariantMap item = i.toMap();
QVariantMap user = item[
"user"
].toMap();
string art;
// If the track artwork is empty, we use the artist picture
if
(item[
"artwork_url"
].toString().toStdString() ==
""
) {
art = user[
"avatar_url"
].toString().toStdString();
}
else
{
art = item[
"artwork_url"
].toString().toStdString();
}
cout << item[
"title"
].toString().toStdString();
// We add each result to our list
result.tracks.emplace_back(
Track {
item[
"id"
].toUInt(), item[
"title"
].toString().toStdString(),
item[
"uri"
].toString().toStdString(), art,
item[
"stream_url"
].toString().toStdString(),
item[
"description"
].toString().toStdString(),
item[
"genre"
].toString().toStdString(),
Artist {
user[
"id"
].toUInt(),
user[
"username"
].toString().toStdString(),
user[
"avatar_url"
].toString().toStdString()
}
}
);
}
return
result;
}
|
就是這樣了。咱們已經得到所需的數據,下面將開始瞭解如何按照咱們喜歡的方式顯示這些數據。
每一個結果都須要在一個類別內顯示。對於UI,一個類別可爲一列結果提供一個標頭標題和一個具體的佈局,佈局說明結果的放置方式和外觀。經過粘貼教程文件的內容到本身的方法中,或執行如下步驟,嘗試這一操做。
CategoryRenderer經過JSON對象建立。這些渲染器做爲原始字符串建立。JSON對象有兩個涉及直接興趣的字段:模板和組件。
修改src/scope/query.cpp上的類別,使其相似與如下類別:
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
|
const
static
string TRACKS_TEMPLATE =
R"(
{
"schema-version"
: 1,
"template"
: {
"category-layout"
:
"grid"
,
"card-layout"
:
"horizontal"
,
"card-size"
:
"large"
},
"components"
: {
"title"
:
"title"
,
"art"
: {
"field"
:
"art"
},
"subtitle"
:
"artist"
}
}
)";
|
這將顯示簡單的結構列表,它是不少Scope中使用的類別樣式,與不少不一樣的內容類型都能兼容。您可查看unity::scopes::CategoryRenderer doc中的全部選項。
如今,在try{}部分/Query::run方法中,咱們能夠在回覆對象行登記咱們的類別:
77
78
79
80
81
82
|
// Register a category for tracks
auto tracks_cat = reply->register_category(
"tracks"
,
""
,
""
,
sc::CategoryRenderer(TRACKS_TEMPLATE));
// register_category(arbitrary category id, header title, header icon, template)
// In this case, since this is the only category used by our scope,
// it doesn’t need to display a header title, we leave it as a blank string.
|
要使這個SoundCloudScope有用,咱們但願每一個結果至少都擁有:
URI:曲目頁面的連接(必要)
類別:如上所示,它決定告終果在UI中的顯示位置和方式(必要)
標頭:曲目的名稱
藝術家:品牌/藝術家的名稱
視覺:專輯/曲目封面
確保您已在類別模板組件中定義的每一個字段都在結果中顯示,即便這些字段爲空。無效結果將自動棄置。
仍是在src/scope/query.cpp中,在try{}部分/咱們的Query::run方法中,咱們須要迭代咱們的曲目列表,爲每一個曲目建立一個unity::scope::CategorisedResult。將教程文件的類容粘貼到您本身的方法中,或複製如下行:
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
|
for
(
const
auto &;track : trackslist.tracks) {
// Use the tracks category
sc::CategorisedResult res(tracks_cat);
// We must have a URI
res.set_uri(track.uri);
// Our result also needs a track title
res.set_title(track.title);
// Set the rest of the attributes, art, artist, etc.
res.set_art(track.artwork_url);
res[
"artist"
] = track.artist.username;
res[
"stream"
] = track.stream_url;
// Push the result
if
(!reply->push(res)) {
// If we fail to push, it means the query has been cancelled.
return
;
}
}
|
如您所見,您可爲某些字段使用特定方法(set_art、set_uri…),也可添加自定義字段(artist、stream、duration…)。
該預覽須要生成小工具,並鏈接其字段到CategorisedResult中的數據字段。
它還將生成處理不一樣顯示環境的佈局。想法是僅由客戶端了解佈局上下文。客戶端在思考佈局上下文時要考慮可用的列數。對於有不一樣列數的佈局,Scope定義哪些列用於放入小工具。
首先,讓咱們來了解一下小工具。
如下是一組預約義的預覽小工具。每一個小工具都有一個您用於建立的輸入字段。每一個小工具類型也有其餘的字段,具體狀況因小工具類型的不一樣而變。
您可看到此處提供的預覽小工具類型和字段列表。
本示例使用三種類型的預覽小工具:
標頭:有一個標題和一個副標題字段
圖像:有用於檢索藝術形式的源字段
操做:用戶單擊預覽時,用於提供按鈕文本「Open」和已打開的URI
此處示範咱們的示例如何建立名爲w_header的標頭小工具(在src/scope/preview.cpp的Preview::run方法中):
40
|
sc::PreviewWidget w_header(
"headerId"
,
"header"
);
|
首個參數爲一個隨意的ID。咱們使用這些ID分配小工具到不一樣的佈局,稍後將展現這一操做。
第二個參數是預覽小工具類型,預約義類型組中的一個類型。
在建立小工具後,小工具字段中將填入由客戶端處理的CategorisedResult中的數據。咱們的w_header小工具的標準字段:標題和副標題已填充。
有兩種可用的方法用於在小工具字段中填入數據:
add_attribute_value(FIELD, VALUE):您可以使用該方法將您手邊的數據填充到小工具字段中
add_attribute_mapping(FIELD, CR_FIELD):使用該方法填充待處理的CategorisedResult中的數據到小工具字段中。
在咱們的示例中,小工具數據經過當前CategorisedResult提取,add_attribute_mapping也使用該方法。
首先,當咱們將w_header小工具的標題字段(第一個參數)映射到當前CategorisedResult的標題字段(第二個參數):
42
|
w_header.add_attribute_mapping(
"title"
,
"title"
);
|
接下來的示例會更有趣,由於咱們將從CategorisedResult(不屬於CategoryRenderer)字段填充小工具字段。該字段爲 藝術家。對於以前的每一個結果,咱們已經直接在CategorisedResult中添加藝術家鍵和值。所以,本示例說明當數據未在結果階段顯示並定製到 Scope時如何在預覽中顯示數據:
43
|
w_header.add_attribute_mapping(
"subtitle"
,
"artist"
);
|
回看查詢,即建立CategorisedResults的位置,咱們再次瞭解藝術家數據如何在CategorisedResult提供:
84
|
res[
"artist"
] = track.artist.username;
|
所以,每一個CategorisedResult都有一個「藝術家」字段,該字段由搜索結果填充。在此預覽階段,咱們將藝術家數據推送到w_header小工具預約義的副標題字段。
教程文件的內容可粘貼到本身的方法中,以試用這些小工具。
如下是咱們的變動結果:
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
|
// Define the header section
sc::PreviewWidget w_header(
"headerId"
,
"header"
);
// It has title and a subtitle properties
w_header.add_attribute_mapping(
"title"
,
"title"
);
w_header.add_attribute_mapping(
"subtitle"
,
"artist"
);
// Define the image section
sc::PreviewWidget w_art(
"imageId"
,
"image"
);
// It has a single source property, mapped to the result's art property
w_art.add_attribute_mapping(
"source"
,
"art"
);
// Define the actions section
sc::PreviewWidget w_actions(
"actionsId"
,
"actions"
);
// Actions are built using tuples with an id, a label and a URI
sc::VariantBuilder builder;
builder.add_tuple({
{
"id"
, sc::Variant(
"open"
)},
{
"label"
, sc::Variant(
"Open"
)},
{
"uri"
, result[
"uri"
]}
});
w_actions.add_attribute_value(
"actions"
, builder.end());
|
如今,它們可與回覆對象一同推送到客戶端:
61
|
reply->push( { w_art, w_header, w_actions });
|
小工具已建立、填充和推送。可是,客戶端也須要了解放置小工具的位置,以及如何在不一樣的上下文中以美觀的方式安排小工具,例如,一個窄屏和一個寬屏,讓咱們一塊兒查看佈局。
咱們的示例定義了兩個佈局:一個有一列,另外一個有兩列。這些佈局以下所示進行聲明:
27
|
sc::ColumnLayout layout1col(1), layout2col(2);
|
提示:查看ColumnLayout文檔(此處)。
咱們沒必要具體瞭解如何客戶端如何使用這些佈局。可是,通常的預期是,單列布局與窄屏狀況相配(好比素描模式),雙列布局可能與寬屏狀況相配(好比景觀模式)。
如今,如您在教程文件src/scope/preview.cpp中所見,咱們須要定義三個小工具在每一個佈局中的放置位置。
天然狀況下,在單列布局中,全部小工具必須放入單列中:
30
|
layout1col.add_column( {
"imageId"
,
"headerId"
,
"actionsId"
});
|
在雙列布局中,咱們決定添加圖像到第一列,標頭和操做添加到第二列:
33
34
|
layout2col.add_column( {
"imageId"
});
layout2col.add_column( {
"headerId"
,
"actionsId"
});
|
如今,咱們須要在回覆對象中註冊佈局,方法以下所示:
37
|
reply->;register_layout({layout1col, layout2col});
|
默認狀況下,您的Scope將以下所示:
不少顯示選項均可在data/<appid>.ini中進行更改。如下是我爲推廣該Scope的最佳作法,大多數選項都是自明式選項:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
[ScopeConfig]
DisplayName = SoundCloud
Description = This is a SoundCloud scope doing SoundCloud things
Art = screenshot.png
Author = Firstname Lastname
Icon = icon.png
[Appearance]
PageHeader.Logo = logo.png
PageHeader.background = color:///#FFFFFF
PageHeader.ForegroundColor = #F8500F
BackgroundColor = #FFFFFF
PageHeader.DividerColor = #F8500F
PreviewButtonColor = #F8500F
|
我也找到了這個SoundCloud徽標來替換模板中提供的徽標。下載它,將其保存爲data/logo.png。
若是您調整類別佈局和顏色,您可獲得差別很是大的樣式。左側的佈局是使用上述代碼片斷生成的:
請查看全部可用的自定義選項並嘗試讓您的Scope美觀起來!
這就是了,咱們的SoundCloudScope完成了。您可按下SDK側欄的Start按鈕啓動該Scope,在編輯器的底部查看是否全部內容都已編寫完成且正確啓動,而後試用您的新Scope!
咱們已看到如何建立可查詢web API的Scope
查詢結果將經過一個獨特的渲染器放入一個類別
客戶端顯示搜索結果
對於預覽階段,使用了四個預約義的小工具類型
多個佈局已建立,在佈局中小工具採用不一樣的佈置方法,以在數個外觀設置中獲得美觀的顯示效果
一些僅與此Scope相配的自定義數據(例如藝術家)在預覽和結果中顯示
Scope是強大的工具,可幫助用戶訪問信息和選中的內容。Ubuntu已提供大量默認的Scope,但咱們老是能夠建立更多的Scope!
新API的收藏夾源(書籍、電影等)轉換到Scope中爲ProgrammableWeb API目錄,但還有其餘多種不一樣的源。請隨意實踐不一樣的佈局和卡,以包含不一樣類型的數據!
發佈Scope與發佈其餘應用程序徹底一致,請查看咱們的發佈指南,以用數分鐘的時間在店內發佈您的Scope。