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。