Merge remote-tracking branch 'origin/master' into svetoslavtsenov/merge-release-in-master

This commit is contained in:
SvetoslavTsenov
2019-03-14 22:13:39 +02:00
63 changed files with 809 additions and 189 deletions

View File

@ -67,7 +67,7 @@ git checkout -b <my-fix-branch> master
5. Before you submit your PR:
- Rebase your changes to the latest master: `git pull --rebase upstream master`.
- Ensure all unit test are green for Android and iOS. Check [running unit tests](DevelopmentWorkflow.md#running-unit-tests).
- Ensure all unit test are green for Android and iOS. Check [running unit tests](DevelopmentWorkflow.md#running-unit-tests).
- Ensure your changes pass tslint validation. (run `npm run tslint` in the root of the repo).
6. Push your fork. If you have rebased you might have to use force-push your branch:
@ -223,32 +223,26 @@ Instructions how to release a new version for **NativeScript Core Team Members**
```
git checkout release
```
2. Create a PR to cut the release:
#### If we prepare major or minor release, merge master in release branch else **skip this step**.
```
export RELEASE_VERSION=version
export BRANCH="release-${RELEASE_VERSION}"
git checkout -b ${BRANCH}
git push --set-upstream origin ${BRANCH}
git merge --ff-only origin/master
```
#### Merge master in release branch or cherry-pick commits. If the commits are in release branch **skip this step**.
```
git merge --ff-only origin/master or git cherry-pick commit-sha
git push --set-upstream origin prep-release-version
```
3. Execute `npm i` to install dependencies:
*** Note: If there are commits in release branch which are not merged in master branch '-ff-merge' command will fail.
In this case the commits should be merge firstly from release in master branch as explained in section 'Merge changes from release into master' and then repeat step 1.
2. Execute `npm i` to install dependencies:
```
npm i
```
4. Execute [`npm version`](https://docs.npmjs.com/cli/version) to bump the version of `tns-platform-declarations`:
3. Execute [`npm version`](https://docs.npmjs.com/cli/version) to bump the version of `tns-platform-declarations`:
```
cd tns-platform-declarations
npm --no-git-tag-version version [major|minor|patch] -m "release: cut the %s release"
cd ..
```
5. Execute [`npm version`](https://docs.npmjs.com/cli/version) to bump the version of `tns-core-modules`,
tag the release and update the CHANGELOG.md.
In case we need to publish release version we need simply to use npm version x.x.x-rc
4. Execute [`npm version`](https://docs.npmjs.com/cli/version) to bump the version of `tns-core-modules`,
tag the release and update the CHANGELOG.md. Don't forget to check the auto-generated CHANGELOG.md
```
cd tns-core-modules
npm --no-git-tag-version version [major|minor|patch] -m "release: cut the %s release"
@ -257,6 +251,11 @@ cd ..
6. Set correct version of **tns-core-modules-widgets** in tns-core-modules/package.json.
Usually tns-core-modules-widgets should already have been released and we need to set the official version.
7. Create release-branch with change log
```
git checkout -b release-[release-version]
```
7. Add changes
```
git add changed-files
@ -268,13 +267,13 @@ git push
git tag release-version
git push --tags
```
9. Create a pull request. Replace env variables ${RELEASE_VERSION} and ${BRANCH} with their values
9. Create a pull request. Be careful to base your branch on the correct branch
```
curl -d '{"title": "release: cut the ${RELEASE_VERSION} release","body": "docs: update changelog","head": "${BRANCH}","base": "release"}' -X POST https://api.github.com/repos/NativeScript/NativeScript/pulls -H "Authorization: token ${GIT_TOKEN}"
curl -d '{"title": "release: cut the [release-version] release","body": "docs: update changelog","head": "${BRANCH}","base": "release"}' -X POST https://api.github.com/repos/NativeScript/NativeScript/pulls -H "Authorization: token ${GIT_TOKEN}"
```
10. Merge PR into release branch.
11. If all checks has passed publish package.
11. If all checks has passed publish package. Usually the night builds will be triggered and the package will be ready to be released on the next day.
## Merge changes from release into master

View File

@ -205,7 +205,7 @@ let b = {"good": "code"
## Equality operator
Use the [strict comparison operators][comparisonoperators]. The triple equality operator helps to maintain data type integrity throughout code.
Use the [strict comparison operators][comparisonoperators]. The triple equality operator helps to maintain data type integrity throughout the code.
*Right:*
@ -341,8 +341,8 @@ the last row of a big room can comfortably read. So don't count on them having
perfect vision and limit yourself to 1/2 of your screen height per function (no screen rotation :).
## Return statements
There are few important considerations here:
+ To avoid deep nesting of if-statements, always return a functions value as early
There are a few important considerations here:
+ To avoid deep nesting of if-statements, always return a function's value as early
as possible. In certain routines, once you know the answer, you want to return it to the calling routine immediately. If the routine is defined in such a way that it doesn't require any cleanup, not returning immediately means that you have to write more code.
+ Minimize the number of returns in each routine. It's harder to understand a routine if, reading it at the bottom, you're unaware of the possibility that it *return*ed somewhere above.
@ -396,7 +396,7 @@ function getSomething(val) {
## Arrow Functions
Use arrow functions over anonymous function expressions. Typescript will take care for `this`.
Use arrow functions over anonymous function expressions. Typescript will take care of `this`.
*Right:*
@ -423,7 +423,7 @@ req.on("end", function () {
Use the [JSDoc][JSDOC] convention for comments. When writing a comment always think how understandable will be for somebody who is new to this code. Even if it may look simple to you think how a guy that just joined will understand it. Always comment in the following cases:
+ When there is some non-trivial logic.
+ Some "external" knowledge is needed which is missing in the context - workaround for driver, module bug, special 'hack' because of a bug and so on;
+ Some "external" knowledge is needed which is missing in the context - workaround for a driver, module bug, special 'hack' because of a bug and so on;
+ When you are creating a new class
+ Public methods - include all the arguments and if possible the types {String}, {Number}. Optional arguments should be marked too. Check the [@param tag][param]
@ -432,7 +432,7 @@ Use the [JSDoc][JSDOC] convention for comments. When writing a comment always th
## File/module structure
Typical module should have the following structure:
A typical module should have the following structure:
1. required dependencies
2. module-private declarations - variables, functions, classes, etc.
@ -442,7 +442,7 @@ Typical module should have the following structure:
For more information see [this file](https://github.com/telerik/xPlatCore/blob/master/JS/BCL/CreateNewModule.md)
## File naming
Use lower case for file names. Use dash to separate different words.
Use lower case for file names. Use a dash to separate different words.
*Right:*
file-system
@ -451,7 +451,7 @@ file-system
FileSystem, fileSystem, file_system
## This, that, self
When you **need** to keep reference to **this** use **that** as the name of the variable. Additionally, if you use the TypeScript lambda support, the compiler will take care of this automatically.
When you **need** to keep a reference to **this** use **that** as the name of the variable. Additionally, if you use the TypeScript lambda support, the compiler will take care of this automatically.
*Right:*
```TypeScript
@ -470,7 +470,7 @@ doSomething(function(){
```
## Private (hidden) variables and methods
Although there is the **private** keyword in TypeScript, it is only a syntax sugar. There is no such notation in JavaScript and everything is available to the users. Hence, always use underscore (**_**) to prefix private variables and methods. There are also methods which have the **public** visibility but they are meant to be used within our code ONLY. Such methods should also be prefixed with underscore.
Although there is the **private** keyword in TypeScript, it is only a syntax sugar. There is no such notation in JavaScript and everything is available to the users. Hence, always use underscore (**_**) to prefix private variables and methods. There are also methods which have the **public** visibility but they are meant to be used within our code ONLY. Such methods should also be prefixed with an underscore.
*Right:*
```TypeScript

View File

@ -1,7 +1,7 @@
# Writing Unit Tests for NativeScript Core Modules
Unit tests for NativeScript Modules are written and executed with a custom lightweight test-runner and assertion framework.
The purpose of this document is to get you familiar with it, so that you can unit-test your contributions to the NativeScript framework.
The purpose of this document is to get you familiar with it so that you can unit-test your contributions to the NativeScript framework.
# Run Unit Tests Project
@ -19,7 +19,7 @@ tns run ios
# Test Modules
All unit tests are organized into test modules(bundles).
By default the test app will run all the tests from all registered test modules. This happens in [`runTests()`](/tests/app/app/mainPage.ts#L26-L28) method in the main page of the test-app. By modifying this method, you can configure the app to:
By default, the test app will run all the tests from all registered test modules. This happens in [`runTests()`](/tests/app/app/mainPage.ts#L26-L28) method in the main page of the test-app. By modifying this method, you can configure the app to:
* **Execute only the tests from a specific test module**:
@ -50,8 +50,8 @@ The test modules are actually TypeScript modules which export unit tests and hoo
* All exported functions with a `test` prefix are unit-tests.
* The `setUpModule()` hook is called once - before all the tests in the module.
* The `setUp()` hook is called before each tests.
* The `tearDown()` hook called after each tests.
* The `setUp()` hook is called before each test.
* The `tearDown()` hook called after each test.
* The `tearDownModule()` hook is called once - after all the tests in the module.
# Asserting
@ -85,4 +85,4 @@ export function test_getJSON(done) {
# Misc
When looking into the code of the existing tests, you might encounter strange comments looking like this `// >> animation-chaining`. These are markers for code snippets generated in the docs documetation. They are not related to testing so you don't need to add any of those in your tests.
When looking into the code of the existing tests, you might encounter strange comments looking like this `// >> animation-chaining`. These are markers for code snippets generated in the docs documentation. They are not related to testing so you don't need to add any of those in your tests.

View File

@ -27,6 +27,17 @@
android:theme="@style/AppTheme"
android:usesCleartextTraffic="true" >
<meta-data android:name="debugLayouts" android:value="true" />
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="org.nativescript.apps.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths"/>
</provider>
<activity
android:name="com.tns.NativeScriptActivity"
android:label="@string/title_activity_kimera"

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="external_files" path="."/>
</paths>

View File

@ -0,0 +1,17 @@
import * as frame from "tns-core-modules/ui/frame";
import { EventData } from "tns-core-modules/ui/frame";
import { Button } from "tns-core-modules/ui/button";
import { ActionBar } from "tns-core-modules/ui/action-bar";
const iconModes = ["automatic", "alwaysOriginal", "alwaysTemplate", undefined];
export function navigate(args) {
frame.topmost().navigate("ui-tests-app/action-bar/clean");
}
export function onChangeRenderingMode(args: EventData) {
const button = <Button>args.object;
const actionBar = <ActionBar>button.page.actionBar
actionBar.iosIconRenderingMode = <"automatic" | "alwaysOriginal" | "alwaysTemplate">iconModes[(iconModes.indexOf(actionBar.iosIconRenderingMode) + 1) % iconModes.length];
button.text = "" + actionBar.iosIconRenderingMode;
}

View File

@ -0,0 +1,14 @@
<Page>
<Page.actionBar>
<ActionBar>
<ActionBar.actionItems>
<ActionItem icon="res://icon" tap="navigate"/>
<ActionItem icon="res://add_to_fav" tap="navigate"/>
</ActionBar.actionItems>
</ActionBar>
</Page.actionBar>
<StackLayout>
<Button text="go to cleared page" tap="navigate"/>
<Button text="undefined" tap="onChangeRenderingMode"/>
</StackLayout>
</Page>

View File

@ -0,0 +1,17 @@
import * as frame from "tns-core-modules/ui/frame";
import { EventData } from "tns-core-modules/ui/frame";
import { Button } from "tns-core-modules/ui/button";
import { ActionBar } from "tns-core-modules/ui/action-bar";
const iconModes = ["automatic", "alwaysOriginal", "alwaysTemplate", undefined];
export function navigate(args) {
frame.topmost().navigate("ui-tests-app/action-bar/clean");
}
export function onChangeRenderingMode(args: EventData) {
const button = <Button>args.object;
const actionBar = <ActionBar>button.page.actionBar
actionBar.iosIconRenderingMode = <"automatic" | "alwaysOriginal" | "alwaysTemplate">iconModes[(iconModes.indexOf(actionBar.iosIconRenderingMode) + 1) % iconModes.length];
button.text = "" + actionBar.iosIconRenderingMode;
}

View File

@ -0,0 +1,14 @@
<Page>
<Page.actionBar>
<ActionBar>
<ActionBar.actionItems>
<ActionItem icon="~/ui-tests-app/resources/images/icon.png" tap="navigate"/>
<ActionItem icon="~/ui-tests-app/resources/images/add_to_fav@3x.png" tap="navigate"/>
</ActionBar.actionItems>
</ActionBar>
</Page.actionBar>
<StackLayout>
<Button text="go to cleared page" tap="navigate"/>
<Button text="undefined" tap="onChangeRenderingMode"/>
</StackLayout>
</Page>

View File

@ -15,6 +15,8 @@ export function loadExamples() {
examples.set("actBG", "action-bar/background");
examples.set("actStyle", "action-bar/all");
examples.set("actIcons", "action-bar/system-icons");
examples.set("actLocalIcons", "action-bar/local-icons");
examples.set("actResIcons", "action-bar/icons");
examples.set("actView", "action-bar/action-view");
examples.set("actionItemPosition", "action-bar/action-item-position");
examples.set("actBGCss", "action-bar/background-css");

View File

@ -0,0 +1,17 @@
import { EventData } from "tns-core-modules/data/observable";
import { SubMainPageViewModel } from "../sub-main-page-view-model";
import { WrapLayout } from "tns-core-modules/ui/layouts/wrap-layout";
import { Page } from "tns-core-modules/ui/page";
export function pageLoaded(args: EventData) {
const page = <Page>args.object;
const wrapLayout = <WrapLayout>page.getViewById("wrapLayoutWithExamples");
page.bindingContext = new SubMainPageViewModel(wrapLayout, loadExamples());
}
export function loadExamples() {
const examples = new Map<string, string>();
examples.set("open-file", "intent/open-file");
return examples;
}

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Page loaded="pageLoaded">
<ScrollView orientation="vertical" row="1">
<WrapLayout id="wrapLayoutWithExamples"/>
</ScrollView>
</Page>

View File

@ -0,0 +1,15 @@
import { isIOS, isAndroid } from "tns-core-modules/platform";
import * as fs from "tns-core-modules/file-system/file-system";
import * as utils from "tns-core-modules/utils/utils";
export function openFile() {
let directory;
if (isIOS) {
directory = fs.knownFolders.ios.downloads();
} else if (isAndroid) {
directory = android.os.Environment.getExternalStorageDirectory().getAbsolutePath().toString();
}
const filePath = fs.path.join(directory, "Test_File_Open.txt");
utils.openFile(filePath);
}

View File

@ -0,0 +1,5 @@
<Page navigatingTo="navigatingTo">
<GridLayout id="root">
<Button text="Open File" tap="openFile" height="40"></Button>
</GridLayout>
</Page>

View File

@ -36,6 +36,7 @@ export function pageLoaded(args: EventData) {
examples.set("progress-bar", "progress-bar/main-page");
examples.set("date-picker", "date-picker/date-picker");
examples.set("nested-frames", "nested-frames/main-page");
examples.set("intent", "intent/main-page");
page.bindingContext = new MainPageViewModel(wrapLayout, examples);
const parent = page.getViewById("parentLayout");

View File

@ -19,6 +19,7 @@ export function loadExamples() {
examples.set("tabmore", "tab-view/tab-view-more");
examples.set("tabViewCss", "tab-view/tab-view-css");
examples.set("tab-view-icons", "tab-view/tab-view-icon");
examples.set("tab-view-icons-local", "tab-view/tab-view-icon-local");
examples.set("tab-view-icon-change", "tab-view/tab-view-icon-change");
examples.set("text-transform", "tab-view/text-transform");
examples.set("tab-view-bottom-position", "tab-view/tab-view-bottom-position");

View File

@ -0,0 +1,21 @@
import { EventData } from "tns-core-modules/data/observable";
import { Button } from "tns-core-modules/ui/button";
import { TabView } from "tns-core-modules/ui/tab-view";
let iconModes = ["automatic", "alwaysOriginal", "alwaysTemplate", undefined];
export const onNavigate = updateButtons;
export function onChangeRenderingMode(args: EventData) {
let tabView = (<Button>args.object).page.getViewById<TabView>("tab-view");
tabView.iosIconRenderingMode = <"automatic" | "alwaysOriginal" | "alwaysTemplate">iconModes[(iconModes.indexOf(tabView.iosIconRenderingMode) + 1) % iconModes.length];
updateButtons(args);
}
function updateButtons(args) {
let button = (<Button>args.object);
let tabView = button.page.getViewById<TabView>("tab-view");
for (let i = 0, length = tabView.items.length; i < length; i++) {
(<Button>tabView.items[i].view).text = "" + tabView.iosIconRenderingMode;
}
}

View File

@ -0,0 +1,26 @@
<Page xmlns="http://schemas.nativescript.org/tns.xsd" navigatingTo="onNavigate">
<TabView id="tab-view" tabTextColor="green" selectedTabTextColor="red" tabBackgroundColor="yellow">
<TabView.items>
<TabViewItem iconSource="~/ui-tests-app/resources/images/icon.png">
<TabViewItem.view>
<Button text="undefined" tap="onChangeRenderingMode"/>
</TabViewItem.view>
</TabViewItem>
<TabViewItem iconSource="~/ui-tests-app/resources/images/add_to_fav@3x.png">
<TabViewItem.view>
<Button text="undefined" tap="onChangeRenderingMode"/>
</TabViewItem.view>
</TabViewItem>
<TabViewItem iconSource="~/ui-tests-app/resources/images/icon.png" title="NativeScript">
<TabViewItem.view>
<Button text="undefined" tap="onChangeRenderingMode"/>
</TabViewItem.view>
</TabViewItem>
<TabViewItem iconSource="~/ui-tests-app/resources/images/add_to_fav@3x.png" title="Favorites">
<TabViewItem.view>
<Button text="undefined" tap="onChangeRenderingMode"/>
</TabViewItem.view>
</TabViewItem>
</TabView.items>
</TabView>
</Page>

View File

@ -0,0 +1,18 @@
const { readFileSync } = require("fs");
const { resolve } = require("path");
const { createPR, argsParser, gitBranch } = require("./pr-helper");
const currentBranch = argsParser().currentBranch || gitBranch;
const modulesPackageVersion = argsParser().packageVersion || JSON.parse(readFileSync(resolve(process.cwd(), "tns-core-modules/package.json")).toString()).version;
const title = argsParser().title || `release: cut the ${modulesPackageVersion} release`;
const baseBranch = argsParser().base || "release";
const body = argsParser().body || "docs: update changelog";
const postQuery = {
"body": body,
"head": currentBranch,
"base": baseBranch,
"title": title
}
createPR(postQuery);

View File

@ -0,0 +1,9 @@
const { createPR, gitBranch } = require("./pr-helper");
const postQuery = {
"head": gitBranch,
"base": "master",
"title": "chore: merge release in master"
}
createPR(postQuery);

39
build/pr-helper.js Normal file
View File

@ -0,0 +1,39 @@
const { execSync } = require('child_process');
const { writeFileSync, unlinkSync } = require("fs");
const { resolve } = require("path");
exports.gitBranch = execSync("git branch").toString()
.split("\n")
.filter(f => f.trim().startsWith("*"))[0]
.replace("*", "").trim();
exports.createPR = (postQuery) => {
if (!process.env.GIT_TOKEN) {
console.error("Missing env variable GIT_TOKEN");
process.exit(1);
}
const releaseDataJsonPath = resolve(process.cwd(), "git-helper.json");
writeFileSync(releaseDataJsonPath, JSON.stringify(postQuery));
const result = execSync(` curl -d "@${releaseDataJsonPath}" -X POST https://api.github.com/repos/NativeScript/NativeScript/pulls -H "Authorization: token ${process.env.GIT_TOKEN}" `);
console.log(result.toString());
unlinkSync(releaseDataJsonPath);
const requesResultJson = JSON.parse(result);
execSync(`open ${requesResultJson.html_url}`);
return requesResultJson;
}
exports.argsParser = () => {
args = {};
process.argv
.filter(a => a.startsWith("--"))
.map(el => {
el = el.split("=");
const prop = el[0].replace("--", "").replace("-", "").trim();
const value = el[1].trim();
args[prop] = value;
});
return args;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 107 KiB

View File

@ -27,6 +27,7 @@
android:label="@string/app_name"
android:largeHeap="true"
android:theme="@style/AppTheme" >
<activity
android:name="com.tns.NativeScriptActivity"
android:label="@string/title_activity_kimera"

View File

@ -0,0 +1,3 @@
Button {
color: green;
}

View File

@ -521,7 +521,18 @@ export function test_CorrectPropertyValueAfterUsingWrappedValue() {
testObservable.set("property1", wrappedArray);
TKUnit.assertEqual(testObservable.get("property1"), testArray, "WrappedValue is used only to execute property change logic and unwrapped value should be used as proeprty value.");
TKUnit.assertEqual(testObservable.get("property1"), testArray, "WrappedValue is used only to execute property change logic and unwrapped value should be used as property value.");
}
export function test_CorrectPropertyValueAfterUsingStringEmptyWrappedValue() {
const emptyString = "";
let testObservable = fromObject({ "property1": emptyString });
let wrappedEmptyString = WrappedValue.wrap(emptyString);
testObservable.set("property1", wrappedEmptyString);
TKUnit.assertEqual(testObservable.get("property1"), emptyString, "WrappedValue is used only to execute property change logic and unwrapped value should be used as property value.");
}
export function test_NestedObservablesWithObservableArrayShouldNotCrash() {

View File

@ -15,6 +15,7 @@ const mainPageCssFileName = "./app/main-page.css";
const mainPageHtmlFileName = "./app/main-page.html";
const mainPageXmlFileName = "./app/main-page.xml";
const black = new Color("black");
const green = new Color("green");
const mainPageTemplate = `
@ -56,7 +57,7 @@ export function test_onLiveSync_ModuleContext_Script_AppTs() {
}
export function test_onLiveSync_ModuleContext_Style_MainPageCss() {
_test_onLiveSync_ModuleContext({ type: "style", path: mainPageCssFileName });
_test_onLiveSync_ModuleContext_TypeStyle({ type: "style", path: mainPageCssFileName });
}
export function test_onLiveSync_ModuleContext_Markup_MainPageHtml() {
@ -110,4 +111,29 @@ function _test_onLiveSync_ModuleContext(context: { type, path }) {
const topmostFrame = frame.topmost();
TKUnit.waitUntilReady(() => topmostFrame.currentPage && topmostFrame.currentPage.isLoaded && !topmostFrame.canGoBack());
TKUnit.assertTrue(topmostFrame.currentPage.getViewById("label").isLoaded);
}
function _test_onLiveSync_ModuleContext_TypeStyle(context: { type, path }) {
const pageBeforeNavigation = helper.getCurrentPage();
const page = <Page>parse(pageTemplate);
helper.navigateWithHistory(() => page);
const pageBeforeLiveSync = helper.getCurrentPage();
pageBeforeLiveSync._moduleName = "main-page";
global.__onLiveSync({ type: context.type, path: context.path });
const pageAfterLiveSync = helper.getCurrentPage();
TKUnit.waitUntilReady(() => pageAfterLiveSync.getViewById("button").style.color.toString() === green.toString());
TKUnit.assertTrue(pageAfterLiveSync.frame.canGoBack(), "Local styles NOT applied - livesync navigation executed!");
TKUnit.assertEqual(pageAfterLiveSync, pageBeforeLiveSync, "Pages are different - livesync navigation executed!");
TKUnit.assertTrue(pageAfterLiveSync._cssState.isSelectorsLatestVersionApplied(), "Latest selectors version NOT applied!");
helper.goBack();
const pageAfterNavigationBack = helper.getCurrentPage();
TKUnit.assertEqual(pageAfterNavigationBack.getViewById("label").style.color, black, "App styles applied on back navigation!");
TKUnit.assertEqual(pageBeforeNavigation, pageAfterNavigationBack, "Pages are different - livesync navigation executed!");
TKUnit.assertTrue(pageAfterNavigationBack._cssState.isSelectorsLatestVersionApplied(), "Latest selectors version is NOT applied!");
}

View File

@ -168,12 +168,15 @@ allTests["VISUAL-STATE"] = visualStateTests;
import * as valueSourceTests from "./ui/styling/value-source-tests";
allTests["VALUE-SOURCE"] = valueSourceTests;
import * as buttonTests from "./ui/button/button-tests";
allTests["BUTTON"] = buttonTests;
import * as borderTests from "./ui/border/border-tests";
allTests["BORDER"] = borderTests;
import * as builderTests from "./ui/builder/builder-tests";
allTests["BUILDER"] = builderTests;
import * as buttonTests from "./ui/button/button-tests";
allTests["BUTTON"] = buttonTests;
import * as labelTests from "./ui/label/label-tests";
allTests["LABEL"] = labelTests;

View File

@ -0,0 +1,26 @@
import { path } from "tns-core-modules/file-system";
import { loadPage } from "tns-core-modules/ui/builder";
import { assertEqual, assertNull } from "../../TKUnit";
const COMPONENT_MODULE = "component-module";
const LABEL = "label";
function getViewComponent() {
const moduleNamePath = path.join(__dirname, COMPONENT_MODULE);
const fileName = path.join(__dirname, `${COMPONENT_MODULE}.xml`);
const view = loadPage(moduleNamePath, fileName);
return view;
}
export function test_view_is_module_root_component() {
const view = getViewComponent();
const actualModule = view._moduleName;
assertEqual(actualModule, COMPONENT_MODULE, `View<${view}> is NOT root component of module <${COMPONENT_MODULE}>.`);
}
export function test_view_is_NOT_module_root_component() {
const view = getViewComponent();
const nestedView = view.getViewById(`${LABEL}`);
const undefinedModule = nestedView._moduleName;
assertNull(undefinedModule, `View<${nestedView}> should NOT be a root component of a module.`);
}

View File

@ -0,0 +1,3 @@
<StackLayout>
<Label id="label"></Label>
</StackLayout>

View File

@ -674,6 +674,23 @@ export function test_CSS_isAppliedOnPage_From_addCssFile() {
});
}
export function test_CSS_isAppliedOnPage_From_changeCssFile() {
const testButton = new buttonModule.Button();
testButton.text = "Test";
const testCss = "button { color: blue; }";
const testFunc = function (views: Array<viewModule.View>) {
helper.assertViewColor(testButton, "#0000FF");
const page: pageModule.Page = <pageModule.Page>views[1];
page.changeCssFile("~/ui/styling/test.css");
helper.assertViewBackgroundColor(page, "#FF0000");
TKUnit.assert(testButton.style.color === undefined, "Color should not have a value");
}
helper.buildUIAndRunTest(testButton, testFunc, { pageCss: testCss });
}
const invalidCSS = ".invalid { " +
"color: invalidValue; " +
"background-color: invalidValue; " +

View File

@ -28,4 +28,4 @@
"scripts": {
"check-circular-deps": "node circular-check.js"
}
}
}

View File

@ -39,7 +39,7 @@ import {
iOSApplication,
LoadAppCSSEventData,
UnhandledErrorEventData,
DiscardedErrorEventData
DiscardedErrorEventData,
} from "./application";
export { UnhandledErrorEventData, DiscardedErrorEventData, CssChangedEventData, LoadAppCSSEventData };
@ -82,19 +82,23 @@ export function setApplication(instance: iOSApplication | AndroidApplication): v
export function livesync(rootView: View, context?: ModuleContext) {
events.notify(<EventData>{ eventName: "livesync", object: app });
const liveSyncCore = global.__onLiveSyncCore;
let reapplyAppCss = false;
let reapplyAppStyles = false;
let reapplyLocalStyles = false;
if (context) {
const fullFileName = getCssFileName();
const fileName = fullFileName.substring(0, fullFileName.lastIndexOf(".") + 1);
if (context && context.path) {
const extensions = ["css", "scss"];
reapplyAppCss = extensions.some(ext => context.path === fileName.concat(ext));
const appStylesFullFileName = getCssFileName();
const appStylesFileName = appStylesFullFileName.substring(0, appStylesFullFileName.lastIndexOf(".") + 1);
reapplyAppStyles = extensions.some(ext => context.path === appStylesFileName.concat(ext));
if (!reapplyAppStyles) {
reapplyLocalStyles = extensions.some(ext => context.path.endsWith(ext));
}
}
if (reapplyAppCss && rootView) {
if (reapplyAppStyles && rootView) {
rootView._onCssStateChange();
} else if (liveSyncCore) {
liveSyncCore();
reapplyLocalStyles ? liveSyncCore(context) : liveSyncCore();
}
}

View File

@ -3,7 +3,9 @@
* @module "application"
*/ /** */
///<reference path="../tns-core-modules.d.ts" /> Include global typings
/// <reference path="../nativescript-error.d.ts" />
/// <reference path="../tns-core-modules.d.ts" />
import { NavigationEntry, View, Observable, EventData } from "../ui/frame";
/**
@ -607,4 +609,4 @@ export function hasLaunched(): boolean;
export interface LoadAppCSSEventData extends EventData {
cssFile: string;
}
}

View File

@ -225,9 +225,9 @@ class IOSApplication implements IOSApplicationDefinition {
}
}
public _onLivesync(): void {
public _onLivesync(context?: ModuleContext): void {
// If view can't handle livesync set window controller.
if (this._rootView && !this._rootView._onLivesync()) {
if (this._rootView && !this._rootView._onLivesync(context)) {
this.setWindowContent();
}
}
@ -264,8 +264,8 @@ exports.ios = iosApp;
setApplication(iosApp);
// attach on global, so it can be overwritten in NativeScript Angular
(<any>global).__onLiveSyncCore = function () {
iosApp._onLivesync();
(<any>global).__onLiveSyncCore = function (context?: ModuleContext) {
iosApp._onLivesync(context);
}
let mainEntry: NavigationEntry;

View File

@ -13,7 +13,7 @@ export class WrappedValue implements WrappedValueDefinition {
}
public static unwrap(value: any) {
return (value && value.wrapped) ? value.wrapped : value;
return (value instanceof WrappedValue) ? value.wrapped : value;
}
public static wrap(value: any) {
@ -249,4 +249,4 @@ export function fromObjectRecursive(source: any): Observable {
let observable = new ObservableFromObject();
addPropertiesFromObject(observable, source, true);
return observable;
}
}

View File

@ -18,6 +18,10 @@ export class CSSDomainDebugger implements inspectorCommandTypes.CSSDomain.CSSDom
this.commands = {};
attachCSSInspectorCommandCallbacks(this.commands);
// By default start enabled because we can miss the "enable" event when
// running with `--debug-brk` -- the frontend will send it before we've been created
this.enable();
}
get enabled(): boolean {

View File

@ -20,6 +20,10 @@ export class DOMDomainDebugger implements inspectorCommandTypes.DOMDomain.DOMDom
attachDOMInspectorEventCallbacks(this.events);
attachDOMInspectorCommandCallbacks(this.commands);
// By default start enabled because we can miss the "enable event when
// running with `--debug-brk` -- the frontend will send it before we've been created
this.enable();
}
get enabled(): boolean {

View File

@ -64,7 +64,7 @@ export class Request {
this._resourceType = "Other";
return;
}
this._mimeType = value;
var resourceType = "Other";
@ -112,19 +112,19 @@ export class Request {
this._resourceType = value;
}
}
public responseReceived(response: inspectorCommandTypes.NetworkDomain.Response): void {
if (this._networkDomainDebugger.enabled) {
this._networkDomainDebugger.events.responseReceived(this.requestID, frameId, loaderId, __inspectorTimestamp(), <any>this.resourceType, response);
}
}
public loadingFinished(): void {
if (this._networkDomainDebugger.enabled) {
this._networkDomainDebugger.events.loadingFinished(this.requestID, __inspectorTimestamp());
}
}
public requestWillBeSent(request: inspectorCommandTypes.NetworkDomain.Request): void {
if (this._networkDomainDebugger.enabled) {
this._networkDomainDebugger.events.requestWillBeSent(this.requestID, frameId, loaderId, request.url, request, __inspectorTimestamp(), { type: "Script" });
@ -136,9 +136,13 @@ export class Request {
export class NetworkDomainDebugger implements inspectorCommandTypes.NetworkDomain.NetworkDomainDispatcher {
private _enabled: boolean;
public events: inspectorCommandTypes.NetworkDomain.NetworkFrontend;
constructor() {
this.events = new inspectorCommands.NetworkDomain.NetworkFrontend();
// By default start enabled because we can miss the "enable" event when
// running with `--debug-brk` -- the frontend will send it before we've been created
this.enable();
}
get enabled(): boolean {
@ -156,7 +160,7 @@ export class NetworkDomainDebugger implements inspectorCommandTypes.NetworkDomai
}
this._enabled = true;
}
/**
* Disables network tracking, prevents network events from being sent to the client.
*/
@ -166,14 +170,14 @@ export class NetworkDomainDebugger implements inspectorCommandTypes.NetworkDomai
}
this._enabled = false;
}
/**
* Specifies whether to always send extra HTTP headers with the requests from this page.
*/
setExtraHTTPHeaders(params: inspectorCommandTypes.NetworkDomain.SetExtraHTTPHeadersMethodArguments): void {
//
}
/**
* Returns content served for the given request.
*/
@ -187,9 +191,9 @@ export class NetworkDomainDebugger implements inspectorCommandTypes.NetworkDomai
body: body,
base64Encoded: !resource_data.hasTextContent
};
}
}
}
/**
* Tells whether clearing browser cache is supported.
*/
@ -198,14 +202,14 @@ export class NetworkDomainDebugger implements inspectorCommandTypes.NetworkDomai
result: false
};
}
/**
* Clears browser cache.
*/
clearBrowserCache(): void {
//
}
/**
* Tells whether clearing browser cookies is supported.
*/
@ -214,21 +218,21 @@ export class NetworkDomainDebugger implements inspectorCommandTypes.NetworkDomai
result: false
};
}
/**
* Clears browser cookies.
*/
clearBrowserCookies(): void {
//
}
/**
* Toggles ignoring cache for each request. If <code>true</code>, cache will not be used.
*/
setCacheDisabled(params: inspectorCommandTypes.NetworkDomain.SetCacheDisabledMethodArguments): void {
//
}
/**
* Loads a resource in the context of a frame on the inspected page without cross origin checks.
*/
@ -245,7 +249,7 @@ export class NetworkDomainDebugger implements inspectorCommandTypes.NetworkDomai
status: 200
}
}
public static idSequence: number = 0;
create(): Request {
let id = (++NetworkDomainDebugger.idSequence).toString();
@ -264,4 +268,4 @@ export class RuntimeDomainDebugger {
compileScript(): { scriptId?: string, exceptionDetails?: Object } {
return {};
}
}
}

View File

@ -1,3 +1,4 @@
/// <reference path="./nativescript-error.d.ts" />
declare var global: NodeJS.Global;
interface ModuleResolver {
@ -52,7 +53,7 @@ declare namespace NodeJS {
__inspector?: any;
__extends: any;
__onLiveSync: (context?: { type: string, path: string }) => void;
__onLiveSyncCore: () => void;
__onLiveSyncCore: (context?: { type: string, path: string }) => void;
__onUncaughtError: (error: NativeScriptError) => void;
__onDiscardedError: (error: NativeScriptError) => void;
TNS_WEBPACK?: boolean;
@ -86,16 +87,6 @@ interface ModuleContext {
path: string;
}
/**
* An extended JavaScript Error which will have the nativeError property initialized in case the error is caused by executing platform-specific code.
*/
interface NativeScriptError extends Error {
/**
* Represents the native error object.
*/
nativeError: any;
}
// Define a minimal subset of NodeRequire and NodeModule so user apps can compile without
// installing @types/node

View File

@ -0,0 +1,9 @@
/**
* An extended JavaScript Error which will have the nativeError property initialized in case the error is caused by executing platform-specific code.
*/
declare interface NativeScriptError extends Error {
/**
* Represents the native error object.
*/
nativeError: any;
}

View File

@ -1,7 +1,7 @@
{
"name": "tns-core-modules",
"description": "Telerik NativeScript Core Modules",
"version": "5.2.2",
"version": "5.3.0",
"homepage": "https://www.nativescript.org",
"repository": {
"type": "git",
@ -26,7 +26,7 @@
"license": "Apache-2.0",
"typings": "tns-core-modules.d.ts",
"dependencies": {
"tns-core-modules-widgets": "5.2.0",
"tns-core-modules-widgets": "next",
"tslib": "^1.9.3"
},
"devDependencies": {

View File

@ -22,12 +22,15 @@ declare var console: Console;
declare var require: NodeRequire;
// Extend NodeRequire with the webpack's require context extension.
interface RequireContext {
keys(): string[];
(id: string): any;
<T>(id: string): T;
resolve(id: string): string;
}
interface NodeRequire {
context(root: string, recursive: boolean, filter: RegExp): {
(module: string): any;
id: number;
keys(): string[];
}
context(path: string, deep?: boolean, filter?: RegExp): RequireContext;
}
declare var __dirname: string;

View File

@ -29,6 +29,7 @@ export class ActionBarBase extends View implements ActionBarDefinition {
public title: string;
public flat: boolean;
public iosIconRenderingMode: "automatic" | "alwaysOriginal" | "alwaysTemplate";
get navigationButton(): NavigationButton {
return this._navigationButton;
@ -88,6 +89,10 @@ export class ActionBarBase extends View implements ActionBarDefinition {
this.update();
}
}
get ios(): any {
return undefined;
}
get android(): AndroidActionBarSettings {
return undefined;
@ -346,6 +351,9 @@ export function traceMissingIcon(icon: string) {
traceMessageType.error);
}
export const iosIconRenderingModeProperty = new Property<ActionBarBase, "automatic" | "alwaysOriginal" | "alwaysTemplate">({ name: "iosIconRenderingMode", defaultValue: "alwaysOriginal" });
iosIconRenderingModeProperty.register(ActionBarBase);
export const textProperty = new Property<ActionItemBase, string>({ name: "text", defaultValue: "", valueChanged: onItemChanged });
textProperty.register(ActionItemBase);

View File

@ -437,7 +437,7 @@ function getDrawableOrResourceId(icon: string, resources: android.content.res.Re
let is = fromFileOrResource(icon);
if (is) {
drawable = new android.graphics.drawable.BitmapDrawable(is.android);
drawable = new android.graphics.drawable.BitmapDrawable(appResources, is.android);
}
result = drawable;

View File

@ -41,6 +41,20 @@ export class ActionBar extends View {
*/
android: AndroidActionBarSettings;
/**
* Gets the native iOS [UINavigationBar](https://developer.apple.com/documentation/uikit/uinavigationbar) that represents the user interface for this component. Valid only when running on iOS.
*/
ios: any /* UITabBarController */;
/**
* Gets or set the UIImageRenderingMode of the action bar icons in iOS. Defaults to "alwaysOriginal"
* Valid values are:
* - automatic
* - alwaysOriginal
* - alwaysTemplate
*/
iosIconRenderingMode: "automatic" | "alwaysOriginal" | "alwaysTemplate";
/**
* Updates the action bar.
*/

View File

@ -2,7 +2,7 @@ import { IOSActionItemSettings, ActionItem as ActionItemDefinition } from ".";
import {
ActionItemBase, ActionBarBase, isVisible, View,
colorProperty, backgroundColorProperty,
backgroundInternalProperty, flatProperty,
backgroundInternalProperty, flatProperty, iosIconRenderingModeProperty,
layout, Color, traceMissingIcon } from "./action-bar-common";
import { fromFileOrResource } from "../../image-source";
import { ios as iosUtils } from "../../utils/utils";
@ -120,6 +120,18 @@ export class ActionBar extends ActionBarBase {
this.layout(0, 0, width, height, false);
}
private _getIconRenderingMode(): UIImageRenderingMode {
switch (this.iosIconRenderingMode) {
case "alwaysOriginal":
return UIImageRenderingMode.AlwaysOriginal;
case "alwaysTemplate":
return UIImageRenderingMode.AlwaysTemplate;
case "automatic":
default:
return UIImageRenderingMode.AlwaysOriginal;
}
}
public update() {
const page = this.page;
// Page should be attached to frame to update the action bar.
@ -241,7 +253,8 @@ export class ActionBar extends ActionBarBase {
barButtonItem = UIBarButtonItem.alloc().initWithBarButtonSystemItemTargetAction(id, tapHandler, "tap");
} else if (item.icon) {
const img = loadActionIconFromFileOrResource(item.icon);
barButtonItem = UIBarButtonItem.alloc().initWithImageStyleTargetAction(img, UIBarButtonItemStyle.Plain, tapHandler, "tap");
const image = img.imageWithRenderingMode(this._getIconRenderingMode());
barButtonItem = UIBarButtonItem.alloc().initWithImageStyleTargetAction(image, UIBarButtonItemStyle.Plain, tapHandler, "tap");
} else {
barButtonItem = UIBarButtonItem.alloc().initWithTitleStyleTargetAction(item.text + "", UIBarButtonItemStyle.Plain, tapHandler, "tap");
}
@ -392,4 +405,11 @@ export class ActionBar extends ActionBarBase {
this.updateFlatness(navBar);
}
}
[iosIconRenderingModeProperty.getDefault](): "automatic" | "alwaysOriginal" | "alwaysTemplate" {
return "alwaysOriginal";
}
[iosIconRenderingModeProperty.setNative](value: "automatic" | "alwaysOriginal" | "alwaysTemplate") {
this.update();
}
}

View File

@ -59,7 +59,9 @@ export function load(pathOrOptions: string | LoadOptions, context?: any): View {
export function loadPage(moduleNamePath: string, fileName: string, context?: any): View {
const componentModule = loadInternal(fileName, context, moduleNamePath);
return componentModule && componentModule.component;
const componentView = componentModule && componentModule.component;
markAsModuleRoot(componentView, moduleNamePath);
return componentView;
}
const loadModule = profile("loadModule", (moduleNamePath: string, entry: ViewEntry): ModuleExports => {
@ -96,7 +98,7 @@ export const createViewFromEntry = profile("createViewFromEntry", (entry: ViewEn
} else if (entry.moduleName) {
// Current app full path.
const currentAppPath = knownFolders.currentApp().path;
// Full path of the module = current app full path + module name.
const moduleNamePath = path.join(currentAppPath, entry.moduleName);
const moduleExports = loadModule(moduleNamePath, entry);
@ -108,7 +110,7 @@ export const createViewFromEntry = profile("createViewFromEntry", (entry: ViewEn
return viewFromBuilder(moduleNamePath, moduleExports);
}
}
throw new Error("Failed to load page XML file for module: " + entry.moduleName);
});
@ -128,7 +130,7 @@ interface ModuleExports {
const moduleCreateView = profile("module.createView", (moduleNamePath: string, moduleExports: ModuleExports): View => {
const view = moduleExports.createPage();
const cssFileName = resolveFileName(moduleNamePath, "css");
// If there is no cssFile only appCss will be applied at loaded.
if (cssFileName) {
view.addCssFile(cssFileName);
@ -136,6 +138,12 @@ const moduleCreateView = profile("module.createView", (moduleNamePath: string, m
return view;
});
function markAsModuleRoot(componentView: View, moduleNamePath: string): void {
const lastIndexOfSeparator = moduleNamePath.lastIndexOf(path.separator);
const moduleName = moduleNamePath.substring(lastIndexOfSeparator + 1);
componentView._moduleName = moduleName;
}
function loadInternal(fileName: string, context?: any, moduleNamePath?: string): ComponentModule {
let componentModule: ComponentModule;

View File

@ -108,6 +108,12 @@ export abstract class ViewBase extends Observable {
flexWrapBefore: FlexWrapBefore;
alignSelf: AlignSelf;
/**
* @private
* Module name when the view is a module root. Otherwise, it is undefined.
*/
_moduleName?: string;
//@private
/**
* @private

View File

@ -244,6 +244,8 @@ export abstract class ViewBase extends Observable implements ViewBaseDefinition
public _defaultPaddingLeft: number;
public _isPaddingRelative: boolean;
public _moduleName: string;
constructor() {
super();
this._domId = viewIdCounter++;
@ -651,7 +653,7 @@ export abstract class ViewBase extends Observable implements ViewBaseDefinition
}
public resetNativeView(): void {
//
//
}
private resetNativeViewInternal(): void {
@ -688,7 +690,7 @@ export abstract class ViewBase extends Observable implements ViewBaseDefinition
this._context = context;
// This will account for nativeView that is created in createNativeView, recycled
// This will account for nativeView that is created in createNativeView, recycled
// or for backward compatability - set before _setupUI in iOS contructor.
let nativeView = this.nativeViewProtected;

View File

@ -5,7 +5,7 @@ import {
} from ".";
import {
ViewBase, Property, booleanConverter, EventData, layout,
ViewBase, Property, booleanConverter, eachDescendant, EventData, layout,
getEventOrGestureName, traceEnabled, traceWrite, traceCategories,
InheritedProperty,
ShowModalOptions
@ -105,6 +105,14 @@ export abstract class ViewCommon extends ViewBase implements ViewDefinition {
this._updateStyleScope(cssFileName);
}
public changeCssFile(cssFileName: string): void {
const scope = this._styleScope;
if (scope && cssFileName) {
scope.changeCssFile(cssFileName);
this._onCssStateChange();
}
}
public _updateStyleScope(cssFileName?: string, cssString?: string, css?: string): void {
let scope = this._styleScope;
if (!scope) {
@ -128,6 +136,37 @@ export abstract class ViewCommon extends ViewBase implements ViewDefinition {
}
}
public _onLivesync(context?: ModuleContext): boolean {
_rootModalViews.forEach(v => v.closeModal());
_rootModalViews.length = 0;
// Currently, we pass `context` only for style modules
if (context && context.path) {
return this.changeLocalStyles(context.path);
}
return false;
}
private changeLocalStyles(contextPath: string): boolean {
if (!this.changeStyles(this, contextPath)) {
eachDescendant(this, (child: ViewBase) => {
this.changeStyles(child, contextPath);
return true;
});
}
// Do not execute frame navigation for a change in styles
return true;
}
private changeStyles(view: ViewBase, contextPath: string): boolean {
if (view._moduleName && contextPath.includes(view._moduleName)) {
(<this>view).changeCssFile(contextPath);
return true;
}
return false;
}
_setupAsRootView(context: any): void {
super._setupAsRootView(context);
if (!this._styleScope) {
@ -202,12 +241,6 @@ export abstract class ViewCommon extends ViewBase implements ViewDefinition {
}
}
_onLivesync(): boolean {
_rootModalViews.forEach(v => v.closeModal());
_rootModalViews.length = 0;
return false;
}
public onBackPressed(): boolean {
return false;
}

View File

@ -2,7 +2,8 @@
* @module "ui/core/view"
*/ /** */
///<reference path="../../../tns-core-modules.d.ts" /> Include global typings
/// <reference path="../../../tns-core-modules.d.ts" />
import { ViewBase, Property, InheritedProperty, EventData, Color } from "../view-base";
import { Animation, AnimationDefinition, AnimationPromise } from "../../animation";
import { HorizontalAlignment, VerticalAlignment, Visibility, Length, PercentLength } from "../../styling/style-properties";
@ -18,14 +19,14 @@ export function PseudoClassHandler(...pseudoClasses: string[]): MethodDecorator;
/**
* Specifies the type name for the instances of this View class,
* that is used when matching CSS type selectors.
*
*
* Usage:
* ```
* @CSSType("Button")
* class Button extends View {
* }
* ```
*
*
* Internally the decorator set `Button.prototype.cssType = "Button"`.
* @param type The type name, e. g. "Button", "Label", etc.
*/
@ -50,8 +51,8 @@ export type px = number;
export type percent = number;
/**
* The Point interface describes a two dimensional location.
* It has two properties x and y, representing the x and y coordinate of the location.
* The Point interface describes a two dimensional location.
* It has two properties x and y, representing the x and y coordinate of the location.
*/
export interface Point {
/**
@ -66,8 +67,8 @@ export interface Point {
}
/**
* The Size interface describes abstract dimensions in two dimensional space.
* It has two properties width and height, representing the width and height values of the size.
* The Size interface describes abstract dimensions in two dimensional space.
* It has two properties width and height, representing the width and height values of the size.
*/
export interface Size {
/**
@ -99,8 +100,8 @@ export interface ShownModallyData extends EventData {
}
/**
* This class is the base class for all UI components.
* A View occupies a rectangular area on the screen and is responsible for drawing and layouting of all UI components within.
* This class is the base class for all UI components.
* A View occupies a rectangular area on the screen and is responsible for drawing and layouting of all UI components within.
*/
export abstract class View extends ViewBase {
/**
@ -475,13 +476,13 @@ export abstract class View extends ViewBase {
* [Deprecated. Please use the on() instead.] Adds a gesture observer.
* @param type - Type of the gesture.
* @param callback - A function that will be executed when gesture is received.
* @param thisArg - An optional parameter which will be used as `this` context for callback execution.
* @param thisArg - An optional parameter which will be used as `this` context for callback execution.
*/
observe(type: GestureTypes, callback: (args: GestureEventData) => void, thisArg?: any);
/**
* A basic method signature to hook an event listener (shortcut alias to the addEventListener method).
* @param eventNames - String corresponding to events (e.g. "propertyChange"). Optionally could be used more events separated by `,` (e.g. "propertyChange", "change") or you can use gesture types.
* @param eventNames - String corresponding to events (e.g. "propertyChange"). Optionally could be used more events separated by `,` (e.g. "propertyChange", "change") or you can use gesture types.
* @param callback - Callback function which will be executed when event is raised.
* @param thisArg - An optional parameter which will be used as `this` context for callback execution.
*/
@ -527,12 +528,12 @@ export abstract class View extends ViewBase {
modal: View;
/**
* Animates one or more properties of the view based on the supplied options.
* Animates one or more properties of the view based on the supplied options.
*/
public animate(options: AnimationDefinition): AnimationPromise;
/**
* Creates an Animation object based on the supplied options.
* Creates an Animation object based on the supplied options.
*/
public createAnimation(options: AnimationDefinition): Animation;
@ -562,7 +563,7 @@ export abstract class View extends ViewBase {
public getActualSize(): Size;
/**
* Derived classes can override this method to handle Android back button press.
* Derived classes can override this method to handle Android back button press.
*/
onBackPressed(): boolean;
@ -575,7 +576,7 @@ export abstract class View extends ViewBase {
/**
* @private
* Adds a new values to current css.
* @param cssString - A valid css which will be added to current css.
* @param cssString - A valid css which will be added to current css.
*/
addCss(cssString: string): void;
@ -586,13 +587,20 @@ export abstract class View extends ViewBase {
*/
addCssFile(cssFileName: string): void;
/**
* @private
* Changes the current css to the content of the file.
* @param cssFileName - A valid file name (from the application root) which contains a valid css.
*/
changeCssFile(cssFileName: string): void;
// Lifecycle events
_getNativeViewsCount(): number;
_eachLayoutView(callback: (View) => void): void;
/**
* Iterates over children of type View.
* Iterates over children of type View.
* @param callback Called for each child of type View. Iteration stops if this method returns falsy value.
*/
public eachChildView(callback: (view: View) => boolean): void;
@ -673,7 +681,7 @@ export abstract class View extends ViewBase {
/**
* @private
*/
_onLivesync(): boolean;
_onLivesync(context?: { type: string, path: string }): boolean;
/**
* @private
*/
@ -681,9 +689,9 @@ export abstract class View extends ViewBase {
/**
* Updates styleScope or create new styleScope.
* @param cssFileName
* @param cssString
* @param css
* @param cssFileName
* @param cssString
* @param css
*/
_updateStyleScope(cssFileName?: string, cssString?: string, css?: string): void;
@ -715,7 +723,7 @@ export abstract class View extends ViewBase {
}
/**
* Base class for all UI components that are containers.
* Base class for all UI components that are containers.
*/
export class ContainerView extends View {
/**
@ -725,7 +733,7 @@ export class ContainerView extends View {
}
/**
* Base class for all UI components that implement custom layouts.
* Base class for all UI components that implement custom layouts.
*/
export class CustomLayoutView extends ContainerView {
//@private

View File

@ -76,7 +76,7 @@ export class FrameBase extends CustomLayoutView implements FrameDefinition {
if (backstackIndex !== -1) {
backstack = backstackIndex;
} else {
// NOTE: We don't search for entries in navigationQueue because there is no way for
// NOTE: We don't search for entries in navigationQueue because there is no way for
// developer to get reference to BackstackEntry unless transition is completed.
// At that point the entry is put in the backstack array.
// If we start to return Backstack entry from navigate method then
@ -153,7 +153,7 @@ export class FrameBase extends CustomLayoutView implements FrameDefinition {
// }
// let currentPage = this._currentEntry.resolvedPage;
// let currentNavigationEntry = this._currentEntry.entry;
// let currentNavigationEntry = this._currentEntry.entry;
// if (currentPage["isBiOrientational"] && currentNavigationEntry.moduleName) {
// if (this.canGoBack()){
// this.goBack();
@ -162,7 +162,7 @@ export class FrameBase extends CustomLayoutView implements FrameDefinition {
// currentNavigationEntry.backstackVisible = false;
// }
// // Re-navigate to the same page so the other (.port or .land) xml is loaded.
// this.navigate(currentNavigationEntry);
// this.navigate(currentNavigationEntry);
// }
// }
@ -224,7 +224,7 @@ export class FrameBase extends CustomLayoutView implements FrameDefinition {
newPage.onNavigatedTo(isBack);
// Reset executing entry after NavigatedTo is raised;
// we do not want to execute two navigations in parallel in case
// we do not want to execute two navigations in parallel in case
// additional navigation is triggered from the NavigatedTo handler.
this._executingEntry = null;
}
@ -259,7 +259,7 @@ export class FrameBase extends CustomLayoutView implements FrameDefinition {
return true;
}
}
return false;
}
@ -563,34 +563,35 @@ export class FrameBase extends CustomLayoutView implements FrameDefinition {
return result;
}
public _onLivesync(): boolean {
super._onLivesync();
if (!this._currentEntry || !this._currentEntry.entry) {
return false;
}
const currentEntry = this._currentEntry.entry;
const newEntry: NavigationEntry = {
animated: false,
clearHistory: true,
context: currentEntry.context,
create: currentEntry.create,
moduleName: currentEntry.moduleName,
backstackVisible: currentEntry.backstackVisible
}
// If create returns the same page instance we can't recreate it.
// Instead of navigation set activity content.
// This could happen if current page was set in XML as a Page instance.
if (newEntry.create) {
const page = newEntry.create();
if (page === this.currentPage) {
public _onLivesync(context?: ModuleContext): boolean {
// Execute a navigation if not handled on `View` level
if (!super._onLivesync(context)) {
if (!this._currentEntry || !this._currentEntry.entry) {
return false;
}
}
this.navigate(newEntry);
const currentEntry = this._currentEntry.entry;
const newEntry: NavigationEntry = {
animated: false,
clearHistory: true,
context: currentEntry.context,
create: currentEntry.create,
moduleName: currentEntry.moduleName,
backstackVisible: currentEntry.backstackVisible
}
// If create returns the same page instance we can't recreate it.
// Instead of navigation set activity content.
// This could happen if current page was set in XML as a Page instance.
if (newEntry.create) {
const page = newEntry.create();
if (page === this.currentPage) {
return false;
}
}
this.navigate(newEntry);
}
return true;
}
}

View File

@ -82,13 +82,13 @@ function getAttachListener(): android.view.View.OnAttachStateChangeListener {
return attachStateChangeListener;
}
export function reloadPage(): void {
export function reloadPage(context?: ModuleContext): void {
const activity = application.android.foregroundActivity;
const callbacks: AndroidActivityCallbacks = activity[CALLBACKS];
if (callbacks) {
const rootView: View = callbacks.getRootView();
if (!rootView || !rootView._onLivesync()) {
if (!rootView || !rootView._onLivesync(context)) {
callbacks.resetActivityContent(activity);
}
} else {
@ -153,7 +153,7 @@ export class Frame extends FrameBase {
// In case activity was destroyed because of back button pressed (e.g. app exit)
// and application is restored from recent apps, current fragment isn't recreated.
// In this case call _navigateCore in order to recreate the current fragment.
// Don't call navigate because it will fire navigation events.
// Don't call navigate because it will fire navigation events.
// As JS instances are alive it is already done for the current page.
if (!this.isLoaded || this._executingEntry || !this._attachedToWindow) {
return;
@ -183,13 +183,13 @@ export class Frame extends FrameBase {
const entry = this._currentEntry;
if (entry && manager && !manager.findFragmentByTag(entry.fragmentTag)) {
// Simulate first navigation (e.g. no animations or transitions)
// we need to cache the original animation settings so we can restore them later; otherwise as the
// simulated first navigation is not animated (it is actually a zero duration animator) the "popExit" animation
// we need to cache the original animation settings so we can restore them later; otherwise as the
// simulated first navigation is not animated (it is actually a zero duration animator) the "popExit" animation
// is broken when transaction.setCustomAnimations(...) is used in a scenario with:
// 1) forward navigation
// 2) suspend / resume app
// 3) back navigation -- the exiting fragment is erroneously animated with the exit animator from the
// simulated navigation (NoTransition, zero duration animator) and thus the fragment immediately disappears;
// 3) back navigation -- the exiting fragment is erroneously animated with the exit animator from the
// simulated navigation (NoTransition, zero duration animator) and thus the fragment immediately disappears;
// the user only sees the animation of the entering fragment as per its specific enter animation settings.
// NOTE: we are restoring the animation settings in Frame.setCurrent(...) as navigation completes asynchronously
this._cachedAnimatorState = getAnimatorState(this._currentEntry);
@ -727,7 +727,7 @@ function ensureFragmentClass() {
return;
}
// this require will apply the FragmentClass implementation
// this require will apply the FragmentClass implementation
require("ui/frame/fragment");
if (!fragmentClass) {
@ -855,11 +855,11 @@ class FragmentCallbacksImplementation implements AndroidFragmentCallbacks {
entry.viewSavedState = null;
}
// fixes 'java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first'.
// fixes 'java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first'.
// on app resume in nested frame scenarios with support library version greater than 26.0.0
// HACK: this whole code block shouldn't be necessary as the native view is supposedly removed from its parent
// right after onDestroyView(...) is called but for some reason the fragment view (page) still thinks it has a
// parent while its supposed parent believes it properly removed its children; in order to "force" the child to
// HACK: this whole code block shouldn't be necessary as the native view is supposedly removed from its parent
// right after onDestroyView(...) is called but for some reason the fragment view (page) still thinks it has a
// parent while its supposed parent believes it properly removed its children; in order to "force" the child to
// lose its parent we temporarily add it to the parent, and then remove it (addViewInLayout doesn't trigger layout pass)
const nativeView = page.nativeViewProtected;
if (nativeView != null) {
@ -908,8 +908,8 @@ class FragmentCallbacksImplementation implements AndroidFragmentCallbacks {
}
// [nested frames / fragments] see https://github.com/NativeScript/NativeScript/issues/6629
// retaining reference to a destroyed fragment here somehow causes a cryptic
// "IllegalStateException: Failure saving state: active fragment has cleared index: -1"
// retaining reference to a destroyed fragment here somehow causes a cryptic
// "IllegalStateException: Failure saving state: active fragment has cleared index: -1"
// in a specific mixed parent / nested frame navigation scenario
entry.fragment = null;
@ -1019,15 +1019,15 @@ class ActivityCallbacksImplementation implements AndroidActivityCallbacks {
}
// NOTE: activity.onPostResume() is called when activity resume is complete and we can
// safely raise the application resume event;
// safely raise the application resume event;
// onActivityResumed(...) lifecycle callback registered in application is called too early
// and raising the application resume event there causes issues like
// and raising the application resume event there causes issues like
// https://github.com/NativeScript/NativeScript/issues/6708
if ((<any>activity).isNativeScriptActivity) {
const args = <application.ApplicationEventData>{
eventName: application.resumeEvent,
object: application.android,
android: activity
object: application.android,
android: activity
};
application.notify(args);
application.android.paused = false;
@ -1151,7 +1151,7 @@ class ActivityCallbacksImplementation implements AndroidActivityCallbacks {
// Paths that go trough this method:
// 1. Application initial start - there is no rootView in callbacks.
// 2. Application revived after Activity is destroyed. this._rootView should have been restored by id in onCreate.
// 2. Application revived after Activity is destroyed. this._rootView should have been restored by id in onCreate.
// 3. Livesync if rootView has no custom _onLivesync. this._rootView should have been cleared upfront. Launch event should not fired
// 4. _resetRootView method. this._rootView should have been cleared upfront. Launch event should not fired
private setActivityContent(

View File

@ -3,7 +3,8 @@
* @module "ui/page"
*/ /** */
///<reference path="../../tns-core-modules.d.ts" /> Include global typings
/// <reference path="../../tns-core-modules.d.ts" />
import { ShownModallyData } from "../core/view";
import { ContentView, EventData, Property, Color, CssProperty, Style } from "../content-view";
import { Frame } from "../frame";
@ -106,7 +107,7 @@ export class Page extends ContentView {
/**
* A basic method signature to hook an event listener (shortcut alias to the addEventListener method).
* @param eventNames - String corresponding to events (e.g. "propertyChange"). Optionally could be used more events separated by `,` (e.g. "propertyChange", "change").
* @param eventNames - String corresponding to events (e.g. "propertyChange"). Optionally could be used more events separated by `,` (e.g. "propertyChange", "change").
* @param callback - Callback function which will be executed when event is raised.
* @param thisArg - An optional parameter which will be used as `this` context for callback execution.
*/
@ -198,4 +199,4 @@ export const statusBarStyleProperty: CssProperty<Style, "light" | "dark">;
/**
* Property backing androidStatusBarBackground.
*/
export const androidStatusBarBackgroundProperty: CssProperty<Style, Color>;
export const androidStatusBarBackgroundProperty: CssProperty<Style, Color>;

View File

@ -29,6 +29,7 @@ export class CssState {
export class StyleScope {
public css: string;
public addCss(cssString: string, cssFileName: string): void;
public changeCssFile(cssFileName: string): void;
public static createSelectorsFromCss(css: string, cssFileName: string, keyframes: Object): RuleSet[];
public static createSelectorsFromImports(tree: SyntaxTree, keyframes: Object): RuleSet[];

View File

@ -569,6 +569,19 @@ export class StyleScope {
this.appendCss(null, cssFileName);
}
@profile
private changeCssFile(cssFileName: string): void {
if (!cssFileName) {
return;
}
const cssSelectors = CSSSource.fromURI(cssFileName, this._keyframes);
this._css = cssSelectors.source;
this._localCssSelectors = cssSelectors.selectors;
this._localCssSelectorVersion++;
this.ensureSelectors();
}
@profile
private setCss(cssString: string, cssFileName?): void {
this._css = cssString;

View File

@ -12,6 +12,7 @@ import { textTransformProperty, TextTransform, getTransformedText } from "../tex
import { fromFileOrResource } from "../../image-source";
import { RESOURCE_PREFIX, ad } from "../../utils/utils";
import { Frame } from "../frame";
import * as application from "../../application";
export * from "./tab-view-common";
@ -242,7 +243,7 @@ function createTabItemSpec(item: TabViewItem): org.nativescript.widgets.TabItemS
const is = fromFileOrResource(item.iconSource);
if (is) {
// TODO: Make this native call that accepts string so that we don't load Bitmap in JS.
result.iconDrawable = new android.graphics.drawable.BitmapDrawable(is.android);
result.iconDrawable = new android.graphics.drawable.BitmapDrawable(application.android.context.getResources(), is.android);
} else {
traceMissingIcon(item.iconSource);
}
@ -643,11 +644,13 @@ export class TabView extends TabViewBase {
}
[selectedIndexProperty.setNative](value: number) {
const smoothScroll = this.androidTabsPosition === "top";
if (traceEnabled()) {
traceWrite("TabView this._viewPager.setCurrentItem(" + value + ", true);", traceCategory);
traceWrite("TabView this._viewPager.setCurrentItem(" + value + ", " + smoothScroll + ");", traceCategory);
}
this._viewPager.setCurrentItem(value, true);
this._viewPager.setCurrentItem(value, smoothScroll);
}
[itemsProperty.getDefault](): TabViewItem[] {

View File

@ -100,7 +100,7 @@ export class TabView extends View {
ios: any /* UITabBarController */;
/**
* Gets or set the UIImageRenderingMode of the tab icons in iOS.
* Gets or set the UIImageRenderingMode of the tab icons in iOS. Defaults to "automatic"
* Valid values are:
* - automatic
* - alwaysOriginal

View File

@ -131,7 +131,8 @@ export class WebView extends WebViewBase {
public _loadUrl(src: string) {
if (src.startsWith("file:///")) {
this.ios.loadFileURLAllowingReadAccessToURL(NSURL.URLWithString(src), NSURL.URLWithString(src));
var cachePath = src.substring(0, src.lastIndexOf("/"));
this.ios.loadFileURLAllowingReadAccessToURL(NSURL.URLWithString(src), NSURL.URLWithString(cachePath));
} else {
this.ios.loadRequest(NSURLRequest.requestWithURL(NSURL.URLWithString(src)));
}
@ -160,4 +161,4 @@ export class WebView extends WebViewBase {
public reload() {
this.ios.reload();
}
}
}

View File

@ -1,10 +1,16 @@
import {
write as traceWrite, categories as traceCategories, messageType as traceMessageType
write as traceWrite,
categories as traceCategories,
messageType as traceMessageType,
} from "../trace";
export * from "./utils-common";
import { getNativeApplication, android as androidApp } from "../application";
import { device } from "../platform";
import { FileSystemAccess } from "../file-system/file-system-access";
const MIN_URI_SHARE_RESTRICTED_APK_VERSION = 24;
export module layout {
let density: number;
@ -224,3 +230,145 @@ export function openUrl(location: string): boolean {
}
return true;
}
/**
* Check whether external storage is read only
*
* @returns {boolean} whether the external storage is read only
*/
function isExternalStorageReadOnly(): boolean {
const extStorageState = android.os.Environment.getExternalStorageState();
if (android.os.Environment.MEDIA_MOUNTED_READ_ONLY === extStorageState) {
return true;
}
return false;
}
/**
* Checks whether external storage is available
*
* @returns {boolean} whether external storage is available
*/
function isExternalStorageAvailable(): boolean {
const extStorageState = android.os.Environment.getExternalStorageState();
if (android.os.Environment.MEDIA_MOUNTED === extStorageState) {
return true;
}
return false;
}
/**
* Detect the mimetype of a file at a given path
*
* @param {string} filePath
* @returns {string} mimetype
*/
function getMimeTypeNameFromExtension(filePath: string): string {
const mimeTypeMap = android.webkit.MimeTypeMap.getSingleton();
const extension = new FileSystemAccess()
.getFileExtension(filePath)
.replace(".", "")
.toLowerCase();
return mimeTypeMap.getMimeTypeFromExtension(extension);
}
/**
* Open a file
*
* @param {string} filePath
* @returns {boolean} whether opening the file succeeded or not
*/
export function openFile(filePath: string): boolean {
const context = ad.getApplicationContext();
try {
// Ensure external storage is available
if (!isExternalStorageAvailable()) {
traceWrite(
`
External storage is unavailable (please check app permissions).
Applications cannot access internal storage of other application on Android (see: https://developer.android.com/guide/topics/data/data-storage).
`,
traceCategories.Error,
traceMessageType.error,
);
return false;
}
// Ensure external storage is available
if (isExternalStorageReadOnly()) {
traceWrite("External storage is read only", traceCategories.Error, traceMessageType.error);
return false;
}
// Determine file mimetype & start creating intent
const mimeType = getMimeTypeNameFromExtension(filePath);
const intent = new android.content.Intent(android.content.Intent.ACTION_VIEW);
const chooserIntent = android.content.Intent.createChooser(intent, "Open File...");
intent.addFlags(android.content.Intent.FLAG_ACTIVITY_NEW_TASK);
chooserIntent.addFlags(android.content.Intent.FLAG_ACTIVITY_NEW_TASK);
// Android SDK <28 only requires starting the chooser Intent straight forwardly
const sdkVersion = parseInt(device.sdkVersion, 10);
if (sdkVersion && sdkVersion < MIN_URI_SHARE_RESTRICTED_APK_VERSION) {
traceWrite(
`detected sdk version ${sdkVersion} (< ${MIN_URI_SHARE_RESTRICTED_APK_VERSION}), using simple openFile`,
traceCategories.Debug
);
intent.setDataAndType(android.net.Uri.fromFile(new java.io.File(filePath)), mimeType);
context.startActivity(chooserIntent);
return true;
}
traceWrite(
`detected sdk version ${sdkVersion} (>= ${MIN_URI_SHARE_RESTRICTED_APK_VERSION}), using URI openFile`,
traceCategories.Debug
);
// Android SDK 24+ introduced file system permissions changes that disallow
// exposing URIs between applications
//
// see: https://developer.android.com/reference/android/os/FileUriExposedException
// see: https://github.com/NativeScript/NativeScript/issues/5661#issuecomment-456405380
const providerName = `${context.getPackageName()}.provider`;
traceWrite(`fully-qualified provider name [${providerName}]`, traceCategories.Debug);
const apkURI = android.support.v4.content.FileProvider.getUriForFile(
context,
providerName,
new java.io.File(filePath),
);
// Set flags & URI as data type on the view action
intent.addFlags(android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION);
chooserIntent.addFlags(android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION);
// Finish intent setup
intent.setDataAndType(apkURI, mimeType);
context.startActivity(chooserIntent);
return true;
} catch (err) {
const msg = err.message ? `: ${err.message}` : "";
traceWrite(`Error in openFile${msg}`, traceCategories.Error, traceMessageType.error);
if (msg &&
msg.includes("Attempt to invoke virtual method") &&
msg.includes("android.content.pm.ProviderInfo.loadXmlMetaData") &&
msg.includes("on a null object reference")) {
// Alert user to possible fix
traceWrite(
`
Please ensure you have your manifest correctly configured with the FileProvider.
(see: https://developer.android.com/reference/android/support/v4/content/FileProvider#ProviderDefinition)
`,
traceCategories.Error,
);
}
return false;
}
}

View File

@ -285,6 +285,12 @@ export function isDataURI(uri: string): boolean
*/
export function openUrl(url: string): boolean
/**
* Opens file.
* @param {string} filePath The file.
*/
export function openFile(filePath: string): boolean
/**
* Escapes special regex symbols (., *, ^, $ and so on) in string in order to create a valid regex from it.
* @param source The original value.

View File

@ -1,6 +1,6 @@
{
"name": "tns-platform-declarations",
"version": "5.2.2",
"version": "5.3.0",
"description": "Platform-specific TypeScript declarations for NativeScript for accessing native objects",
"main": "",
"scripts": {