Upload
xiaoguo-liu
View
417
Download
0
Embed Size (px)
Citation preview
Scope development :)
Agenda
1 What are scopes? Terminology 5 Building blocks (back-end)
2 Scope in detail
3 Tools
4 Building blocks (front-end)
6 Hands-on
What are scopes? Terminology
What do scopes look like?
Scopes are a a UI toolkit to present local or remote content and services in the home screen
Make you content and services easy to discover:
● Scopes are easy to find and favourite as a new home screen
● Scopes dont fight for user attention, related content :○ Search for an artist in Music to see songs
in your phone, concerts near you or buy their new album
Scopes are very easy to build, the only thing you need to get started is any kind of web api.
Scopes | A New UI ParadigmA user experience where content is front and center
Scopes are fast and engaging Scopes are a fast and engaging way to embed your services in Ubuntu
Indian Today(Neaby)
Time of Indian(News)
Indian Video content(Video)
Surface you content on the homescreenDefault aggregating scopes can put your content where it matters
Relevant sources displayed together
Users can discover your service via search
The user can easily configure what to see
Scopes in detail
● A scope is a dedicated search engine that responds to queries, or surfacing about content
● A scope is passive, runs on demand only
● Produces query results● Responds to preview and/or
activation requests on results● A scope can customize visual
appearance or results (order, layout, etc.)
What is a scope really about technically?
Scopes data flow
Aggregated scope
A scope can call a scope, and a scope can aggregate data from any data sources, including other scopes
Process life cycle
● scoperegistry started by upstart, runs permanently
● scoperunners started by registry on demand
● scoperunners exit after 40 seconds of idle time
Registry
● White pages lookup service○ Provides list of all scopes (local and
remote)○ Provides metadata for each scope
(name, description, author, etc.)○ Provides invocation handle for each
scope● Starts and stops scoperunners● Monitors installation files for
added/deleted scopes
Tools
IDE● Ubuntu SDK to cross-build a scope to armhf and click package it
CLI● cmake & make● unity-scope-tool● phablet-screenshot● http://blog.csdn.net/ubuntutouch/article/details/40651093
Environment/host● Utopic 14.10, develop against latest APIs● click chroot or sbuild to cross build on your work machine● armhf device (i.e. Nexus 4, Nexus 7, Chromebook)● http://blog.csdn.net/ubuntutouch/article/details/38395635
Tools
Building blocks (front-end)
Categories
● Every result has a category● Categories group results into named groups● The shell renders categories according to a JSON
definition that controls aspects of the display● Results are rendered in the order in which they are
pushed, grouped by category. The order in which results are pushed determines the order of the categories. Every time a result with a new category is pushed, the shell creates a new group (in top-to-bottom or left-to-right order)
● The shell may choose to show categories collapsed if they contain more than one row of results (depending on the renderer)
Categories vertical templateconst static string CAT_RENDERER { R"( { "schema_version" : 1, "template" : { "category-layout" : "grid", "card-layout": "vertical", "card-size" : "large", "card-background": "#00FF00" }, "components" : { "title" : "title", "art" : "art", "subtitle": "subtitle", "mascot": "mascot", "emblem": "emblem", "summary": "summary", "overlay-color": "overlay-color", "attributes": { "field": "attributes", "max-count": 2 } } } )" };
Categories horizontal templateconst static string CAT_RENDERER { R"( { "schema_version" : 1, "template" : { "category-layout" : "grid", "card-layout": "horizontal", "card-size" : "large", "card-background": "#00FF00" }, "components" : { "title" : "title", "art" : "art", "subtitle": "subtitle", "mascot": "mascot", "emblem": "emblem", "summary": "summary", "overlay-color": "overlay-color", "attributes": { "field": "attributes", "max-count": 2 } } } )" };
Cards
Sizes● small● medium● large
Card-Layout● vertical● horizontal
Overlay
Layouts● grid● carousel● v-journal
http://goo.gl/75s3Ln - should not be treated as documentation, but a nice referencehttp://goo.gl/1VXa0l -a good tutorial for customization and branding of your scope
Building blocks | front-end
Category layouts
Widgets● audio
● video
● image
● gallery
● header
● actions
● progress
● text
● rating-input
● reviews
● table
Building blocks | front-end
http://developer.ubuntu.com/api/scopes/sdk-14.04/preview_20widget_20types/
Building blocks (back-end)
Scope source constitutes of the following classes
● scope - extends unity::scopes::ScopeBase● query - extends unity::scopes::SearchQueryBase● preview - extends unity::scopes::PreviewQueryBase
Most important classes
● CannedQuery - contains query, department id, filter state● PreviewReplyProxy - feeds back the results
Building blocks | back-end
http://developer.ubuntu.com/api/scopes/sdk-14.10/
Scope - directories
● Scope directorystd::string unity::scopes::ScopeBase::scope_directory()/opt/click.ubuntu.com/com.ubuntu.developer.liu-xiao-guo.dianping/0.1/dianping
● Scope cachestd::string unity::scopes::ScopeBase::cache_directory()/home/phablet/.local/share/unity-scopes/leaf-net/com.ubuntu.developer.liu-xiao-guo.dianping
● Scope tmp directorystd::string unity::scopes::ScopeBase::tmp_directory()/run/user/32011/scopes/leaf-net/com.ubuntu.developer.liu-xiao-guo.dianping_dianping
Scope - search metadata
class SearchMetadata : public QueryMetadata {
public:
int cardinality() const;
Location location() const;
bool has_location() const;
...
};
class QueryMetadata {
public:
std::string locale() const; // "zh_CN"
std::string form_factor() const; // "phone"
...
}
void Query::run(sc::SearchReplyProxy const& reply) { auto metadata = search_metadata();
}
● The query string may be the empty string when scope is started
● UI is asking your scope to produce default results that are shown in what is known as surfacing mode
● Show something when scope is started○ Music scope could be “Popular songs”○ Weather scope could “current location”
weather○ A developer has to decide what to show
Query - surfacing mode
Query - department scope
● A way for users to navigate the data sources/channels exposed by the scope
● A department may have optional sub-department
● Each department has an unique id to identify it
● Department id info is used to generate the request uri
● Display will be matched with the returned results
Query - department scope with filter
● A way for users to provide multiple options to select
Query - CannedQuery
class CannedQuery final {
public:
string scope_id() const;
string department_id() const;
string query_string() const;
string to_uri() const;
static CannedQuery from_uri(string const& uri);
// …
};
● CannedQuery() provides accessors for query details and to/from URI conversion (scope:// schema)
● Also provides constructor and modifiers, so you can create a query
void Query::run( sc::SearchReplyProxy const& reply ) { CannedQuery cannedQuery = query();}
Query - register categories
class SearchReply : public virtual Reply
{
public:
virtual Category::SCPtr register_category(
std::string const& id, //must be unique!
std::string const& title,
std::string const& icon,
CategoryRenderer const& renderer_template = CategoryRenderer()) = 0;
virtual bool push(CategorisedResult const& result) = 0;
// ...
};
CategoryRenderer rdrCarousel(CR_CAROUSEL_TEMPLATE);
auto carousel = reply->register_category
("dianpingcarousel", title.toStdString(), "",
rdrCarousel);
Query - push results 1/2
class Result { // abstract base class
public:
void set_uri(std::string const& uri); // mandatory
void set_title(std::string const& title);
void set_art(std::string const& image);
Variant& operator[](std::string const& key);
Variant const& operator[](std::string const& key) const;
bool contains(std::string const& key) const;
// ...
};
class CategorisedResult: public Result{public: explicit CategorisedResult(Category::SCPtr category); void set_category(Category::SCPtr category); Category::SCPtr category() const;};
Query - push results 2/2
CategorisedResult catres(carousel);
catres.set_uri(business_url.toStdString());
catres.set_dnd_uri(business_url.toStdString()); catres.set_title(name.toStdString());
catres["subtitle"] = address.toStdString();
catres["summary"] = summary.toStdString();
catres.set_art(photo_url.toStdString());
catres["address"] = Variant(address.toStdString());
catres["telephone"] = Variant(telephone.toStdString());
//push the categorized result to the client
if (!reply->push(catres)) {
break; // false from push() means search waas cancelled
}
The category order depends on the order of the pushing CategorisedResult
● Previews support 1-, 2-, and 3- column layout● The dash picks what is appropriate for the device● Each column contains one or more display widgets● Widgets are displayed in the order in which they are added to columns.● Widgets come in a number of types:
- audio, video, image, gallery, header, actions, progress, test, rating-input, reviews, expandable
- Each widget type has a number of attributes that depend on the widget type
- E.g., a text widget has a mandatory string attribute named “text” and an optional string attribute named “title”
- Valid widget types and attributes are described in the doc
Preview - layout
Preview layout (cont.)void Preview::run(unity::scopes::PreviewReplyProxy const& reply){ ColumnLayout layout1col(1), layout2col(2);
layout1col.add_column({"headerId", "artId", "infoId", "telId", "actionsId"}); layout2col.add_column({"artId", "headerId"}); layout2col.add_column({"infoId", "telId", "actionsId"});
// Push the layouts into the PreviewReplyProxy intance, thus making them // available for use in Preview diplay reply->register_layout({layout1col, layout2col});
//Create some widgets PreviewWidget w_header("headerId", "header"); w_header.add_attribute_mapping("title", "title"); … PreviewWidget w_tel("telId", "text"); w_tel.add_attribute_mapping("text", "telephone");
Result result = PreviewQueryBase::result(); QString urlString(result["uri"].get_string().c_str()); // Bundle out widgets as required into a PreviewWidgetList PreviewWidgetList widgets({w_header, w_art, w_info, w_tel, w_actions}); // And push them to the PreviewReplyProxy as needed for use in the preview reply->push(widgets);}
Preview - widget initialization
● Four ways to initialize widget attributes:○ Set attribute directly and push widget○ Push value to widget (new value
overrides any previous value and updates display)
○ Map result attributes to widget attributes○ Construct entire widget from a JSON
string
Preview - widget direct attr initialization
// Widget for “summary_wgt” of type text
PreviewWidget description(“summary_wgt”, “text”);
Variant v(“This is a description”);
Description.add_attribute_value(v);
reply->push({ description });
● Variant is a thin wrapper around boost::variant with much the same functionality
● You can update values, pushing the same attribute several times renders the last-pushed value.
Preview - widget attribute mapping // Widget of type image
PreviewWidget image(“image_wgt”, “image”);
// “art” field of result becomes “source” attribute.
image.add_attribute_mapping(“source”, “art”);
// Widget type header
PreviewWidget header(“header_wgt”, “header”);
// “title” field of result goes into title,
// “subtitle” field of result goes into subtitle.
header.add_attribute_mapping(“title”, “title”);
header.add_attribute_mapping(“subtitle”, “subtitle”);
reply->push({ image, header}); // Pushes both widgets
The first constructor argument links the widget to its column. The second constructor argument sets the type of widget (text, audio, etc.) add_attribute_mapping arranges for the specified widget attribute to be filled by the specified attribute in the result: add_attribute_mapping(widget_attr, result_attr); To pass the corresponding value to the shell for display, call push() on the PreviewReplyProxy:
Preview - pushing into mapped attribute
PreviewWidget description(“summary_wgt”, “text”);\
description.add_attribute_mapping(“title”,
“description_heading”);
description.add_attribute_mapping(“text”, “description”);
reply->push({ description });
reply->push(“description”, Variant(“replacement description”);
● Pushing an attribute directly pushes the attribute into the result, as if the attribute had been set in the result to begin with
● The attribute mapping then maps the result attribute to the widget attribute
Scope .ini file
● The .ini file for a scope must contain a [ScopeConfig] group with at least the following attributes:○ DisplayName○ Description○ Author○ DisplayName and Description can (and should) be
localized.
[ScopeConfig]DisplayName = Sports NewsDisplayName[de] = SportnachrichtenDescription = Breaking news from the world of sportsAuthor = Not MeIcon = URL for icon fileArt = URL for screen shot of the
Scope .ini file (cont.)
● The [Appearance] group of the scope’s .ini file controls basic visual settings:
● [Appearance]● ForegroundColor =● BackgroundColor =● ShapeImages = true or false● CategoryHeaderBackground = URL● PreviewButtonColor =● LogoOverlayColor =● PageHeader.Logo = URL● PageHeader.ForegroundColor =● PageHeader.Background = URL● PageHeader.DividerColor =● PageHeader.NavigationBackground = URL● Color and background specifications can be color names (white)
or URLs
http://developer.ubuntu.com/scopes/guides/scopes-customization-branding/
Scope setting
● Scopes can use persistent settings● Scope author ships a .ini settings definition
that defines which settings exist● The shell constructs a UI for the scope’s
settings● User enters/changes setting values● ScopeBase and QueryBase provide access
to currently-established settings● Very simple types: bool, string, number (int
or float), single-choice pick list
Scope setting definitions
● Setting name: “scope ini file with no extension”-settting.ini data/com.ubuntu.developer.liu-xiao-guo.dianping_dianping-settings.ini
[location]type = stringdefaultValue = LondondisplayName = Location
[distanceUnit]type = listdefaultValue = 1displayName = Distance UnitdisplayName[de] = EntfernungseinheitdisplayValues = Kilometers;MilesdisplayValues[de] = Kilometer;Meilen
[color]type = stringdisplayName = Color
Scope setting access
The settings() method on QueryBase and ScopeBase returns a dictionary of string-Variant pairs.
// In your ScopeBase or QueryBase implementation:
// (The settings method is provided by the base class.)
unity::scopes::VariantMap s = settings();
// Prints "London" unless the user changed the value
cout << s.at("location”).get_string();
try {
cout << s.at("color").get_string() << endl;
} catch (std::out_of_range) {
// Color not set, no default value defined.
}
如
Adding keyword to your scopes (1/2)
Adding keywords to your scope in order to improve its discoverability in these searches- Users searching for scopes on the store- Aggregators looking for child scopes
[ScopeConfig]
DisplayName = scopename
Description = description of scope
Author = author
Keywords = videos;
https://developer.ubuntu.com/en/scopes/tutorials/scope-keywords/http://summit.ubuntu.com/uos-1505/meeting/22460/scopes-keywords/
Adding keyword to your scopes (2/2)
● Ubuntu Developer http://developer.ubuntu.com/- Scope http://developer.ubuntu.com/scopes/overview/
● Touch - Ubuntu Wiki https://wiki.ubuntu.com/Touch- Touch/Porting https://wiki.ubuntu.com/Touch/Porting- Touch/Deploying https://wiki.ubuntu.com/Touch/Deploying
● Ubuntu Phone - https://lists.launchpad.net/ubuntu-phone/● Tech support - http://askubuntu.com/● Publish your app: http://developer.ubuntu.com/publish/● Department scope example: http://goo.gl/o5XZxf● Scope tutorials: http://developer.ubuntu.com/scopes/tutorials/● Scopes:https://developer.ubuntu.com/api/scopes/cpp/development/
Hands-on
Questions?
IRC: liuxg | mail: [email protected]