Scope是一组数据的定制视图,可使用定制的布局、显示和品牌创建选项。从RSS新闻推送到天气数据和搜索引擎结果,Scope的灵活性使您能够使用其余OS提供简单、明确且一致的体验。Scope也可与系统范围内的帐户集成(电子邮件、社交网络…),将您的内容分为多个类别并在各个类别 中进行集合(例如,“shopping”Scope集合了多个商店Scope的结果)。
在本教程中,您将了解如何使用Ubuntu SDK编写SouldCloud的C++Scope。在本示例中,只需要非常少的C++知识,将其根据暴露JSON API其他服务来调整也非常简单。
注意:本教程也适用于Ubuntu 14.04及更高版本。如果您希望使用Scope布局工具,则至少要使用Ubuntu 14.10。
SDK设置
SDK提供适用于多种不同应用程序类型的多种模板。C++Scope有自己的模板,这也是我们将使用的模板。单击“New Project”按钮来创建新Scope项目。系统将要求您填入一些值来生成该项目。
如果您需要获得更多有关SDK入门指南的帮助,请查看。
注意:即使您要使用平台的,您还需要了解有关Scope的另一件事:如果您在某个时刻需要使用网络,您将无法访问用户数据。这是一项合理的隐私政策,以避免在未得到明确许可的情况下提取用户数据。
测试您的Scope
在本教程的任一点,您都可按下SDK侧栏上的Play按钮来测试的Scope。等待几秒,项目的生成并上传到设备后,项目应会自动打开。
关键源文件
您可通过运行以下项目获得本教程的源代码
$ bzr branch lp:~davidc3/ubuntu-sdk-tutorials/scope-tutorial-soundcloud-qjson
生成的项目包含相当多的文件,我们将讨论其中最重要的一些文件。需要注意的一点是:模板已提供了一个正在使用的Scope:使用openweathermap.org的天气Scope。我们将对其进行更改,使其从SoundCloud中提取结果。
manifest.json
其能容将由生成系统用于生成点击数据包,您能够在Ubuntu Store内安装和发布该点击数据包。大多数情况下,您可保留从开发人员环境中提取的默认值。
<scope>.apparmor
您的Scope使用的安全策略组。我们的示例中为无,因为我们使用的“ubuntu-scope-network”模板已经许可网络调用。了解更多。
data/<appid>.ini
一个非常重要的文件,将允许您自定义和推广您的Scope(图标、背景图像、颜色…)。我们稍后将看到相关情况。
include/api/config.h
我们的HTTP配置:用户代理和基础API URL。让我们更改SoundCloud API URL的apiroot,完成首个更改。
15 |
std::string apiroot { "" };
|
其他URL参数稍后将通过net-cpp库添加。
include/api/client.h, /scope/scope.h, /scope/query.h, /scope/preview.h
我们的C++标头的其余部分。如下所示,更改标头,以匹配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_;
};
|
src/api/client.cpp
我们的API客户端。它提供Scope代码和HTTP API访问之间的隔离。其唯一的作用是检索SoundCloud中的数据。
src/scope/scope.cpp
该文件定义类型类,该类型类提供客户端用于与Scope互动的输入点API。
它实行启动和停止方法。很多Scope都保持这些方法处于不修改的状态,本示例也一样。
它还实行两个关键方法:搜索和预览。这些方法一般不需要修改,本示例也未修改。但是,如下所述,它们调用每个Scope需要实行的关键方法。
注意:您可能会发现在相应的标头文件:include/scope/scope.h中检查ScopeBase类声明(其API)很有用。该标头文件是了解C++类的绝佳方法,因为它们的API无需其他任何实行代码即可声明,理解非常容易。
提示:如果您希望深入了解各种特定类,请在本教程期间查看。
src/scope/query.cpp
我们在该位置发送查询到API客户端,传输返回的结果到结果卡,声明将托管这些卡及其布局的类别。
该文件定义一个类型类。
该类从客户端提供的查询字符串生成搜索结果,并将其返回作为对客户端的回复:
接收来自客户端的查询字符串
接收来自客户端的回复对象
发送查询到API客户端
创建搜索结果类别(对于有不同布局的示例:grid/carousel)
将每个搜索结果与其类别结合(创建CategorisedResult对象)
推送分类结果到回复对象中,由客户端进行显示
在运行方法中完成了大量的编码规则,我们在此处只需完成最少的变动。
检查相应标头文件:include/scope/query.h中的SearchQueryBase类声明(其API)。
src/scope/preview.cpp
该关键文件定义一个类型类。
该类定义预览阶段每个搜索结果使用的小工具和布局。它:
定义预览中使用的小工具
每个结果中针对数据字段的Maps小工具字段
定义有不同列数的布局——取决于显示大小的不同,仅由客户端在显示时间了解。
分配小工具到每个布局的各列
接收回复对象,推送由客户端使用的小工具和布局到对象上
检查相应标头文件:include/scope/preview.h中的SearchPreviewBase类声明(其API)。
对于预览小工具列表和文档,请参阅。
当我们深入了解我们的示例Scope并详细说明一些代码,从查询开始。
查询字符串
在中,您可轻松看到Scope的哪个位置在接收用户查询。在Scope打开后,该查询为空白,您将为本示例提供一些数据。这是呈现特色类容或最新/流行项目的好机会。
在这里,我刚刚触发了一个有关字符串“blur cover”的搜索,该搜索将推送至API客户端,因为SoundCloud为自己的歌曲设置了美观的封面。您可能希望看到更明确的解释,但就本示例来看,让我们假设这是我们用户的一个好起点。修改,使其如此处所示,或者只需将教程文件的内容粘贴到您自己的方法中:
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);
}
(...)
|
生成搜索结果
让我们移至,获取来自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,您将需要在中注册自己的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对象有两个涉及直接兴趣的字段:模板和组件。
修改上的类别,使其类似与以下类别:
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中使用的类别样式,与很多不同的内容类型都能兼容。您可查看中的所有选项。
现在,在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中的显示位置和方式(必要)
标头:曲目的名称
艺术家:品牌/艺术家的名称
视觉:专辑/曲目封面
确保您已在类别模板组件中定义的每个字段都在结果中显示,即使这些字段为空。无效结果将自动弃置。
还是在中,在try{}部分/我们的Query::run方法中,我们需要迭代我们的曲目列表,为每个曲目创建一个。将教程文件的类容粘贴到您自己的方法中,或复制以下行:
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的标头小工具(在的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文档()。
我们不必具体了解如何客户端如何使用这些布局。但是,一般的预期是,单列布局与窄屏情况相配(比如素描模式),双列布局可能与宽屏情况相配(比如景观模式)。
现在,如您在教程文件中所见,我们需要定义三个小工具在每个布局中的放置位置。
自然情况下,在单列布局中,所有小工具必须放入单列中:
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
|
我也找到了来替换模板中提供的徽标。下载它,将其保存为data/logo.png。
如果您调整类别布局和颜色,您可得到差异非常大的样式。左侧的布局是使用上述代码片段生成的:
请查看所有可用的并尝试让您的Scope美观起来!
这就是了,我们的SoundCloudScope完成了。您可按下SDK侧栏的Start按钮启动该Scope,在编辑器的底部查看是否所有内容都已编写完成且正确启动,然后试用您的新Scope!
概述
我们已看到如何创建可查询web API的Scope
查询结果将通过一个独特的渲染器放入一个类别
客户端显示搜索结果
对于预览阶段,使用了四个预定义的小工具类型
多个布局已创建,在布局中小工具采用不同的布置方法,以在数个外观设置中得到美观的显示效果
一些仅与此Scope相配的自定义数据(例如艺术家)在预览和结果中显示
进一步了解
Scope是强大的工具,可帮助用户访问信息和选中的内容。Ubuntu已提供大量默认的Scope,但我们总是可以创建更多的Scope!
新API的收藏夹源(书籍、电影等)转换到Scope中为 API目录,但还有其他多种不同的源。请随意实践不同的布局和卡,以包含不同类型的数据!
发布Scope与发布其他应用程序完全一致,请查看,以用数分钟的时间在店内发布您的Scope。