├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── pull_request_template.md ├── .gitignore ├── .travis.yml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── DevelopmentWorkflow.md ├── LICENSE ├── README.md ├── android ├── .gitignore ├── README.md ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── widgets │ ├── .gitignore │ ├── build.gradle │ ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── proguard-rules.pro │ └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── org │ └── nativescript │ ├── Process.java │ └── widgets │ ├── AbsoluteLayout.java │ ├── AnimatorHelper.java │ ├── Async.java │ ├── BorderDrawable.java │ ├── CSSValue.java │ ├── CommonLayoutParams.java │ ├── ContentLayout.java │ ├── CustomTypefaceSpan.java │ ├── Dock.java │ ├── DockLayout.java │ ├── FlexLine.java │ ├── FlexboxLayout.java │ ├── FragmentBase.java │ ├── GridLayout.java │ ├── GridUnitType.java │ ├── HorizontalScrollView.java │ ├── Image │ ├── AsyncTask.java │ ├── Cache.java │ ├── DiskLruCache.java │ ├── Fetcher.java │ ├── Utils.java │ └── Worker.java │ ├── ImageView.java │ ├── ItemSpec.java │ ├── LayoutBase.java │ ├── LinearGradientDefinition.java │ ├── Orientation.java │ ├── OriginPoint.java │ ├── SegmentedBarColorDrawable.java │ ├── StackLayout.java │ ├── TabItemSpec.java │ ├── TabLayout.java │ ├── TabStrip.java │ ├── TabViewPager.java │ ├── VerticalScrollView.java │ ├── ViewHelper.java │ ├── WrapLayout.java │ └── image │ └── BitmapOwner.java ├── build.android.sh ├── build.ios.sh ├── build.sh ├── ios ├── README.md ├── TNSWidgets │ ├── TNSProcess.h │ ├── TNSProcess.m │ ├── TNSWidgets.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ ├── xcshareddata │ │ │ │ └── WorkspaceSettings.xcsettings │ │ │ └── xcuserdata │ │ │ │ └── cankov.xcuserdatad │ │ │ │ └── UserInterfaceState.xcuserstate │ │ └── xcuserdata │ │ │ └── cankov.xcuserdatad │ │ │ └── xcschemes │ │ │ ├── TNSWidgets.xcscheme │ │ │ └── xcschememanagement.plist │ ├── TNSWidgets │ │ ├── Info.plist │ │ ├── NSObject+PropertyBag.h │ │ ├── NSObject+PropertyBag.m │ │ ├── NSObject+Swizzling.h │ │ ├── NSObject+Swizzling.m │ │ ├── TNSLabel.h │ │ ├── TNSLabel.m │ │ ├── TNSWidgets.h │ │ ├── UIImage+TNSBlocks.h │ │ ├── UIImage+TNSBlocks.m │ │ ├── UIView+PassThroughParent.h │ │ └── UIView+PassThroughParent.m │ └── TNSWidgetsTests │ │ ├── Info.plist │ │ └── TNSWidgetsTests.m └── build.sh └── package.json /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: 'We really appreciate your effort to provide feedback. Before opening a new 4 | issue, please make sure that this case is not already reported in GitHub as an 5 | issue or in StackOverflow as a question.' 6 | 7 | --- 8 | 9 | **Environment** 10 | Provide version numbers for the following components (information can be retrieved by running `tns info` in your project folder or by inspecting the `package.json` of the project): 11 | - CLI: 12 | - Cross-platform modules: 13 | - Android Runtime: 14 | - iOS Runtime: 15 | - Plugin(s): 16 | 17 | **Describe the bug** 18 | 19 | 20 | **To Reproduce** 21 | 22 | 23 | **Expected behavior** 24 | 25 | **Sample project** 26 | 27 | 28 | **Additional context** 29 | 30 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | 7 | **Is your feature request related to a problem? Please describe.** 8 | 9 | 10 | **Describe the solution you'd like** 11 | 12 | 13 | **Describe alternatives you've considered** 14 | 15 | 16 | **Additional context** 17 | 18 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | ## PR Checklist 11 | 12 | - [ ] The PR title follows our guidelines: https://github.com/NativeScript/NativeScript/blob/master/CONTRIBUTING.md#commit-messages. 13 | - [ ] There is an issue for the bug/feature this PR is for. To avoid wasting your time, it's best to open a suggestion issue first and wait for approval before working on it. 14 | - [ ] You have signed the [CLA](http://www.nativescript.org/cla). 15 | - [ ] All existing tests are passing 16 | - [ ] Tests for the changes are included 17 | 18 | ## What is the current behavior? 19 | 20 | 21 | ## What is the new behavior? 22 | 23 | 24 | Fixes/Implements/Closes #[Issue Number]. 25 | 26 | 27 | 28 | 37 | 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /dist 2 | /build 3 | 4 | *.iml 5 | 6 | # Proguard folder generated by Eclipse 7 | proguard/ 8 | 9 | # Log Files 10 | *.log 11 | 12 | # Android Studio Navigation editor temp files 13 | .navigation/ 14 | 15 | # Android Studio captures folder 16 | captures/ 17 | 18 | # Built application files 19 | build/ 20 | 21 | ## File-based project format: 22 | *.ipr 23 | *.iws 24 | 25 | # Crashlytics plugin (for Android Studio and IntelliJ) 26 | com_crashlytics_export_strings.xml 27 | crashlytics.properties 28 | crashlytics-build.properties 29 | fabric.properties 30 | 31 | ## Directory-based project format: 32 | .idea/ 33 | 34 | # Local configuration file (sdk path, etc) 35 | local.properties 36 | 37 | # Gradle generated files 38 | .gradle/ 39 | 40 | # Signing files 41 | .signing/ 42 | 43 | # OS-specific files 44 | .DS_Store 45 | .DS_Store? 46 | ios/TNSWidgets/TNSWidgets.xcodeproj/project.xcworkspace/xcuserdata/ 47 | ios/TNSWidgets/TNSWidgets.xcodeproj/xcuserdata/ 48 | ios/TNSWidgets/DerivedData/ 49 | 50 | android/widgets/bin 51 | android/widgets/.settings 52 | android/.project 53 | android/widgets/.project 54 | android/.settings -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | env: 2 | global: 3 | - DATE=$(date +%Y-%m-%d) 4 | - PACKAGE_VERSION=$DATE-$TRAVIS_BUILD_NUMBER 5 | language: objective-c 6 | osx_image: xcode8.3 7 | install: 8 | - brew update 9 | - brew cask install android-sdk 10 | # Suppress output of sdkmanager to keep log under the 4MB limit of travis-ci 11 | - yes | sdkmanager "platforms;android-23" >/dev/null 12 | - yes | sdkmanager "build-tools;23.0.3" >/dev/null 13 | - yes | sdkmanager "extras;android;m2repository" >/dev/null 14 | before_script: 15 | - export ANDROID_HOME=/usr/local/share/android-sdk 16 | script: ./build.sh $PACKAGE_VERSION 17 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # NativeScript Community Code of Conduct 2 | 3 | Our community members come from all walks of life and are all at different stages of their personal and professional journeys. To support everyone, we've prepared a short code of conduct. Our mission is best served in an environment that is friendly, safe, and accepting; free from intimidation or harassment. 4 | 5 | Towards this end, certain behaviors and practices will not be tolerated. 6 | 7 | ## tl;dr 8 | 9 | - Be respectful. 10 | - We're here to help. 11 | - Abusive behavior is never tolerated. 12 | - Violations of this code may result in swift and permanent expulsion from the NativeScript community channels. 13 | 14 | ## Administrators 15 | 16 | - Dan Wilson (@DanWilson on Slack) 17 | - Jen Looper (@jen.looper on Slack) 18 | - TJ VanToll (@tjvantoll on Slack) 19 | 20 | ## Scope 21 | 22 | We expect all members of the NativeScript community, including administrators, users, facilitators, and vendors to abide by this Code of Conduct at all times in our community venues, online and in person, and in one-on-one communications pertaining to NativeScript affairs. 23 | 24 | This policy covers the usage of the NativeScript Slack community, as well as the NativeScript support forums, NativeScript GitHub repositories, the NativeScript website, and any NativeScript-related events. This Code of Conduct is in addition to, and does not in any way nullify or invalidate, any other terms or conditions related to use of NativeScript. 25 | 26 | The definitions of various subjective terms such as "discriminatory", "hateful", or "confusing" will be decided at the sole discretion of the NativeScript administrators. 27 | 28 | ## Friendly, Harassment-Free Space 29 | 30 | We are committed to providing a friendly, safe, and welcoming environment for all, regardless of gender identity, sexual orientation, disability, ethnicity, religion, age, physical appearance, body size, race, or similar personal characteristics. 31 | 32 | We ask that you please respect that people have differences of opinion regarding technical choices, and acknowledge that every design or implementation choice carries a trade-off and numerous costs. There is seldom a single right answer. A difference of technology preferences is never a license to be rude. 33 | 34 | Any spamming, trolling, flaming, baiting, or other attention-stealing behaviour is not welcome, and will not be tolerated. 35 | 36 | Harassing other users of NativeScript is never tolerated, whether via public or private media. 37 | 38 | Avoid using offensive or harassing package names, nicknames, or other identifiers that might detract from a friendly, safe, and welcoming environment for all. 39 | 40 | Harassment includes, but is not limited to: harmful or prejudicial verbal or written comments related to gender identity, sexual orientation, disability, ethnicity, religion, age, physical appearance, body size, race, or similar personal characteristics; inappropriate use of nudity, sexual images, and/or sexually explicit language in public spaces; threats of physical or non-physical harm; deliberate intimidation, stalking or following; harassing photography or recording; sustained disruption of talks or other events; inappropriate physical contact; and unwelcome sexual attention. 41 | 42 | ## Acceptable Content 43 | 44 | The NativeScript administrators reserve the right to make judgement calls about what is and isn't appropriate in published content. These are guidelines to help you be successful in our community. 45 | 46 | Content must contain something applicable to the previously stated goals of the NativeScript community. "Spamming", that is, publishing any form of content that is not applicable, is not allowed. 47 | 48 | Content must not contain illegal or infringing content. You should only publish content to NativeScript properties if you have the right to do so. This includes complying with all software license agreements or other intellectual property restrictions. For example, redistributing an MIT-licensed module with the copyright notice removed, would not be allowed. You will be responsible for any violation of laws or others’ intellectual property rights. 49 | 50 | Content must not be malware. For example, content (code, video, pictures, words, etc.) which is designed to maliciously exploit or damage computer systems, is not allowed. 51 | 52 | Content name, description, and other visible metadata must not include abusive, inappropriate, or harassing content. 53 | 54 | ## Reporting Violations of this Code of Conduct 55 | 56 | If you believe someone is harassing you or has otherwise violated this Code of Conduct, please contact the administrators and send us an abuse report. If this is the initial report of a problem, please include as much detail as possible. It is easiest for us to address issues when we have more context. 57 | 58 | ## Consequences 59 | 60 | All content published to the NativeScript community channels is hosted at the sole discretion of the NativeScript administrators. 61 | 62 | Unacceptable behavior from any community member, including sponsors, employees, customers, or others with decision-making authority, will not be tolerated. 63 | 64 | Anyone asked to stop unacceptable behavior is expected to comply immediately. 65 | 66 | If a community member engages in unacceptable behavior, the NativeScript administrators may take any action they deem appropriate, up to and including a temporary ban or permanent expulsion from the community without warning (and without refund in the case of a paid event or service). 67 | 68 | ## Addressing Grievances 69 | 70 | If you feel you have been falsely or unfairly accused of violating this Code of Conduct, you should notify the administrators. We will do our best to ensure that your grievance is handled appropriately. 71 | 72 | In general, we will choose the course of action that we judge as being most in the interest of fostering a safe and friendly community. 73 | 74 | ## Contact Info 75 | Please contact Dan Wilson @DanWilson if you need to report a problem or address a grievance related to an abuse report. 76 | 77 | You are also encouraged to contact us if you are curious about something that might be "on the line" between appropriate and inappropriate content. We are happy to provide guidance to help you be a successful part of our community. 78 | 79 | ## Credit and License 80 | 81 | This Code of Conduct borrows heavily from the WADE Code of Conduct, which is derived from the NodeBots Code of Conduct, which in turn borrows from the npm Code of Conduct, which was derived from the Stumptown Syndicate Citizen's Code of Conduct, and the Rust Project Code of Conduct. 82 | 83 | This document may be reused under a Creative Commons Attribution-ShareAlike License. -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to NativeScript Core Modules Widgets 2 | 3 | :+1: First of all, thank you for taking the time to contribute! :+1: 4 | 5 | Here are some guides on how to do that: 6 | 7 | 8 | 9 | - [Code of Conduct](#code-of-conduct) 10 | - [Reporting Bugs](#reporting-bugs) 11 | - [Requesting Features](#requesting-features) 12 | - [Submitting a PR](#submitting-a-pr) 13 | - [Where to Start](#where-to-start) 14 | 15 | 16 | 17 | ## Code of Conduct 18 | Help us keep a healthy and open community. We expect all participants in this project to adhere to the [NativeScript Code Of Conduct](https://github.com/NativeScript/codeofconduct). 19 | 20 | 21 | ## Reporting Bugs 22 | 23 | 1. Always update to the most recent master release; the bug may already be resolved. 24 | 2. Search for similar issues in the issues list for this repo; it may already be an identified problem. 25 | 3. If this is a bug or problem that is clear, simple, and is unlikely to require any discussion -- it is OK to open an issue on GitHub with a reproduction of the bug including workflows and screenshots. If possible, submit a Pull Request with a failing test, entire application or module. If you'd rather take matters into your own hands, fix the bug yourself (jump down to the [Submitting a PR](#submitting-a-pr) section). 26 | 27 | ## Requesting Features 28 | 29 | 1. Use Github Issues to submit feature requests. 30 | 2. First, search for a similar request and extend it if applicable. This way it would be easier for the community to track the features. 31 | 3. When requesting a new feature, please provide as much detail as possible about why you need the feature in your apps. We prefer that you explain a need rather than explain a technical solution for it. That might trigger a nice conversation on finding the best and broadest technical solution to a specific need. 32 | 33 | ## Submitting a PR 34 | 35 | Before you begin: 36 | * Read and sign the [NativeScript Contribution License Agreement](http://www.nativescript.org/cla). 37 | * Make sure there is an issue for the bug or feature you will be working on. 38 | 39 | Following these steps is the best way to get you code included in the project: 40 | 41 | 1. Fork and clone the tns-core-modules-widgets repo: 42 | ```bash 43 | git clone https://github.com//tns-core-modules-widgets.git 44 | # Navigate to the newly cloned directory 45 | cd tns-core-modules-widgets 46 | # Add an "upstream" remote pointing to the original repo. 47 | git remote add upstream https://github.com/NativeScript/tns-core-modules-widgets.git 48 | ``` 49 | 50 | 2. Read our [development workflow guide](DevelopmentWorkflow.md) for local setup: 51 | 52 | 3. Create a branch for your PR 53 | ```bash 54 | git checkout -b master 55 | ``` 56 | 57 | 4. The fun part! Make your code changes. Make sure you: 58 | - Follow the [code conventions guide](https://github.com/NativeScript/NativeScript/blob/master/CodingConvention.md). 59 | - Write unit tests for your fix or feature. 60 | 61 | 5. Before you submit your PR: 62 | - Rebase your changes to the latest master: `git pull --rebase upstream master`. 63 | - Ensure all unit test are green. Check [running unit tests](DevelopmentWorkflow.md#running-the-tests). 64 | - Ensure your changes pass tslint validation. (run `npm run tslint` in the root of the repo). 65 | 66 | 6. Push your fork. If you have rebased you might have to use force-push your branch: 67 | ``` 68 | git push origin --force 69 | ``` 70 | 71 | 7. [Submit your pull request](https://github.com/NativeScript/tns-core-modules-widgets/compare). Please, fill in the Pull Request template - it will help us better understand the PR and increase the chances of it getting merged quickly. 72 | 73 | It's our turn from there on! We will review the PR and discuss changes you might have to make before merging it! Thanks! 74 | 75 | 76 | ## Where to Start 77 | 78 | If you want to contribute, but you are not sure where to start - look for issues labeled [`help wanted`](https://github.com/NativeScript/tns-core-modules-widgets/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22). 79 | -------------------------------------------------------------------------------- /DevelopmentWorkflow.md: -------------------------------------------------------------------------------- 1 | # Development Workflow 2 | 3 | 4 | 5 | - [Prerequisites](#prerequisites) 6 | - [How to Build the Package](#how-to-build-the-package) 7 | - [How to Build Android](#how-to-build-android) 8 | - [How to Build iOS](#how-to-build-ios) 9 | - [How to Use in an Application](#how-to-use-in-an-application) 10 | 11 | 12 | 13 | ## Prerequisites 14 | 15 | Install your native toolchain and NativeScript as described in the docs: https://docs.nativescript.org/setup/quick-setup. In order to open the native Android and iOS project, you need Android Studio and Xcode respectively. 16 | 17 | ## How to Build the Package 18 | 19 | On macOS you can execute: 20 | 21 | ```shell 22 | $ ./build.sh 23 | ``` 24 | 25 | This script builds both Android and iOS, assembles the package at `./dist/package` and packs it as `./dist/tns-core-modules-widgets-*.tgz`. 26 | 27 | ## How to Build Android 28 | 29 | On Unix-like operating systems you can execute: 30 | 31 | ```shell 32 | $ ./build.android.sh 33 | ``` 34 | This script builds only the Android project, assembles the package at `./dist/package` and packs it as `./dist/tns-core-modules-widgets-*.tgz`. The output file is available at `./android/widgets/build/outputs/aar/widgets-release.aar`. 35 | 36 | **NOTE:** To run bash script on Windows you can install [GIT SCM](https://git-for-windows.github.io/) and use Git Bash. 37 | 38 | ## How to Build iOS 39 | 40 | On macOS you can execute: 41 | 42 | ```shell 43 | $ ./build.ios.sh 44 | ``` 45 | This script builds only the Xcode project, assembles the package at `./dist/package` and packs it as `./dist/tns-core-modules-widgets-*.tgz`. The output native iOS framework is available at `./ios/TNSWidgets/build/TNSWidgets.framework`. 46 | 47 | ## How to Use in an Application 48 | 49 | You could link the `tns-core-modules-widgets` plugin package to your application through the steps listed below. 50 | 51 | In the `./dist/package` folder execute: 52 | 53 | ``` 54 | npm link 55 | ``` 56 | 57 | In your application project folder execute: 58 | 59 | ``` 60 | npm link tns-core-modules-widgets 61 | ``` 62 | 63 | Build the plugin with the above-mentioned commands after each change you would like to test. 64 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright (c) 2015-2019 Progress Software Corporation 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **__This repository is deprecated__ in favor** of [NativeScirpt/NativeScript/tns-core-modules-widgets](https://github.com/NativeScript/NativeScript). 2 | 3 | # NativeScript Core Modules Widgets 4 | [![Build Status](https://travis-ci.org/NativeScript/tns-core-modules-widgets.svg?branch=master)](https://travis-ci.org/NativeScript/tns-core-modules-widgets) 5 | 6 | This repository contains the source code of the `tns-core-modules-widgets` library. This library represents native code (Java and Objective-C) used by the NativeScript [`core modules`](https://github.com/NativeScript/NativeScript/tree/master/tns-core-modules). 7 | 8 | [NativeScript](https://www.nativescript.org/) is a framework which enables developers to write truly native mobile applications for Android and iOS using JavaScript and CSS. 9 | 10 | 11 | 12 | - [Contribute](#contribute) 13 | - [Get Help](#get-help) 14 | 15 | 16 | 17 | ## Contribute 18 | We love PRs! Check out the [contributing guidelines](CONTRIBUTING.md) and [development workflow for local setup](DevelopmentWorkflow.md). If you want to contribute, but you are not sure where to start - look for issues labeled [`help wanted`](https://github.com/NativeScript/tns-core-modules-widgets/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22). 19 | 20 | ## Get Help 21 | Please, use [github issues](https://github.com/NativeScript/tns-core-modules-widgets/issues) strictly for [reporting bugs](CONTRIBUTING.md#reporting-bugs) or [requesting features](CONTRIBUTING.md#requesting-new-features). For general questions and support, check out the [NativeScript community forum](https://discourse.nativescript.org/) or ask our experts in [NativeScript community Slack channel](http://developer.telerik.com/wp-login.php?action=slack-invitation). 22 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | -------------------------------------------------------------------------------- /android/README.md: -------------------------------------------------------------------------------- 1 | ### Android 2 | 3 | This directory contains an Android Studio project. 4 | 5 | ### How to open? 6 | * In Android Studio choose: File -> Open 7 | * Navigate to `tns-core-modules-widgets/android/` folder 8 | * On the left side of the screen choose the Project tab and select `widgets` 9 | 10 | ### How to build? 11 | * On the right side of the screen choose the Gradle tab 12 | * Navigate to `android/widgets/Tasks/build/` 13 | * Execute the `assembleRelease` task 14 | * Output will be in `./android/widgets/build/outputs/` 15 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | google() 6 | jcenter() 7 | } 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:3.1.3' 10 | 11 | // NOTE: Do not place your application dependencies here; they belong 12 | // in the individual module build.gradle files 13 | } 14 | } 15 | 16 | allprojects { 17 | repositories { 18 | google() 19 | jcenter() 20 | } 21 | } 22 | 23 | task clean(type: Delete) { 24 | delete rootProject.buildDir 25 | } 26 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true 19 | 20 | # Due to a known issue with Android Gradle Plugin versions 3.0.* and 3.1.*, 21 | # the configuration on demand should be disabled. 22 | # https://developer.android.com/studio/known-issues 23 | org.gradle.configureondemand=false -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NativeScript/tns-core-modules-widgets/6e1fbfa70801c808f2557468bf2ce656c96d3ecc/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Sep 02 07:50:15 EEST 2016 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.7-all.zip 7 | -------------------------------------------------------------------------------- /android/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':widgets' 2 | -------------------------------------------------------------------------------- /android/widgets/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /android/widgets/build.gradle: -------------------------------------------------------------------------------- 1 | import groovy.json.JsonSlurper //used to parse package.json 2 | import groovy.json.JsonBuilder 3 | import groovy.json.JsonOutput 4 | 5 | def isWinOs = System.properties['os.name'].toLowerCase().contains('windows') 6 | 7 | apply plugin: 'com.android.library' 8 | 9 | def computeCompileSdkVersion () { 10 | if(project.hasProperty("compileSdk")) { 11 | return compileSdk 12 | } 13 | else { 14 | return 28 15 | } 16 | } 17 | 18 | def computeBuildToolsVersion() { 19 | if(project.hasProperty("buildToolsVersion")) { 20 | return buildToolsVersion 21 | } 22 | else { 23 | return "28.0.2" 24 | } 25 | } 26 | 27 | def computeSupportVersion() { 28 | if(project.hasProperty("supportVersion")) { 29 | return supportVersion 30 | } 31 | else { 32 | return "28.0.0" 33 | } 34 | } 35 | 36 | def computeTargetSdkVersion() { 37 | if(project.hasProperty("targetSdk")) { 38 | return targetSdk 39 | } 40 | else { 41 | return 28 42 | } 43 | } 44 | 45 | android { 46 | compileSdkVersion computeCompileSdkVersion() 47 | buildToolsVersion computeBuildToolsVersion() 48 | 49 | defaultConfig { 50 | minSdkVersion 16 51 | targetSdkVersion computeTargetSdkVersion() 52 | versionCode 1 53 | versionName "1.0" 54 | } 55 | buildTypes { 56 | release { 57 | minifyEnabled false 58 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 59 | } 60 | } 61 | } 62 | 63 | dependencies { 64 | implementation fileTree(include: ['*.jar'], dir: 'libs') 65 | implementation 'com.android.support:support-v4:' + computeSupportVersion() 66 | } 67 | 68 | task cleanBuildDir (type: Delete) { 69 | delete "../build/" 70 | } 71 | 72 | task copyAar << { 73 | copy { 74 | from "build/outputs/aar/widgets-release.aar" 75 | into "../build/" 76 | } 77 | } 78 | 79 | assemble.dependsOn(cleanBuildDir) 80 | copyAar.dependsOn(assemble) 81 | build.dependsOn(copyAar) -------------------------------------------------------------------------------- /android/widgets/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NativeScript/tns-core-modules-widgets/6e1fbfa70801c808f2557468bf2ce656c96d3ecc/android/widgets/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /android/widgets/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Dec 28 10:00:20 PST 2015 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip 7 | -------------------------------------------------------------------------------- /android/widgets/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /android/widgets/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /android/widgets/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /home/plamen5kov/tools/android-sdk-linux/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /android/widgets/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /android/widgets/src/main/java/org/nativescript/Process.java: -------------------------------------------------------------------------------- 1 | package org.nativescript; 2 | 3 | import android.os.SystemClock; 4 | import android.util.Log; 5 | 6 | import java.io.BufferedReader; 7 | import java.io.FileReader; 8 | 9 | /** 10 | * Created by cankov on 15/05/2017. 11 | */ 12 | public class Process { 13 | static long startTime = -1; 14 | 15 | public static long getStartTime() { 16 | if (startTime != -1) { 17 | return startTime; 18 | } 19 | 20 | try { 21 | int pid = android.os.Process.myPid(); 22 | final String path = "/proc/" + pid + "/stat"; 23 | final BufferedReader reader = new BufferedReader(new FileReader(path)); 24 | final String stat; 25 | try { 26 | stat = reader.readLine(); 27 | } finally { 28 | reader.close(); 29 | } 30 | final String field2End = ") "; 31 | final String fieldSep = " "; 32 | final int fieldStartTime = 20; 33 | final int msInSec = 1000; 34 | 35 | final String[] fields = stat.substring(stat.lastIndexOf(field2End)).split(fieldSep); 36 | final long t = Long.parseLong(fields[fieldStartTime]); 37 | int tckName; 38 | try { 39 | tckName = Class.forName("android.system.OsConstants").getField("_SC_CLK_TCK").getInt(null); 40 | } catch (ClassNotFoundException e) { 41 | tckName = Class.forName("libcore.io.OsConstants").getField("_SC_CLK_TCK").getInt(null); 42 | } 43 | final Object os = Class.forName("libcore.io.Libcore").getField("os").get(null); 44 | final long tck = (Long) os.getClass().getMethod("sysconf", Integer.TYPE).invoke(os, tckName); 45 | startTime = t * msInSec / tck; 46 | } catch (Exception e) { 47 | Log.v("JS", "Failed to get process start time. Using the current time as start time. Error: " + e); 48 | startTime = SystemClock.elapsedRealtime(); 49 | } 50 | 51 | return startTime; 52 | } 53 | 54 | public static long getUpTime() { 55 | long startTime = getStartTime(); 56 | long currentTime = SystemClock.elapsedRealtime(); 57 | return currentTime - startTime; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /android/widgets/src/main/java/org/nativescript/widgets/AbsoluteLayout.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package org.nativescript.widgets; 5 | 6 | import android.content.Context; 7 | import android.view.View; 8 | 9 | /** 10 | * @author hhristov 11 | * 12 | */ 13 | public class AbsoluteLayout extends LayoutBase { 14 | 15 | public AbsoluteLayout(Context context) { 16 | super(context); 17 | } 18 | 19 | @Override 20 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 21 | CommonLayoutParams.adjustChildrenLayoutParams(this, widthMeasureSpec, heightMeasureSpec); 22 | 23 | int measureWidth = 0; 24 | int measureHeight = 0; 25 | int childMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); 26 | 27 | for (int i = 0, count = this.getChildCount(); i < count; i++) { 28 | View child = this.getChildAt(i); 29 | if (child.getVisibility() == View.GONE) { 30 | continue; 31 | } 32 | 33 | CommonLayoutParams.measureChild(child, childMeasureSpec, childMeasureSpec); 34 | final int childMeasuredWidth = CommonLayoutParams.getDesiredWidth(child); 35 | final int childMeasuredHeight = CommonLayoutParams.getDesiredHeight(child); 36 | 37 | CommonLayoutParams childLayoutParams = (CommonLayoutParams)child.getLayoutParams(); 38 | measureWidth = Math.max(measureWidth, childLayoutParams.left + childMeasuredWidth); 39 | measureHeight = Math.max(measureHeight, childLayoutParams.top + childMeasuredHeight); 40 | } 41 | 42 | // Add in our padding 43 | measureWidth += this.getPaddingLeft() + this.getPaddingRight(); 44 | measureHeight += this.getPaddingTop() + this.getPaddingBottom(); 45 | 46 | // Check against our minimum height 47 | measureWidth = Math.max(measureWidth, this.getSuggestedMinimumWidth()); 48 | measureHeight = Math.max(measureHeight, this.getSuggestedMinimumHeight()); 49 | 50 | int widthSizeAndState = resolveSizeAndState(measureWidth, widthMeasureSpec, 0); 51 | int heightSizeAndState = resolveSizeAndState(measureHeight, heightMeasureSpec, 0); 52 | 53 | this.setMeasuredDimension(widthSizeAndState, heightSizeAndState); 54 | } 55 | 56 | @Override 57 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 58 | int leftPadding = this.getPaddingLeft(); 59 | int topPadding = this.getPaddingTop(); 60 | 61 | for (int i = 0, count = this.getChildCount(); i < count; i++) { 62 | View child = this.getChildAt(i); 63 | if (child.getVisibility() == View.GONE) { 64 | continue; 65 | } 66 | 67 | CommonLayoutParams childLayoutParams = (CommonLayoutParams)child.getLayoutParams(); 68 | int childWidth = child.getMeasuredWidth(); 69 | int childHeight = child.getMeasuredHeight(); 70 | 71 | int childLeft = leftPadding + childLayoutParams.left; 72 | int childTop = topPadding + childLayoutParams.top; 73 | int childRight = childLeft + childWidth + childLayoutParams.leftMargin + childLayoutParams.rightMargin; 74 | int childBottom = childTop + childHeight + childLayoutParams.topMargin + childLayoutParams.bottomMargin; 75 | 76 | CommonLayoutParams.layoutChild(child, childLeft, childTop, childRight, childBottom); 77 | } 78 | 79 | CommonLayoutParams.restoreOriginalParams(this); 80 | } 81 | } -------------------------------------------------------------------------------- /android/widgets/src/main/java/org/nativescript/widgets/AnimatorHelper.java: -------------------------------------------------------------------------------- 1 | package org.nativescript.widgets; 2 | 3 | import android.animation.Animator; 4 | import android.animation.AnimatorSet; 5 | import android.animation.ObjectAnimator; 6 | 7 | class AnimatorHelper { 8 | static final int version = android.os.Build.VERSION.SDK_INT; 9 | static final int exitFakeResourceId = -20; 10 | 11 | static Animator createDummyAnimator(long duration) { 12 | float[] alphaValues = new float[2]; 13 | alphaValues[0] = 1; 14 | alphaValues[1] = 1; 15 | 16 | Animator animator = ObjectAnimator.ofFloat(null, "alpha", alphaValues); 17 | if (duration > 0) { 18 | animator.setDuration(duration); 19 | } 20 | 21 | return animator; 22 | } 23 | 24 | static long getTotalDuration(Animator animator) { 25 | if (animator instanceof AnimatorSet) { 26 | return getAnimatorSetTotalDuration((AnimatorSet)animator); 27 | } else { 28 | return getAnimatorTotalDuration(animator); 29 | } 30 | } 31 | 32 | static long getAnimatorTotalDuration(Animator animator) { 33 | long totalDuration; 34 | if (version >= 24) { 35 | totalDuration = animator.getTotalDuration(); 36 | } else { 37 | long duration = animator.getDuration(); 38 | if (duration == Animator.DURATION_INFINITE) { 39 | totalDuration = Animator.DURATION_INFINITE; 40 | } else { 41 | totalDuration = animator.getStartDelay() + duration; 42 | } 43 | } 44 | 45 | return totalDuration; 46 | } 47 | 48 | static long getAnimatorSetTotalDuration(AnimatorSet animatorSet) { 49 | long totalDuration = 0; 50 | if (version >= 24) { 51 | totalDuration = animatorSet.getTotalDuration(); 52 | } else { 53 | // TODO: this is only meaningful for "playTogether" animators 54 | for (Animator animator: animatorSet.getChildAnimations()) { 55 | totalDuration = Math.max(totalDuration, getTotalDuration(animator)); 56 | } 57 | } 58 | 59 | return totalDuration; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /android/widgets/src/main/java/org/nativescript/widgets/CSSValue.java: -------------------------------------------------------------------------------- 1 | package org.nativescript.widgets; 2 | 3 | /** 4 | * Created by hristov on 6/16/2016. 5 | */ 6 | public class CSSValue { 7 | private String type; 8 | private String str; 9 | private String unit; 10 | private float value; 11 | 12 | public String getType() { 13 | return type; 14 | } 15 | 16 | public String getString() { 17 | return str; 18 | } 19 | 20 | public String getUnit() { 21 | return unit; 22 | } 23 | 24 | public float getValue() { 25 | return value; 26 | } 27 | 28 | public CSSValue(String type, String str, String unit, float value) { 29 | this.type = type; 30 | this.str = str; 31 | this.unit = unit; 32 | this.value = value; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /android/widgets/src/main/java/org/nativescript/widgets/ContentLayout.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package org.nativescript.widgets; 5 | 6 | import android.content.Context; 7 | import android.view.View; 8 | 9 | /** 10 | * @author hhristov 11 | * 12 | */ 13 | public class ContentLayout extends LayoutBase { 14 | 15 | public ContentLayout(Context context) { 16 | super(context); 17 | } 18 | 19 | @Override 20 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 21 | CommonLayoutParams.adjustChildrenLayoutParams(this, widthMeasureSpec, heightMeasureSpec); 22 | 23 | int measureWidth = 0; 24 | int measureHeight = 0; 25 | 26 | for (int i = 0, count = this.getChildCount(); i < count; i++) { 27 | View child = this.getChildAt(i); 28 | if (child.getVisibility() == View.GONE) { 29 | continue; 30 | } 31 | 32 | CommonLayoutParams.measureChild(child, widthMeasureSpec, heightMeasureSpec); 33 | final int childMeasuredWidth = CommonLayoutParams.getDesiredWidth(child); 34 | final int childMeasuredHeight = CommonLayoutParams.getDesiredHeight(child); 35 | 36 | measureWidth = Math.max(measureWidth, childMeasuredWidth); 37 | measureHeight = Math.max(measureHeight, childMeasuredHeight); 38 | } 39 | 40 | // Add in our padding 41 | measureWidth += this.getPaddingLeft() + this.getPaddingRight(); 42 | measureHeight += this.getPaddingTop() + this.getPaddingBottom(); 43 | 44 | // Check against our minimum sizes 45 | measureWidth = Math.max(measureWidth, this.getSuggestedMinimumWidth()); 46 | measureHeight = Math.max(measureHeight, this.getSuggestedMinimumHeight()); 47 | 48 | int widthSizeAndState = resolveSizeAndState(measureWidth, widthMeasureSpec, 0); 49 | int heightSizeAndState = resolveSizeAndState(measureHeight, heightMeasureSpec, 0); 50 | 51 | this.setMeasuredDimension(widthSizeAndState, heightSizeAndState); 52 | } 53 | 54 | @Override 55 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 56 | int paddingLeft = this.getPaddingLeft(); 57 | int paddingRight = this.getPaddingRight(); 58 | int paddingTop = this.getPaddingTop(); 59 | int paddingBottom = this.getPaddingBottom(); 60 | 61 | int childLeft = paddingLeft; 62 | int childTop = paddingTop; 63 | 64 | int childRight = right - left - (paddingLeft + paddingRight); 65 | int childBottom = bottom - top - (paddingRight + paddingBottom); 66 | 67 | for (int i = 0, count = this.getChildCount(); i < count; i++) { 68 | View child = this.getChildAt(i); 69 | if (child.getVisibility() == View.GONE) { 70 | continue; 71 | } 72 | 73 | CommonLayoutParams.layoutChild(child, childLeft, childTop, childRight, childBottom); 74 | } 75 | 76 | CommonLayoutParams.restoreOriginalParams(this); 77 | } 78 | } -------------------------------------------------------------------------------- /android/widgets/src/main/java/org/nativescript/widgets/CustomTypefaceSpan.java: -------------------------------------------------------------------------------- 1 | package org.nativescript.widgets; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.graphics.Paint; 5 | import android.graphics.Typeface; 6 | import android.text.TextPaint; 7 | import android.text.style.TypefaceSpan; 8 | 9 | /** 10 | * Created by hhristov on 2/27/17. 11 | */ 12 | 13 | @SuppressLint("ParcelCreator") 14 | public class CustomTypefaceSpan extends TypefaceSpan { 15 | private Typeface typeface; 16 | 17 | public CustomTypefaceSpan(String family, Typeface typeface) { 18 | super(family); 19 | this.typeface = typeface; 20 | } 21 | 22 | public void updateDrawState(TextPaint ds) { 23 | this.applyCustomTypeFace(ds); 24 | } 25 | 26 | public void updateMeasureState(TextPaint paint) { 27 | this.applyCustomTypeFace(paint); 28 | } 29 | 30 | private void applyCustomTypeFace(TextPaint paint) { 31 | final Typeface old = paint.getTypeface(); 32 | final int oldStyle = (old == null) ? 0 : old.getStyle(); 33 | 34 | Typeface typeface = this.typeface; 35 | int fake = oldStyle & ~typeface.getStyle(); 36 | if ((fake & android.graphics.Typeface.BOLD) != 0) { 37 | paint.setFakeBoldText(true); 38 | } 39 | 40 | if ((fake & android.graphics.Typeface.ITALIC) != 0) { 41 | paint.setTextSkewX(-0.25f); 42 | } 43 | 44 | paint.setTypeface(typeface); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /android/widgets/src/main/java/org/nativescript/widgets/Dock.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package org.nativescript.widgets; 5 | 6 | /** 7 | * @author hhristov 8 | * 9 | */ 10 | public enum Dock { 11 | left, 12 | top, 13 | right, 14 | bottom 15 | } -------------------------------------------------------------------------------- /android/widgets/src/main/java/org/nativescript/widgets/DockLayout.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package org.nativescript.widgets; 5 | 6 | import android.content.Context; 7 | import android.view.View; 8 | 9 | /** 10 | * @author hhristov 11 | * 12 | */ 13 | public class DockLayout extends LayoutBase { 14 | 15 | private boolean _stretchLastChild = true; 16 | 17 | public DockLayout(Context context) { 18 | super(context); 19 | } 20 | 21 | public boolean getStretchLastChild() { 22 | return this._stretchLastChild; 23 | } 24 | public void setStretchLastChild(boolean value) { 25 | this._stretchLastChild = value; 26 | this.requestLayout(); 27 | } 28 | 29 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 30 | CommonLayoutParams.adjustChildrenLayoutParams(this, widthMeasureSpec, heightMeasureSpec); 31 | 32 | int measureWidth = 0; 33 | int measureHeight = 0; 34 | 35 | int width = MeasureSpec.getSize(widthMeasureSpec); 36 | int widthMode = MeasureSpec.getMode(widthMeasureSpec); 37 | 38 | int height = MeasureSpec.getSize(heightMeasureSpec); 39 | int heightMode = MeasureSpec.getMode(heightMeasureSpec); 40 | 41 | int verticalPadding = this.getPaddingTop() + this.getPaddingBottom(); 42 | int horizontalPadding = this.getPaddingLeft() + this.getPaddingRight(); 43 | 44 | int remainingWidth = widthMode == MeasureSpec.UNSPECIFIED ? 0 : width - horizontalPadding; 45 | int remainingHeight = heightMode == MeasureSpec.UNSPECIFIED ? 0 : height - verticalPadding; 46 | 47 | int tempHeight = 0; 48 | int tempWidth = 0; 49 | int childWidthMeasureSpec = 0; 50 | int childHeightMeasureSpec = 0; 51 | int count = this.getChildCount(); 52 | for (int i = 0; i < count; i++) { 53 | View child = this.getChildAt(i); 54 | if (child.getVisibility() == View.GONE) { 55 | continue; 56 | } 57 | 58 | if (this._stretchLastChild && (i == (count - 1))) { 59 | childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(remainingWidth, widthMode); 60 | childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(remainingHeight, heightMode); 61 | } 62 | else { 63 | // Measure children with AT_MOST even if our mode is EXACT 64 | childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(remainingWidth, widthMode == MeasureSpec.EXACTLY ? MeasureSpec.AT_MOST : widthMode); 65 | childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(remainingHeight, heightMode == MeasureSpec.EXACTLY ? MeasureSpec.AT_MOST : heightMode); 66 | } 67 | 68 | CommonLayoutParams.measureChild(child, childWidthMeasureSpec, childHeightMeasureSpec); 69 | final int childMeasuredWidth = CommonLayoutParams.getDesiredWidth(child); 70 | final int childMeasuredHeight = CommonLayoutParams.getDesiredHeight(child); 71 | 72 | CommonLayoutParams childLayoutParams = (CommonLayoutParams)child.getLayoutParams(); 73 | Dock dock = childLayoutParams.dock; 74 | switch (dock) { 75 | case top: 76 | case bottom: 77 | remainingHeight = Math.max(0, remainingHeight - childMeasuredHeight); 78 | tempHeight += childMeasuredHeight; 79 | measureWidth = Math.max(measureWidth, tempWidth + childMeasuredWidth); 80 | measureHeight = Math.max(measureHeight, tempHeight); 81 | break; 82 | 83 | case left: 84 | case right: 85 | default: 86 | remainingWidth = Math.max(0, remainingWidth - childMeasuredWidth); 87 | tempWidth += childMeasuredWidth; 88 | measureWidth = Math.max(measureWidth, tempWidth); 89 | measureHeight = Math.max(measureHeight, tempHeight + childMeasuredHeight); 90 | break; 91 | } 92 | } 93 | 94 | // Add in our padding 95 | measureWidth += horizontalPadding; 96 | measureHeight += verticalPadding; 97 | 98 | // Check against our minimum sizes 99 | measureWidth = Math.max(measureWidth, this.getSuggestedMinimumWidth()); 100 | measureHeight = Math.max(measureHeight, this.getSuggestedMinimumHeight()); 101 | 102 | int widthSizeAndState = resolveSizeAndState(measureWidth, widthMeasureSpec, 0); 103 | int heightSizeAndState = resolveSizeAndState(measureHeight, heightMeasureSpec, 0); 104 | 105 | this.setMeasuredDimension(widthSizeAndState, heightSizeAndState); 106 | } 107 | 108 | @Override 109 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 110 | int childLeft = this.getPaddingLeft(); 111 | int childTop = this.getPaddingTop(); 112 | 113 | int x = childLeft; 114 | int y = childTop; 115 | 116 | int remainingWidth = Math.max(0, right - left - (this.getPaddingLeft() + this.getPaddingRight())); 117 | int remainingHeight = Math.max(0, bottom - top - (this.getPaddingTop() + this.getPaddingBottom())); 118 | 119 | int count = this.getChildCount(); 120 | View childToStretch = null; 121 | if (count > 0 && this._stretchLastChild) { 122 | count--; 123 | childToStretch = this.getChildAt(count); 124 | } 125 | 126 | for (int i = 0; i < count; i++) { 127 | View child = this.getChildAt(i); 128 | if (child.getVisibility() == View.GONE) { 129 | continue; 130 | } 131 | 132 | CommonLayoutParams childLayoutParams = (CommonLayoutParams)child.getLayoutParams(); 133 | int childWidth = CommonLayoutParams.getDesiredWidth(child); 134 | int childHeight = CommonLayoutParams.getDesiredHeight(child); 135 | 136 | switch (childLayoutParams.dock) { 137 | case top: 138 | childLeft = x; 139 | childTop = y; 140 | childWidth = remainingWidth; 141 | y += childHeight; 142 | remainingHeight = Math.max(0, remainingHeight - childHeight); 143 | break; 144 | 145 | case bottom: 146 | childLeft = x; 147 | childTop = y + remainingHeight - childHeight; 148 | childWidth = remainingWidth; 149 | remainingHeight = Math.max(0, remainingHeight - childHeight); 150 | break; 151 | 152 | case right: 153 | childLeft = x + remainingWidth - childWidth; 154 | childTop = y; 155 | childHeight = remainingHeight; 156 | remainingWidth = Math.max(0, remainingWidth - childWidth); 157 | break; 158 | 159 | case left: 160 | default: 161 | childLeft = x; 162 | childTop = y; 163 | childHeight = remainingHeight; 164 | x += childWidth; 165 | remainingWidth = Math.max(0, remainingWidth - childWidth); 166 | break; 167 | } 168 | 169 | CommonLayoutParams.layoutChild(child, childLeft, childTop, childLeft + childWidth, childTop + childHeight); 170 | } 171 | 172 | if (childToStretch != null) { 173 | CommonLayoutParams.layoutChild(childToStretch, x, y, x + remainingWidth, y + remainingHeight); 174 | } 175 | 176 | CommonLayoutParams.restoreOriginalParams(this); 177 | } 178 | } -------------------------------------------------------------------------------- /android/widgets/src/main/java/org/nativescript/widgets/FlexLine.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Google Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.nativescript.widgets; 18 | 19 | import java.util.ArrayList; 20 | import java.util.List; 21 | 22 | /** 23 | * Holds properties related to a single flex line. This class is not expected to be changed outside 24 | * of the {@link FlexboxLayout}, thus only exposing the getter methods that may be useful for 25 | * other classes using the {@link FlexboxLayout}. 26 | */ 27 | public class FlexLine { 28 | 29 | FlexLine() { 30 | } 31 | 32 | /** @see {@link #getLeft()} */ 33 | int mLeft = Integer.MAX_VALUE; 34 | 35 | /** @see {@link #getTop()} */ 36 | int mTop = Integer.MAX_VALUE; 37 | 38 | /** @see {@link #getRight()} */ 39 | int mRight = Integer.MIN_VALUE; 40 | 41 | /** @see {@link #getBottom()} */ 42 | int mBottom = Integer.MIN_VALUE; 43 | 44 | /** @see {@link #getMainSize()} */ 45 | int mMainSize; 46 | 47 | /** 48 | * The sum of the lengths of dividers along the main axis. This value should be lower or 49 | * than than the value of {@link #mMainSize}. 50 | */ 51 | int mDividerLengthInMainSize; 52 | 53 | /** @see {@link #getCrossSize()} */ 54 | int mCrossSize; 55 | 56 | /** @see {@link #getItemCount()} */ 57 | int mItemCount; 58 | 59 | /** @see {@link #getTotalFlexGrow()} */ 60 | float mTotalFlexGrow; 61 | 62 | /** @see {@link #getTotalFlexShrink()} */ 63 | float mTotalFlexShrink; 64 | 65 | /** 66 | * The largest value of the individual child's baseline (obtained by View#getBaseline() 67 | * if the {@link FlexboxLayout#mAlignItems} value is not {@link FlexboxLayout#ALIGN_ITEMS_BASELINE} 68 | * or the flex direction is vertical, this value is not used. 69 | * If the alignment direction is from the bottom to top, 70 | * (e.g. flexWrap == FLEX_WRAP_WRAP_REVERSE and flexDirection == FLEX_DIRECTION_ROW) 71 | * store this value from the distance from the bottom of the view minus baseline. 72 | * (Calculated as view.getMeasuredHeight() - view.getBaseline - LayoutParams.bottomMargin) 73 | */ 74 | int mMaxBaseline; 75 | 76 | /** 77 | * Store the indices of the children views whose alignSelf property is stretch. 78 | * The stored indices are the absolute indices including all children in the Flexbox, 79 | * not the relative indices in this flex line. 80 | */ 81 | List mIndicesAlignSelfStretch = new ArrayList<>(); 82 | 83 | /** 84 | * @return the distance in pixels from the top edge of this view's parent 85 | * to the top edge of this FlexLine. 86 | */ 87 | public int getLeft() { 88 | return mLeft; 89 | } 90 | 91 | /** 92 | * @return the distance in pixels from the top edge of this view's parent 93 | * to the top edge of this FlexLine. 94 | */ 95 | public int getTop() { 96 | return mTop; 97 | } 98 | 99 | /** 100 | * @return the distance in pixels from the right edge of this view's parent 101 | * to the right edge of this FlexLine. 102 | */ 103 | public int getRight() { 104 | return mRight; 105 | } 106 | 107 | /** 108 | * @return the distance in pixels from the bottom edge of this view's parent 109 | * to the bottom edge of this FlexLine. 110 | */ 111 | public int getBottom() { 112 | return mBottom; 113 | } 114 | 115 | /** 116 | * @return the size of the flex line in pixels along the main axis of the flex container. 117 | */ 118 | public int getMainSize() { 119 | return mMainSize; 120 | } 121 | 122 | /** 123 | * @return the size of the flex line in pixels along the cross axis of the flex container. 124 | */ 125 | public int getCrossSize() { 126 | return mCrossSize; 127 | } 128 | 129 | /** 130 | * @return the count of the views contained in this flex line. 131 | */ 132 | public int getItemCount() { 133 | return mItemCount; 134 | } 135 | 136 | /** 137 | * @return the sum of the flexGrow properties of the children included in this flex line 138 | */ 139 | public float getTotalFlexGrow() { 140 | return mTotalFlexGrow; 141 | } 142 | 143 | /** 144 | * @return the sum of the flexShrink properties of the children included in this flex line 145 | */ 146 | public float getTotalFlexShrink() { 147 | return mTotalFlexShrink; 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /android/widgets/src/main/java/org/nativescript/widgets/FragmentBase.java: -------------------------------------------------------------------------------- 1 | package org.nativescript.widgets; 2 | 3 | import android.animation.Animator; 4 | import android.support.v4.app.Fragment; 5 | 6 | public abstract class FragmentBase extends Fragment { 7 | 8 | @Override 9 | public Animator onCreateAnimator(int transit, boolean enter, int nextAnim) { 10 | // [nested frames / fragments] apply dummy animator to the nested fragment with 11 | // the same duration as the exit animator of the removing parent fragment to work around 12 | // https://code.google.com/p/android/issues/detail?id=55228 (child fragments disappear 13 | // when parent fragment is removed as all children are first removed from parent) 14 | if (!enter) { 15 | Fragment removingParentFragment = this.getRemovingParentFragment(); 16 | if (removingParentFragment != null) { 17 | Animator parentAnimator = removingParentFragment.onCreateAnimator(transit, enter, AnimatorHelper.exitFakeResourceId); 18 | if (parentAnimator != null) { 19 | long duration = AnimatorHelper.getTotalDuration(parentAnimator); 20 | return AnimatorHelper.createDummyAnimator(duration); 21 | } 22 | } 23 | } 24 | 25 | return super.onCreateAnimator(transit, enter, nextAnim); 26 | } 27 | 28 | public Fragment getRemovingParentFragment() { 29 | Fragment parentFragment = this.getParentFragment(); 30 | while (parentFragment != null && !parentFragment.isRemoving()) { 31 | parentFragment = parentFragment.getParentFragment(); 32 | } 33 | 34 | return parentFragment; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /android/widgets/src/main/java/org/nativescript/widgets/GridUnitType.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package org.nativescript.widgets; 5 | 6 | /** 7 | * @author hhristov 8 | * 9 | */ 10 | public enum GridUnitType { 11 | auto, 12 | pixel, 13 | star 14 | } -------------------------------------------------------------------------------- /android/widgets/src/main/java/org/nativescript/widgets/HorizontalScrollView.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package org.nativescript.widgets; 5 | 6 | import android.content.Context; 7 | import android.graphics.Rect; 8 | import android.os.Parcel; 9 | import android.os.Parcelable; 10 | import android.util.AttributeSet; 11 | import android.view.MotionEvent; 12 | import android.view.View; 13 | import android.view.ViewGroup; 14 | import android.view.ViewParent; 15 | import android.widget.FrameLayout; 16 | 17 | /** 18 | * @author hhristov 19 | * 20 | */ 21 | public class HorizontalScrollView extends android.widget.HorizontalScrollView { 22 | 23 | private final Rect mTempRect = new Rect(); 24 | 25 | private int contentMeasuredWidth = 0; 26 | private int contentMeasuredHeight = 0; 27 | private int scrollableLength = 0; 28 | private SavedState mSavedState; 29 | private boolean isFirstLayout = true; 30 | private boolean scrollEnabled = true; 31 | 32 | /** 33 | * True when the layout has changed but the traversal has not come through yet. 34 | * Ideally the view hierarchy would keep track of this for us. 35 | */ 36 | private boolean mIsLayoutDirty = true; 37 | 38 | /** 39 | * The child to give focus to in the event that a child has requested focus while the 40 | * layout is dirty. This prevents the scroll from being wrong if the child has not been 41 | * laid out before requesting focus. 42 | */ 43 | private View mChildToScrollTo = null; 44 | 45 | public HorizontalScrollView(Context context) { 46 | super(context); 47 | } 48 | 49 | public int getScrollableLength() { 50 | return this.scrollableLength; 51 | } 52 | 53 | public boolean getScrollEnabled() { 54 | return this.scrollEnabled; 55 | } 56 | 57 | public void setScrollEnabled(boolean value) { 58 | this.scrollEnabled = value; 59 | } 60 | 61 | @Override 62 | public boolean onInterceptTouchEvent(MotionEvent ev) { 63 | // Do nothing with intercepted touch events if we are not scrollable 64 | if (!this.scrollEnabled) { 65 | return false; 66 | } 67 | 68 | return super.onInterceptTouchEvent(ev); 69 | } 70 | 71 | @Override 72 | public boolean onTouchEvent(MotionEvent ev) { 73 | if (!this.scrollEnabled && ev.getAction() == MotionEvent.ACTION_DOWN) { 74 | return false; 75 | } 76 | 77 | return super.onTouchEvent(ev); 78 | } 79 | 80 | @Override 81 | public void requestLayout() { 82 | this.mIsLayoutDirty = true; 83 | super.requestLayout(); 84 | } 85 | 86 | @Override 87 | protected CommonLayoutParams generateDefaultLayoutParams() { 88 | return new CommonLayoutParams(); 89 | } 90 | 91 | /** 92 | * {@inheritDoc} 93 | */ 94 | @Override 95 | public CommonLayoutParams generateLayoutParams(AttributeSet attrs) { 96 | return new CommonLayoutParams(); 97 | } 98 | 99 | /** 100 | * {@inheritDoc} 101 | */ 102 | @Override 103 | protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { 104 | return p instanceof CommonLayoutParams; 105 | } 106 | 107 | @Override 108 | protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams from) { 109 | if (from instanceof CommonLayoutParams) 110 | return new CommonLayoutParams((CommonLayoutParams)from); 111 | 112 | if (from instanceof FrameLayout.LayoutParams) 113 | return new CommonLayoutParams((FrameLayout.LayoutParams)from); 114 | 115 | if (from instanceof ViewGroup.MarginLayoutParams) 116 | return new CommonLayoutParams((ViewGroup.MarginLayoutParams)from); 117 | 118 | return new CommonLayoutParams(from); 119 | } 120 | 121 | @Override 122 | public void requestChildFocus(View child, View focused) { 123 | if (!mIsLayoutDirty) { 124 | this.scrollToChild(focused); 125 | } else { 126 | // The child may not be laid out yet, we can't compute the scroll yet 127 | mChildToScrollTo = focused; 128 | } 129 | super.requestChildFocus(child, focused); 130 | } 131 | 132 | @Override 133 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 134 | CommonLayoutParams.adjustChildrenLayoutParams(this, widthMeasureSpec, heightMeasureSpec); 135 | 136 | // Don't call measure because it will measure content twice. 137 | // ScrollView is expected to have single child so we measure only the first child. 138 | View child = this.getChildCount() > 0 ? this.getChildAt(0) : null; 139 | if (child == null) { 140 | this.scrollableLength = 0; 141 | this.contentMeasuredWidth = 0; 142 | this.contentMeasuredHeight = 0; 143 | } 144 | else { 145 | CommonLayoutParams.measureChild(child, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), heightMeasureSpec); 146 | this.contentMeasuredWidth = CommonLayoutParams.getDesiredWidth(child); 147 | this.contentMeasuredHeight = CommonLayoutParams.getDesiredHeight(child); 148 | 149 | // Android ScrollView does not account to child margins so we set them as paddings. Otherwise you can never scroll to bottom. 150 | CommonLayoutParams lp = (CommonLayoutParams)child.getLayoutParams(); 151 | this.setPadding(lp.leftMargin, lp.topMargin, lp.rightMargin, lp.bottomMargin); 152 | } 153 | 154 | // Don't add in our paddings because they are already added as child margins. (we will include them twice if we add them). 155 | // Check the previous line - this.setPadding(lp.leftMargin, lp.topMargin, lp.rightMargin, lp.bottomMargin); 156 | //this.contentMeasuredWidth += this.getPaddingLeft() + this.getPaddingRight(); 157 | //this.contentMeasuredHeight += this.getPaddingTop() + this.getPaddingBottom(); 158 | 159 | // Check against our minimum height 160 | this.contentMeasuredWidth = Math.max(this.contentMeasuredWidth, this.getSuggestedMinimumWidth()); 161 | this.contentMeasuredHeight = Math.max(this.contentMeasuredHeight, this.getSuggestedMinimumHeight()); 162 | 163 | int widthSizeAndState = resolveSizeAndState(this.contentMeasuredWidth, widthMeasureSpec, 0); 164 | int heightSizeAndState = resolveSizeAndState(this.contentMeasuredHeight, heightMeasureSpec, 0); 165 | 166 | this.setMeasuredDimension(widthSizeAndState, heightSizeAndState); 167 | } 168 | 169 | @Override 170 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 171 | int childWidth = 0; 172 | if (this.getChildCount() > 0) { 173 | View child = this.getChildAt(0); 174 | childWidth = child.getMeasuredWidth(); 175 | 176 | int width = right - left; 177 | int height = bottom - top; 178 | 179 | this.scrollableLength = this.contentMeasuredWidth - width; 180 | CommonLayoutParams.layoutChild(child, 0, 0, Math.max(this.contentMeasuredWidth, width), height); 181 | this.scrollableLength = Math.max(0, this.scrollableLength); 182 | } 183 | 184 | this.mIsLayoutDirty = false; 185 | // Give a child focus if it needs it 186 | if (this.mChildToScrollTo != null && isViewDescendantOf(this.mChildToScrollTo, this)) { 187 | this.scrollToChild(this.mChildToScrollTo); 188 | } 189 | 190 | this.mChildToScrollTo = null; 191 | 192 | int scrollX = this.getScrollX(); 193 | int scrollY = this.getScrollY(); 194 | 195 | if (this.isFirstLayout) { 196 | this.isFirstLayout = false; 197 | 198 | final int scrollRange = Math.max(0, childWidth - (right - left - this.getPaddingLeft() - this.getPaddingRight())); 199 | if (this.mSavedState != null) { 200 | scrollX = (this.isLayoutRtl() == mSavedState.isLayoutRtl) ? mSavedState.scrollPosition : (scrollRange - this.mSavedState.scrollPosition); 201 | mSavedState = null; 202 | } else { 203 | if (this.isLayoutRtl()) { 204 | scrollX = scrollRange - scrollX; 205 | } // mScrollX default value is "0" for LTR 206 | } 207 | // Don't forget to clamp 208 | if (scrollX > scrollRange) { 209 | scrollX = scrollRange; 210 | } else if (scrollX < 0) { 211 | scrollX = 0; 212 | } 213 | } 214 | 215 | // Calling this with the present values causes it to re-claim them 216 | this.scrollTo(scrollX, scrollY); 217 | CommonLayoutParams.restoreOriginalParams(this); 218 | } 219 | 220 | @Override 221 | protected void onAttachedToWindow() { 222 | super.onAttachedToWindow(); 223 | this.isFirstLayout = true; 224 | } 225 | 226 | @Override 227 | protected void onDetachedFromWindow() { 228 | super.onDetachedFromWindow(); 229 | this.isFirstLayout = true; 230 | } 231 | 232 | @Override 233 | protected void onRestoreInstanceState(Parcelable state) { 234 | SavedState ss = (SavedState) state; 235 | super.onRestoreInstanceState(ss.getSuperState()); 236 | this.mSavedState = ss; 237 | this.requestLayout(); 238 | } 239 | 240 | @Override 241 | protected Parcelable onSaveInstanceState() { 242 | Parcelable superState = super.onSaveInstanceState(); 243 | SavedState ss = new SavedState(superState); 244 | ss.scrollPosition = this.getScrollX(); 245 | ss.isLayoutRtl = this.isLayoutRtl(); 246 | return ss; 247 | } 248 | 249 | private void scrollToChild(View child) { 250 | child.getDrawingRect(mTempRect); 251 | 252 | /* Offset from child's local coordinates to ScrollView coordinates */ 253 | offsetDescendantRectToMyCoords(child, mTempRect); 254 | 255 | int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect); 256 | if (scrollDelta != 0) { 257 | this.scrollBy(scrollDelta, 0); 258 | } 259 | } 260 | 261 | private boolean isLayoutRtl() { 262 | return (this.getLayoutDirection() == LAYOUT_DIRECTION_RTL); 263 | } 264 | 265 | /** 266 | * Return true if child is a descendant of parent, (or equal to the parent). 267 | */ 268 | static boolean isViewDescendantOf(View child, View parent) { 269 | if (child == parent) { 270 | return true; 271 | } 272 | 273 | final ViewParent theParent = child.getParent(); 274 | return (theParent instanceof ViewGroup) && isViewDescendantOf((View) theParent, parent); 275 | } 276 | 277 | static class SavedState extends BaseSavedState { 278 | public int scrollPosition; 279 | public boolean isLayoutRtl; 280 | 281 | SavedState(Parcelable superState) { 282 | super(superState); 283 | } 284 | 285 | public SavedState(Parcel source) { 286 | super(source); 287 | scrollPosition = source.readInt(); 288 | isLayoutRtl = (source.readInt() == 0) ? true : false; 289 | } 290 | 291 | @Override 292 | public void writeToParcel(Parcel dest, int flags) { 293 | super.writeToParcel(dest, flags); 294 | dest.writeInt(scrollPosition); 295 | dest.writeInt(isLayoutRtl ? 1 : 0); 296 | } 297 | 298 | @Override 299 | public String toString() { 300 | return "HorizontalScrollView.SavedState{" 301 | + Integer.toHexString(System.identityHashCode(this)) 302 | + " scrollPosition=" + scrollPosition 303 | + " isLayoutRtl=" + isLayoutRtl + "}"; 304 | } 305 | 306 | public static final Creator CREATOR 307 | = new Creator() { 308 | public SavedState createFromParcel(Parcel in) { 309 | return new SavedState(in); 310 | } 311 | 312 | public SavedState[] newArray(int size) { 313 | return new SavedState[size]; 314 | } 315 | }; 316 | } 317 | } -------------------------------------------------------------------------------- /android/widgets/src/main/java/org/nativescript/widgets/Image/Utils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.nativescript.widgets.image; 18 | 19 | import android.annotation.TargetApi; 20 | import android.os.Build; 21 | import android.os.Build.VERSION_CODES; 22 | import android.os.StrictMode; 23 | 24 | /** 25 | * Class containing some static utility methods. 26 | */ 27 | public class Utils { 28 | private Utils() {}; 29 | 30 | public static boolean hasFroyo() { 31 | // Can use static final constants like FROYO, declared in later versions 32 | // of the OS since they are inlined at compile time. This is guaranteed behavior. 33 | return Build.VERSION.SDK_INT >= VERSION_CODES.FROYO; 34 | } 35 | 36 | public static boolean hasGingerbread() { 37 | return Build.VERSION.SDK_INT >= VERSION_CODES.GINGERBREAD; 38 | } 39 | 40 | public static boolean hasHoneycomb() { 41 | return Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB; 42 | } 43 | 44 | public static boolean hasHoneycombMR1() { 45 | return Build.VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB_MR1; 46 | } 47 | 48 | public static boolean hasJellyBean() { 49 | return Build.VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN; 50 | } 51 | 52 | public static boolean hasKitKat() { 53 | return Build.VERSION.SDK_INT >= VERSION_CODES.KITKAT; 54 | } 55 | 56 | public static boolean hasN() { 57 | return Build.VERSION.SDK_INT >= VERSION_CODES.N; 58 | } 59 | } -------------------------------------------------------------------------------- /android/widgets/src/main/java/org/nativescript/widgets/ImageView.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package org.nativescript.widgets; 5 | 6 | import android.content.Context; 7 | import android.graphics.*; 8 | import android.graphics.drawable.Drawable; 9 | import android.graphics.drawable.shapes.RoundRectShape; 10 | 11 | import org.nativescript.widgets.image.BitmapOwner; 12 | import org.nativescript.widgets.image.Fetcher; 13 | import org.nativescript.widgets.image.Worker; 14 | /** 15 | * @author hhristov 16 | */ 17 | public class ImageView extends android.widget.ImageView implements BitmapOwner { 18 | private static final double EPSILON = 1E-05; 19 | 20 | private Path path = new Path(); 21 | private RectF rect = new RectF(); 22 | 23 | private double scaleW = 1; 24 | private double scaleH = 1; 25 | 26 | private float rotationAngle; 27 | 28 | private Matrix mMatrix; 29 | private Bitmap mBitmap; 30 | private String mUri; 31 | private int mDecodeWidth; 32 | private int mDecodeHeight; 33 | private boolean mKeepAspectRatio; 34 | private boolean mUseCache; 35 | private boolean mAsync; 36 | private Worker.OnImageLoadedListener mListener; 37 | private boolean mAttachedToWindow = false; 38 | 39 | public float getRotationAngle() { 40 | return rotationAngle; 41 | } 42 | 43 | public void setRotationAngle(float rotationAngle) { 44 | this.rotationAngle = rotationAngle; 45 | invalidate(); 46 | } 47 | 48 | public ImageView(Context context) { 49 | super(context); 50 | this.mMatrix = new Matrix(); 51 | this.setScaleType(ScaleType.FIT_CENTER); 52 | } 53 | 54 | @Override 55 | protected void onAttachedToWindow() { 56 | mAttachedToWindow = true; 57 | super.onAttachedToWindow(); 58 | this.loadImage(); 59 | } 60 | 61 | @Override 62 | protected void onDetachedFromWindow() { 63 | mAttachedToWindow = false; 64 | super.onDetachedFromWindow(); 65 | if (mUri != null) { 66 | // Clear the bitmap as we are not in the visual tree. 67 | this.setImageBitmap(null); 68 | } 69 | } 70 | 71 | @Override 72 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 73 | 74 | int width = MeasureSpec.getSize(widthMeasureSpec); 75 | int widthMode = MeasureSpec.getMode(widthMeasureSpec); 76 | 77 | int height = MeasureSpec.getSize(heightMeasureSpec); 78 | int heightMode = MeasureSpec.getMode(heightMeasureSpec); 79 | 80 | Drawable drawable = this.getDrawable(); 81 | int measureWidth; 82 | int measureHeight; 83 | if (drawable != null) { 84 | measureWidth = drawable.getIntrinsicWidth(); 85 | measureHeight = drawable.getIntrinsicHeight(); 86 | } else { 87 | measureWidth = 0; 88 | measureHeight = 0; 89 | } 90 | 91 | boolean finiteWidth = widthMode != MeasureSpec.UNSPECIFIED; 92 | boolean finiteHeight = heightMode != MeasureSpec.UNSPECIFIED; 93 | 94 | if (measureWidth != 0 && measureHeight != 0 && (finiteWidth || finiteHeight)) { 95 | this.computeScaleFactor(width, height, finiteWidth, finiteHeight, measureWidth, measureHeight); 96 | int resultW = (int) Math.round(measureWidth * this.scaleW); 97 | int resultH = (int) Math.round(measureHeight * this.scaleH); 98 | 99 | measureWidth = finiteWidth ? Math.min(resultW, width) : resultW; 100 | measureHeight = finiteHeight ? Math.min(resultH, height) : resultH; 101 | } 102 | 103 | measureWidth += this.getPaddingLeft() + this.getPaddingRight(); 104 | measureHeight += this.getPaddingTop() + this.getPaddingBottom(); 105 | 106 | measureWidth = Math.max(measureWidth, getSuggestedMinimumWidth()); 107 | measureHeight = Math.max(measureHeight, getSuggestedMinimumHeight()); 108 | 109 | if (CommonLayoutParams.debuggable > 0) { 110 | StringBuilder sb = CommonLayoutParams.getStringBuilder(); 111 | sb.append("ImageView onMeasure: "); 112 | sb.append(MeasureSpec.toString(widthMeasureSpec)); 113 | sb.append(", "); 114 | sb.append(MeasureSpec.toString(heightMeasureSpec)); 115 | sb.append(", stretch: "); 116 | sb.append(this.getScaleType()); 117 | sb.append(", measureWidth: "); 118 | sb.append(measureWidth); 119 | sb.append(", measureHeight: "); 120 | sb.append(measureHeight); 121 | 122 | CommonLayoutParams.log(CommonLayoutParams.TAG, sb.toString()); 123 | } 124 | 125 | int widthSizeAndState = resolveSizeAndState(measureWidth, widthMeasureSpec, 0); 126 | int heightSizeAndState = resolveSizeAndState(measureHeight, heightMeasureSpec, 0); 127 | 128 | this.setMeasuredDimension(widthSizeAndState, heightSizeAndState); 129 | } 130 | 131 | private void computeScaleFactor(int measureWidth, int measureHeight, boolean widthIsFinite, boolean heightIsFinite, double nativeWidth, double nativeHeight) { 132 | 133 | this.scaleW = 1; 134 | this.scaleH = 1; 135 | 136 | ScaleType scale = this.getScaleType(); 137 | if ((scale == ScaleType.CENTER_CROP || scale == ScaleType.FIT_CENTER || scale == ScaleType.FIT_XY) && 138 | (widthIsFinite || heightIsFinite)) { 139 | 140 | this.scaleW = (nativeWidth > 0) ? measureWidth / nativeWidth : 0d; 141 | this.scaleH = (nativeHeight > 0) ? measureHeight / nativeHeight : 0d; 142 | 143 | if (!widthIsFinite) { 144 | this.scaleW = scaleH; 145 | } else if (!heightIsFinite) { 146 | this.scaleH = scaleW; 147 | } else { 148 | // No infinite dimensions. 149 | switch (scale) { 150 | case FIT_CENTER: 151 | this.scaleH = this.scaleW < this.scaleH ? this.scaleW : this.scaleH; 152 | this.scaleW = this.scaleH; 153 | break; 154 | case CENTER_CROP: 155 | this.scaleH = this.scaleW > this.scaleH ? this.scaleW : this.scaleH; 156 | this.scaleW = this.scaleH; 157 | break; 158 | default: 159 | break; 160 | } 161 | } 162 | } 163 | } 164 | 165 | public void setUri(String uri, int decodeWidth, int decodeHeight, boolean useCache, boolean async) { 166 | this.setUri(uri, decodeWidth, decodeHeight, false, useCache, async); 167 | } 168 | 169 | public void setUri(String uri, int decodeWidth, int decodeHeight, boolean keepAspectRatio, boolean useCache, boolean async) { 170 | mUri = uri; 171 | mDecodeWidth = decodeWidth; 172 | mDecodeHeight = decodeHeight; 173 | mKeepAspectRatio = keepAspectRatio; 174 | mUseCache = useCache; 175 | mAsync = async; 176 | 177 | // Clear current bitmap only if we set empty URI. 178 | // We support setting bitmap through ImageSource (e.g. Bitmap). 179 | if (uri == null || uri.trim() == "") { 180 | this.setImageBitmap(null); 181 | } 182 | 183 | // Begin loading image only if we are attached to window. 184 | if (mAttachedToWindow) { 185 | loadImage(); 186 | } 187 | } 188 | 189 | public void setImageLoadedListener(Worker.OnImageLoadedListener listener) { 190 | mListener = listener; 191 | } 192 | 193 | private void loadImage() { 194 | Fetcher fetcher = Fetcher.getInstance(this.getContext()); 195 | if (mUri != null && fetcher != null) { 196 | // Get the Bitmap from cache. 197 | fetcher.loadImage(mUri, this, mDecodeWidth, mDecodeHeight, mKeepAspectRatio, mUseCache, mAsync, mListener); 198 | } 199 | } 200 | 201 | @Override 202 | public void setImageBitmap(Bitmap bm) { 203 | Fetcher fetcher = Fetcher.getInstance(this.getContext()); 204 | // if we have existing bitmap from uri notify fetcher that this bitmap is not shown in this ImageView instance. 205 | // This is needed so that fetcher inner cache could reuse the bitmap only when no other ImageView shows it. 206 | if (mUseCache && mUri != null && mBitmap != null && fetcher != null) { 207 | fetcher.removeBitmap(mUri); 208 | } 209 | 210 | super.setImageBitmap(bm); 211 | this.mBitmap = bm; 212 | } 213 | 214 | @Override 215 | protected void onDraw(Canvas canvas) { 216 | BorderDrawable background = this.getBackground() instanceof BorderDrawable ? (BorderDrawable) this.getBackground() : null; 217 | 218 | if (this.mBitmap != null) { 219 | float borderTopLeftRadius, borderTopRightRadius, borderBottomRightRadius, borderBottomLeftRadius; 220 | 221 | if (background != null) { 222 | background.draw(canvas); 223 | 224 | borderTopLeftRadius = background.getBorderTopLeftRadius(); 225 | borderTopRightRadius = background.getBorderTopRightRadius(); 226 | borderBottomRightRadius = background.getBorderBottomRightRadius(); 227 | borderBottomLeftRadius = background.getBorderBottomLeftRadius(); 228 | } else { 229 | borderTopLeftRadius = borderTopRightRadius = borderBottomRightRadius = borderBottomLeftRadius = 0; 230 | } 231 | 232 | // Padding? 233 | float borderTopWidth = this.getPaddingTop(); 234 | float borderRightWidth = this.getPaddingRight(); 235 | float borderBottomWidth = this.getPaddingBottom(); 236 | float borderLeftWidth = this.getPaddingLeft(); 237 | 238 | float innerWidth, innerHeight; 239 | 240 | float rotationDegree = this.getRotationAngle(); 241 | boolean swap = Math.abs(rotationDegree % 180) > 45 && Math.abs(rotationDegree % 180) < 135; 242 | 243 | innerWidth = this.getWidth() - borderLeftWidth - borderRightWidth; 244 | innerHeight = this.getHeight() - borderTopWidth - borderBottomWidth; 245 | 246 | // TODO: Capture all created objects here in locals and update them instead... 247 | Path path = new Path(); 248 | float[] radii = { 249 | Math.max(0, borderTopLeftRadius - borderLeftWidth), Math.max(0, borderTopLeftRadius - borderTopWidth), 250 | Math.max(0, borderTopRightRadius - borderRightWidth), Math.max(0, borderTopRightRadius - borderTopWidth), 251 | Math.max(0, borderBottomRightRadius - borderRightWidth), Math.max(0, borderBottomRightRadius - borderBottomWidth), 252 | Math.max(0, borderBottomLeftRadius - borderLeftWidth), Math.max(0, borderBottomLeftRadius - borderBottomWidth) 253 | }; 254 | path.addRoundRect(new RectF(borderLeftWidth, borderTopWidth, borderLeftWidth + innerWidth, borderTopWidth + innerHeight), radii, Path.Direction.CW); 255 | 256 | Paint paint = new Paint(); 257 | BitmapShader bitmapShader = new BitmapShader(this.mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); 258 | 259 | float bitmapWidth = (float) mBitmap.getWidth(); 260 | float bitmapHeight = (float) mBitmap.getHeight(); 261 | 262 | Matrix matrix = this.mMatrix; 263 | matrix.reset(); 264 | 265 | matrix.postRotate(rotationDegree, bitmapWidth / 2, bitmapHeight / 2); 266 | if (swap) { 267 | matrix.postTranslate((bitmapHeight - bitmapWidth) / 2, (bitmapWidth - bitmapHeight) / 2); 268 | float temp = bitmapWidth; 269 | bitmapWidth = bitmapHeight; 270 | bitmapHeight = temp; 271 | } 272 | 273 | float fittingScaleX = innerWidth / bitmapWidth; 274 | float fittingScaleY = innerHeight / bitmapHeight; 275 | 276 | float uniformScale; 277 | float pivotX, pivotY; 278 | switch(this.getScaleType()) { 279 | case FIT_CENTER: // aspectFit 280 | uniformScale = Math.min(fittingScaleX, fittingScaleY); 281 | matrix.postTranslate((innerWidth - bitmapWidth) / 2, (innerHeight - bitmapHeight) / 2); 282 | matrix.postScale(uniformScale, uniformScale, innerWidth / 2, innerHeight / 2); 283 | canvas.clipRect( 284 | borderLeftWidth + (innerWidth - bitmapWidth * uniformScale) / 2, 285 | borderTopWidth + (innerHeight - bitmapHeight * uniformScale) / 2, 286 | borderLeftWidth + (innerWidth + bitmapWidth * uniformScale) / 2, 287 | borderTopWidth + (innerHeight + bitmapHeight * uniformScale) / 2 288 | ); 289 | break; 290 | case CENTER_CROP: // aspectFill 291 | uniformScale = Math.max(fittingScaleX, fittingScaleY); 292 | matrix.postTranslate((innerWidth - bitmapWidth) / 2, (innerHeight - bitmapHeight) / 2); 293 | matrix.postScale(uniformScale, uniformScale, innerWidth / 2, innerHeight / 2); 294 | canvas.clipRect( 295 | borderLeftWidth + (innerWidth - bitmapWidth * uniformScale) / 2, 296 | borderTopWidth + (innerHeight - bitmapHeight * uniformScale) / 2, 297 | borderLeftWidth + (innerWidth + bitmapWidth * uniformScale) / 2, 298 | borderTopWidth + (innerHeight + bitmapHeight * uniformScale) / 2 299 | ); 300 | break; 301 | case FIT_XY: // fill 302 | matrix.postScale(fittingScaleX, fittingScaleY); 303 | break; 304 | case MATRIX: // none 305 | canvas.clipRect( 306 | borderLeftWidth, 307 | borderTopWidth, 308 | borderLeftWidth + bitmapWidth, 309 | borderTopWidth + bitmapHeight 310 | ); 311 | break; 312 | } 313 | matrix.postTranslate(borderLeftWidth, borderTopWidth); 314 | bitmapShader.setLocalMatrix(matrix); 315 | paint.setAntiAlias(true); 316 | paint.setFilterBitmap(true); 317 | paint.setShader(bitmapShader); 318 | ColorFilter filter = this.getColorFilter(); 319 | if (filter != null) { 320 | paint.setColorFilter(filter); 321 | } 322 | canvas.drawPath(path, paint); 323 | } 324 | } 325 | 326 | @Override 327 | public void setBitmap(Bitmap value) { 328 | this.setImageBitmap(value); 329 | } 330 | 331 | @Override 332 | public void setDrawable(Drawable asyncDrawable) { 333 | this.setImageDrawable(asyncDrawable); 334 | } 335 | } -------------------------------------------------------------------------------- /android/widgets/src/main/java/org/nativescript/widgets/ItemSpec.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package org.nativescript.widgets; 5 | 6 | /** 7 | * @author hhristov 8 | * 9 | */ 10 | public class ItemSpec { 11 | 12 | private int _value; 13 | private GridUnitType _unitType; 14 | 15 | public ItemSpec() { 16 | this(1, GridUnitType.star); 17 | } 18 | 19 | public ItemSpec(int value, GridUnitType unitType) { 20 | this._value = value; 21 | this._unitType = unitType; 22 | } 23 | 24 | GridLayout owner; 25 | int _actualLength = 0; 26 | 27 | @Override 28 | public boolean equals(Object o) { 29 | if (!(o instanceof ItemSpec)) { 30 | return false; 31 | } 32 | 33 | ItemSpec other = (ItemSpec)o; 34 | return (this._unitType == other._unitType) && (this._value == other._value) && (this.owner == other.owner); 35 | } 36 | 37 | public GridUnitType getGridUnitType() { 38 | return this._unitType; 39 | } 40 | 41 | public boolean getIsAbsolute() { 42 | return this._unitType == GridUnitType.pixel; 43 | } 44 | 45 | public boolean getIsAuto() { 46 | return this._unitType == GridUnitType.auto; 47 | } 48 | 49 | public boolean getIsStar() { 50 | return this._unitType == GridUnitType.star; 51 | } 52 | 53 | public int getValue() { 54 | return this._value; 55 | } 56 | 57 | public int getActualLength() { 58 | return this._actualLength; 59 | } 60 | } -------------------------------------------------------------------------------- /android/widgets/src/main/java/org/nativescript/widgets/LayoutBase.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package org.nativescript.widgets; 5 | 6 | import android.content.Context; 7 | import android.util.AttributeSet; 8 | import android.view.Gravity; 9 | import android.view.MotionEvent; 10 | import android.view.View; 11 | import android.view.ViewGroup; 12 | import android.widget.FrameLayout; 13 | 14 | /** 15 | * @author hhristov 16 | * 17 | */ 18 | public abstract class LayoutBase extends ViewGroup { 19 | 20 | public LayoutBase(Context context) { 21 | super(context); 22 | } 23 | 24 | private boolean passThroughParent; 25 | 26 | public boolean getPassThroughParent() { return this.passThroughParent; } 27 | public void setPassThroughParent(boolean value) { this.passThroughParent = value; } 28 | 29 | @Override 30 | protected LayoutParams generateDefaultLayoutParams() { 31 | return new CommonLayoutParams(); 32 | } 33 | 34 | /** 35 | * {@inheritDoc} 36 | */ 37 | @Override 38 | public LayoutParams generateLayoutParams(AttributeSet attrs) { 39 | return new CommonLayoutParams(); 40 | } 41 | 42 | /** 43 | * {@inheritDoc} 44 | */ 45 | @Override 46 | protected boolean checkLayoutParams(LayoutParams p) { 47 | return p instanceof CommonLayoutParams; 48 | } 49 | 50 | @Override 51 | protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams from) { 52 | if (from instanceof CommonLayoutParams) 53 | return new CommonLayoutParams((CommonLayoutParams)from); 54 | 55 | if (from instanceof FrameLayout.LayoutParams) 56 | return new CommonLayoutParams((FrameLayout.LayoutParams)from); 57 | 58 | if (from instanceof ViewGroup.MarginLayoutParams) 59 | return new CommonLayoutParams((ViewGroup.MarginLayoutParams)from); 60 | 61 | return new CommonLayoutParams(from); 62 | } 63 | 64 | @Override 65 | public boolean shouldDelayChildPressedState() { 66 | return false; 67 | } 68 | 69 | @Override 70 | public boolean onTouchEvent(MotionEvent event) { 71 | if (!this.passThroughParent) { 72 | return super.onTouchEvent(event); 73 | } 74 | 75 | // LayoutBase.onTouchEvent(ev) execution means no interactive child view handled 76 | // the event so we let the event pass through to parent view of the layout container 77 | // because passThroughParent is set to true 78 | return false; 79 | } 80 | 81 | protected static int getGravity(View view) { 82 | int gravity = -1; 83 | LayoutParams params = view.getLayoutParams(); 84 | if (params instanceof FrameLayout.LayoutParams) { 85 | gravity = ((FrameLayout.LayoutParams)params).gravity; 86 | } 87 | 88 | if (gravity == -1) { 89 | gravity = Gravity.FILL; 90 | } 91 | 92 | return gravity; 93 | } 94 | } -------------------------------------------------------------------------------- /android/widgets/src/main/java/org/nativescript/widgets/LinearGradientDefinition.java: -------------------------------------------------------------------------------- 1 | package org.nativescript.widgets; 2 | 3 | /** 4 | * Created by Vultix on 3/12/2018. 5 | */ 6 | public class LinearGradientDefinition { 7 | private float startX; 8 | private float startY; 9 | private float endX; 10 | private float endY; 11 | private int[] colors; 12 | private float[] stops; 13 | 14 | public float getStartX() { return startX; } 15 | public float getStartY() { return startY; } 16 | public float getEndX() { return endX; } 17 | public float getEndY() { return endY; } 18 | public int[] getColors() { return colors; } 19 | public float[] getStops() { return stops; } 20 | 21 | public LinearGradientDefinition(float startX, float startY, float endX, float endY, int[] colors, float[] stops) { 22 | this.startX = startX; 23 | this.startY = startY; 24 | this.endX = endX; 25 | this.endY = endY; 26 | this.colors = colors; 27 | this.stops = stops; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /android/widgets/src/main/java/org/nativescript/widgets/Orientation.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package org.nativescript.widgets; 5 | 6 | /** 7 | * @author hhristov 8 | * 9 | */ 10 | public enum Orientation { 11 | horizontal, 12 | vertical 13 | } -------------------------------------------------------------------------------- /android/widgets/src/main/java/org/nativescript/widgets/OriginPoint.java: -------------------------------------------------------------------------------- 1 | package org.nativescript.widgets; 2 | 3 | import android.view.View; 4 | import java.util.WeakHashMap; 5 | 6 | /** 7 | * A class encapsulating the logic of setting an origin point. 8 | * The origin acts as a pivot point but is relative to the View size, 9 | * Where 0 is the left or top of the view, 0.5 is at the middle and 1 is right or bottom. 10 | * Under the hood the pivot point is set but is updated when the View's layout is changed. 11 | */ 12 | public class OriginPoint { 13 | private static WeakHashMap layoutListeners; 14 | 15 | public static void setX(View view, float x) { 16 | getSetter(view).setOriginX(view, x); 17 | } 18 | 19 | public static void setY(View view, float y) { 20 | getSetter(view).setOriginY(view, y); 21 | } 22 | 23 | private static PivotSetter getSetter(View view) { 24 | PivotSetter setter = null; 25 | 26 | if (layoutListeners == null) { 27 | layoutListeners = new WeakHashMap<>(); 28 | } else { 29 | setter = layoutListeners.get(view); 30 | } 31 | 32 | if (setter == null) { 33 | setter = new PivotSetter(); 34 | view.addOnLayoutChangeListener(setter); 35 | layoutListeners.put(view, setter); 36 | } 37 | 38 | return setter; 39 | } 40 | 41 | private static class PivotSetter implements View.OnLayoutChangeListener { 42 | private float originX; 43 | private float originY; 44 | 45 | public PivotSetter() { 46 | originX = 0.5f; 47 | originY = 0.5f; 48 | } 49 | 50 | public void setOriginX(View view, float x) { 51 | originX = x; 52 | updateX(view, view.getWidth()); 53 | } 54 | 55 | public void setOriginY(View view, float y) { 56 | originY = y; 57 | updateY(view, view.getHeight()); 58 | } 59 | 60 | public void onLayoutChange(View view, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { 61 | updateX(view, right - left); 62 | updateY(view, bottom - top); 63 | } 64 | 65 | private void updateX(View view, int width) { 66 | view.setPivotX(originX * width); 67 | } 68 | 69 | private void updateY(View view, int height) { 70 | view.setPivotY(originY * height); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /android/widgets/src/main/java/org/nativescript/widgets/SegmentedBarColorDrawable.java: -------------------------------------------------------------------------------- 1 | package org.nativescript.widgets; 2 | 3 | import android.graphics.Paint; 4 | import android.graphics.drawable.ColorDrawable; 5 | import android.support.annotation.ColorInt; 6 | 7 | /** 8 | * Created by hhristov on 2/23/17. 9 | */ 10 | 11 | public class SegmentedBarColorDrawable extends ColorDrawable { 12 | 13 | private float thickness; 14 | 15 | public SegmentedBarColorDrawable(@ColorInt int color, float thickness) { 16 | super(color); 17 | this.thickness = thickness; 18 | } 19 | 20 | public void draw(android.graphics.Canvas canvas) { 21 | Paint p = new Paint(); 22 | p.setColor(this.getColor()); 23 | p.setStyle(android.graphics.Paint.Style.FILL); 24 | canvas.drawRect(0, this.getBounds().height() - thickness, this.getBounds().width(), this.getBounds().height(), p); 25 | } 26 | } -------------------------------------------------------------------------------- /android/widgets/src/main/java/org/nativescript/widgets/StackLayout.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package org.nativescript.widgets; 5 | 6 | import android.annotation.SuppressLint; 7 | import android.content.Context; 8 | import android.view.Gravity; 9 | import android.view.View; 10 | import android.util.Log; 11 | 12 | /** 13 | * @author hhristov 14 | * 15 | */ 16 | public class StackLayout extends LayoutBase { 17 | static final String TAG = "JS"; 18 | private int _totalLength = 0; 19 | private Orientation _orientation = Orientation.vertical; 20 | 21 | public StackLayout(Context context) { 22 | super(context); 23 | } 24 | 25 | public Orientation getOrientation() { 26 | return this._orientation; 27 | } 28 | public void setOrientation(Orientation value) { 29 | this._orientation = value; 30 | this.requestLayout(); 31 | } 32 | 33 | @Override 34 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 35 | CommonLayoutParams.adjustChildrenLayoutParams(this, widthMeasureSpec, heightMeasureSpec); 36 | 37 | int childState = 0; 38 | int measureWidth = 0; 39 | int measureHeight = 0; 40 | 41 | int width = MeasureSpec.getSize(widthMeasureSpec); 42 | int widthMode = MeasureSpec.getMode(widthMeasureSpec); 43 | 44 | int height = MeasureSpec.getSize(heightMeasureSpec); 45 | int heightMode = MeasureSpec.getMode(heightMeasureSpec); 46 | 47 | boolean isVertical = this._orientation == Orientation.vertical; 48 | int verticalPadding = this.getPaddingTop() + this.getPaddingBottom(); 49 | int horizontalPadding = this.getPaddingLeft() + this.getPaddingRight(); 50 | 51 | int count = this.getChildCount(); 52 | int measureSpecMode; 53 | int remainingLength; 54 | 55 | int mode = isVertical ? heightMode : widthMode; 56 | if (mode == MeasureSpec.UNSPECIFIED) { 57 | measureSpecMode = MeasureSpec.UNSPECIFIED; 58 | remainingLength = 0; 59 | } 60 | else { 61 | measureSpecMode = MeasureSpec.AT_MOST; 62 | remainingLength = isVertical ? height - verticalPadding : width - horizontalPadding; 63 | } 64 | 65 | int childMeasureSpec; 66 | if (isVertical) { 67 | int childWidth = (widthMode == MeasureSpec.UNSPECIFIED) ? 0 : width - horizontalPadding; 68 | childWidth = Math.max(0, childWidth); 69 | childMeasureSpec = MeasureSpec.makeMeasureSpec(childWidth, widthMode); 70 | } 71 | else { 72 | int childHeight = (heightMode == MeasureSpec.UNSPECIFIED) ? 0 : height - verticalPadding; 73 | childHeight = Math.max(0, childHeight); 74 | childMeasureSpec = MeasureSpec.makeMeasureSpec(childHeight, heightMode); 75 | } 76 | 77 | for (int i = 0; i < count; i++) { 78 | View child = this.getChildAt(i); 79 | if (child.getVisibility() == View.GONE) { 80 | continue; 81 | } 82 | 83 | if (isVertical) { 84 | // Measuring android.widget.ListView, with no height property set, with MeasureSpec.AT_MOST will 85 | // result in height required for all list view items or the maximum available space for the StackLayout. 86 | // Any following controls will be visible only if enough space left. 87 | CommonLayoutParams.measureChild(child, childMeasureSpec, MeasureSpec.makeMeasureSpec(remainingLength, measureSpecMode)); 88 | 89 | if(measureSpecMode == MeasureSpec.AT_MOST && this.isUnsizedScrollableView(child)){ 90 | Log.e(TAG, "Avoid using ListView or ScrollView with no explicit height set inside StackLayout. Doing so might results in poor user interface performance and a poor user experience."); 91 | } 92 | 93 | final int childMeasuredWidth = CommonLayoutParams.getDesiredWidth(child); 94 | final int childMeasuredHeight = CommonLayoutParams.getDesiredHeight(child); 95 | 96 | measureWidth = Math.max(measureWidth, childMeasuredWidth); 97 | measureHeight += childMeasuredHeight; 98 | remainingLength = Math.max(0, remainingLength - childMeasuredHeight); 99 | } 100 | else { 101 | CommonLayoutParams.measureChild(child, MeasureSpec.makeMeasureSpec(remainingLength, measureSpecMode), childMeasureSpec); 102 | final int childMeasuredWidth = CommonLayoutParams.getDesiredWidth(child); 103 | final int childMeasuredHeight = CommonLayoutParams.getDesiredHeight(child); 104 | 105 | measureHeight = Math.max(measureHeight, childMeasuredHeight); 106 | measureWidth += childMeasuredWidth; 107 | remainingLength = Math.max(0, remainingLength - childMeasuredWidth); 108 | } 109 | 110 | childState = combineMeasuredStates(childState, child.getMeasuredState()); 111 | } 112 | 113 | // Add in our padding 114 | measureWidth += horizontalPadding; 115 | measureHeight += verticalPadding; 116 | 117 | // Check against our minimum sizes 118 | measureWidth = Math.max(measureWidth, this.getSuggestedMinimumWidth()); 119 | measureHeight = Math.max(measureHeight, this.getSuggestedMinimumHeight()); 120 | 121 | this._totalLength = isVertical ? measureHeight : measureWidth; 122 | 123 | int widthSizeAndState = resolveSizeAndState(measureWidth, widthMeasureSpec, isVertical ? childState : 0); 124 | int heightSizeAndState = resolveSizeAndState(measureHeight, heightMeasureSpec, isVertical ? 0 : childState); 125 | 126 | this.setMeasuredDimension(widthSizeAndState, heightSizeAndState); 127 | } 128 | 129 | @Override 130 | protected void onLayout(boolean changed, int l, int t, int r, int b) { 131 | if (this._orientation == Orientation.vertical) { 132 | this.layoutVertical(l, t, r, b); 133 | } 134 | else { 135 | this.layoutHorizontal(l, t, r, b); 136 | } 137 | 138 | CommonLayoutParams.restoreOriginalParams(this); 139 | } 140 | 141 | private void layoutVertical(int left, int top, int right, int bottom) { 142 | 143 | int paddingLeft = this.getPaddingLeft(); 144 | int paddingRight = this.getPaddingRight(); 145 | int paddingTop = this.getPaddingTop(); 146 | int paddingBottom = this.getPaddingBottom(); 147 | 148 | int childTop = 0; 149 | int childLeft = paddingLeft; 150 | int childRight = right - left - paddingRight; 151 | 152 | int gravity = LayoutBase.getGravity(this); 153 | final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK; 154 | 155 | switch (verticalGravity) { 156 | case Gravity.CENTER_VERTICAL: 157 | childTop = (bottom - top - this._totalLength) / 2 + paddingTop - paddingBottom; 158 | break; 159 | 160 | case Gravity.BOTTOM: 161 | childTop = bottom - top - this._totalLength + paddingTop - paddingBottom; 162 | break; 163 | 164 | case Gravity.TOP: 165 | case Gravity.FILL_VERTICAL: 166 | default: 167 | childTop = paddingTop; 168 | break; 169 | } 170 | 171 | for (int i = 0, count = this.getChildCount(); i < count; i++) { 172 | View child = this.getChildAt(i); 173 | if (child.getVisibility() == View.GONE) { 174 | continue; 175 | } 176 | 177 | int childHeight = CommonLayoutParams.getDesiredHeight(child); 178 | CommonLayoutParams.layoutChild(child, childLeft, childTop, childRight, childTop + childHeight); 179 | childTop += childHeight; 180 | } 181 | } 182 | 183 | @SuppressLint("RtlHardcoded") 184 | private void layoutHorizontal(int left, int top, int right, int bottom) { 185 | 186 | int paddingLeft = this.getPaddingLeft(); 187 | int paddingRight = this.getPaddingRight(); 188 | int paddingTop = this.getPaddingTop(); 189 | int paddingBottom = this.getPaddingBottom(); 190 | 191 | int childTop = paddingTop; 192 | int childLeft = 0; 193 | int childBottom = bottom - top - paddingBottom; 194 | 195 | int gravity = LayoutBase.getGravity(this); 196 | final int horizontalGravity = Gravity.getAbsoluteGravity(gravity, this.getLayoutDirection()) & Gravity.HORIZONTAL_GRAVITY_MASK; 197 | 198 | switch (horizontalGravity) { 199 | case Gravity.CENTER_HORIZONTAL: 200 | childLeft = (right - left - this._totalLength) / 2 + paddingLeft - paddingRight; 201 | break; 202 | 203 | case Gravity.RIGHT: 204 | childLeft = right - left - this._totalLength + paddingLeft - paddingRight; 205 | break; 206 | 207 | case Gravity.LEFT: 208 | case Gravity.FILL_HORIZONTAL: 209 | default: 210 | childLeft = paddingLeft; 211 | break; 212 | } 213 | 214 | for (int i = 0, count = this.getChildCount(); i < count; i++) { 215 | View child = this.getChildAt(i); 216 | if (child.getVisibility() == View.GONE) { 217 | continue; 218 | } 219 | 220 | int childWidth = CommonLayoutParams.getDesiredWidth(child); 221 | CommonLayoutParams.layoutChild(child, childLeft, childTop, childLeft + childWidth, childBottom); 222 | childLeft += childWidth; 223 | } 224 | } 225 | 226 | private boolean isUnsizedScrollableView(View child) { 227 | LayoutParams childLayoutParams = child.getLayoutParams(); 228 | 229 | if (childLayoutParams.height == -1 && (child instanceof android.widget.ListView || child instanceof org.nativescript.widgets.VerticalScrollView)) { 230 | return true; 231 | } 232 | 233 | return false; 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /android/widgets/src/main/java/org/nativescript/widgets/TabItemSpec.java: -------------------------------------------------------------------------------- 1 | package org.nativescript.widgets; 2 | 3 | import android.graphics.drawable.Drawable; 4 | 5 | public class TabItemSpec { 6 | public String title; 7 | public int iconId; 8 | public Drawable iconDrawable; 9 | } -------------------------------------------------------------------------------- /android/widgets/src/main/java/org/nativescript/widgets/TabLayout.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Google Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.nativescript.widgets; 18 | 19 | import android.content.Context; 20 | import android.graphics.Typeface; 21 | import android.support.v4.view.PagerAdapter; 22 | import android.support.v4.view.ViewPager; 23 | import android.text.TextUtils; 24 | import android.util.AttributeSet; 25 | import android.util.SparseArray; 26 | import android.util.TypedValue; 27 | import android.view.Gravity; 28 | import android.view.View; 29 | import android.view.ViewGroup; 30 | import android.widget.HorizontalScrollView; 31 | import android.widget.ImageView; 32 | import android.widget.ImageView.ScaleType; 33 | import android.widget.LinearLayout; 34 | import android.widget.TextView; 35 | 36 | /** 37 | * To be used with ViewPager to provide a tab indicator component which give 38 | * constant feedback as to the user's scroll progress. 39 | *

40 | * To use the component, simply add it to your view hierarchy. Then in your 41 | * {@link android.app.Activity} or {@link android.support.v4.app.Fragment} call 42 | * {@link #setViewPager(ViewPager)} providing it the ViewPager this layout is 43 | * being used for. 44 | *

45 | * The colors can be customized in two ways. The first and simplest is to 46 | * provide an array of colors via {@link #setSelectedIndicatorColors(int...)}. 47 | * The alternative is via the {@link TabColorizer} interface which provides you 48 | * complete control over which color is used for any individual position. 49 | *

50 | */ 51 | public class TabLayout extends HorizontalScrollView { 52 | /** 53 | * Allows complete control over the colors drawn in the tab layout. Set with 54 | * {@link #setCustomTabColorizer(TabColorizer)}. 55 | */ 56 | public interface TabColorizer { 57 | 58 | /** 59 | * @return return the color of the indicator used when {@code position} 60 | * is selected. 61 | */ 62 | int getIndicatorColor(int position); 63 | 64 | } 65 | 66 | private static final int TITLE_OFFSET_DIPS = 24; 67 | private static final int TAB_VIEW_PADDING_DIPS = 16; 68 | private static final int TAB_VIEW_TEXT_SIZE_SP = 12; 69 | private static final int TEXT_MAX_WIDTH = 180; 70 | private static final int SMALL_MIN_HEIGHT = 48; 71 | private static final int LARGE_MIN_HEIGHT = 72; 72 | 73 | private int mTitleOffset; 74 | 75 | private boolean mDistributeEvenly = true; 76 | 77 | private TabItemSpec[] mTabItems; 78 | private ViewPager mViewPager; 79 | private SparseArray mContentDescriptions = new SparseArray(); 80 | private ViewPager.OnPageChangeListener mViewPagerPageChangeListener; 81 | 82 | private final TabStrip mTabStrip; 83 | 84 | public TabLayout(Context context) { 85 | this(context, null); 86 | } 87 | 88 | public TabLayout(Context context, AttributeSet attrs) { 89 | this(context, attrs, 0); 90 | } 91 | 92 | public TabLayout(Context context, AttributeSet attrs, int defStyle) { 93 | super(context, attrs, defStyle); 94 | // Disable the Scroll Bar 95 | setHorizontalScrollBarEnabled(false); 96 | // Make sure that the Tab Strips fills this View 97 | setFillViewport(true); 98 | 99 | mTitleOffset = (int) (TITLE_OFFSET_DIPS * getResources().getDisplayMetrics().density); 100 | 101 | mTabStrip = new TabStrip(context); 102 | addView(mTabStrip, LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); 103 | } 104 | 105 | /** 106 | * Set the custom {@link TabColorizer} to be used. 107 | * 108 | * If you only require simple customisation then you can use 109 | * {@link #setSelectedIndicatorColors(int...)} to achieve similar effects. 110 | */ 111 | public void setCustomTabColorizer(TabColorizer tabColorizer) { 112 | mTabStrip.setCustomTabColorizer(tabColorizer); 113 | } 114 | 115 | public void setDistributeEvenly(boolean distributeEvenly) { 116 | mDistributeEvenly = distributeEvenly; 117 | } 118 | 119 | /** 120 | * Sets the colors to be used for indicating the selected tab. These colors 121 | * are treated as a circular array. Providing one color will mean that all 122 | * tabs are indicated with the same color. 123 | */ 124 | public void setSelectedIndicatorColors(int... colors) { 125 | mTabStrip.setSelectedIndicatorColors(colors); 126 | this.mSelectedIndicatorColors = colors; 127 | } 128 | 129 | private int[] mSelectedIndicatorColors; 130 | public int[] getSelectedIndicatorColors() { 131 | return this.mSelectedIndicatorColors; 132 | } 133 | 134 | public void setTabTextColor(int color){ 135 | mTabStrip.setTabTextColor(color); 136 | } 137 | 138 | public int getTabTextColor(){ 139 | return mTabStrip.getTabTextColor(); 140 | } 141 | 142 | public void setSelectedTabTextColor(int color){ 143 | mTabStrip.setSelectedTabTextColor(color); 144 | } 145 | 146 | public int getSelectedTabTextColor(){ 147 | return mTabStrip.getSelectedTabTextColor(); 148 | } 149 | 150 | public void setTabTextFontSize(float fontSize){ 151 | mTabStrip.setTabTextFontSize(fontSize); 152 | } 153 | 154 | public float getTabTextFontSize(){ 155 | return mTabStrip.getTabTextFontSize(); 156 | } 157 | 158 | /** 159 | * Set the {@link ViewPager.OnPageChangeListener}. When using 160 | * {@link TabLayout} you are required to set any 161 | * {@link ViewPager.OnPageChangeListener} through this method. This is so 162 | * that the layout can update it's scroll position correctly. 163 | * 164 | * @see ViewPager#setOnPageChangeListener(ViewPager.OnPageChangeListener) 165 | */ 166 | public void setOnPageChangeListener(ViewPager.OnPageChangeListener listener) { 167 | mViewPagerPageChangeListener = listener; 168 | } 169 | 170 | /** 171 | * Sets the associated view pager. Note that the assumption here is that the 172 | * pager content (number of tabs and tab titles) does not change after this 173 | * call has been made. 174 | */ 175 | public void setViewPager(ViewPager viewPager) { 176 | this.setItems(null, viewPager); 177 | } 178 | 179 | public void setItems(TabItemSpec[] items, ViewPager viewPager) { 180 | mTabStrip.removeAllViews(); 181 | 182 | mViewPager = viewPager; 183 | mTabItems = items; 184 | if (viewPager != null) { 185 | viewPager.addOnPageChangeListener(new InternalViewPagerListener()); 186 | populateTabStrip(); 187 | } 188 | } 189 | 190 | /** 191 | * Updates the UI of an item at specified index 192 | */ 193 | public void updateItemAt(int position, TabItemSpec tabItem) { 194 | LinearLayout ll = (LinearLayout)mTabStrip.getChildAt(position); 195 | ImageView imgView = (ImageView)ll.getChildAt(0); 196 | TextView textView = (TextView)ll.getChildAt(1); 197 | this.setupItem(ll, textView, imgView, tabItem); 198 | } 199 | 200 | /** 201 | * Gets the TextView for tab item at index 202 | */ 203 | public TextView getTextViewForItemAt(int index){ 204 | LinearLayout ll = this.getViewForItemAt(index); 205 | return (ll != null) ? (TextView)ll.getChildAt(1) : null; 206 | } 207 | 208 | /** 209 | * Gets the LinearLayout container for tab item at index 210 | */ 211 | public LinearLayout getViewForItemAt(int index){ 212 | LinearLayout result = null; 213 | 214 | if(this.mTabStrip.getChildCount() > index){ 215 | result = (LinearLayout)this.mTabStrip.getChildAt(index); 216 | } 217 | 218 | return result; 219 | } 220 | 221 | /** 222 | * Gets the number of realized tabs. 223 | */ 224 | public int getItemCount(){ 225 | return this.mTabStrip.getChildCount(); 226 | } 227 | 228 | /** 229 | * Create a default view to be used for tabs. 230 | */ 231 | protected View createDefaultTabView(Context context, TabItemSpec tabItem) { 232 | float density = getResources().getDisplayMetrics().density; 233 | int padding = (int) (TAB_VIEW_PADDING_DIPS * density); 234 | 235 | LinearLayout ll = new LinearLayout(context); 236 | ll.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT)); 237 | ll.setGravity(Gravity.CENTER); 238 | ll.setOrientation(LinearLayout.VERTICAL); 239 | TypedValue outValue = new TypedValue(); 240 | getContext().getTheme().resolveAttribute(android.R.attr.selectableItemBackground, outValue, true); 241 | ll.setBackgroundResource(outValue.resourceId); 242 | 243 | ImageView imgView = new ImageView(context); 244 | imgView.setScaleType(ScaleType.FIT_CENTER); 245 | LinearLayout.LayoutParams imgLP = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); 246 | imgLP.gravity = Gravity.CENTER; 247 | imgView.setLayoutParams(imgLP); 248 | 249 | TextView textView = new TextView(context); 250 | textView.setGravity(Gravity.CENTER); 251 | textView.setMaxWidth((int) (TEXT_MAX_WIDTH * density)); 252 | textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, TAB_VIEW_TEXT_SIZE_SP); 253 | textView.setTypeface(Typeface.DEFAULT_BOLD); 254 | textView.setEllipsize(TextUtils.TruncateAt.END); 255 | textView.setAllCaps(true); 256 | textView.setMaxLines(2); 257 | textView.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); 258 | textView.setPadding(padding, 0, padding, 0); 259 | 260 | this.setupItem(ll, textView, imgView, tabItem); 261 | 262 | ll.addView(imgView); 263 | ll.addView(textView); 264 | return ll; 265 | } 266 | 267 | private void setupItem(LinearLayout ll, TextView textView,ImageView imgView, TabItemSpec tabItem){ 268 | float density = getResources().getDisplayMetrics().density; 269 | 270 | if (tabItem.iconId != 0) { 271 | imgView.setImageResource(tabItem.iconId); 272 | imgView.setVisibility(VISIBLE); 273 | } else if (tabItem.iconDrawable != null) { 274 | imgView.setImageDrawable(tabItem.iconDrawable); 275 | imgView.setVisibility(VISIBLE); 276 | } else { 277 | imgView.setVisibility(GONE); 278 | } 279 | 280 | if (tabItem.title != null && !tabItem.title.isEmpty()) { 281 | textView.setText(tabItem.title); 282 | textView.setVisibility(VISIBLE); 283 | } else { 284 | textView.setVisibility(GONE); 285 | } 286 | 287 | if (imgView.getVisibility() == VISIBLE && textView.getVisibility() == VISIBLE) { 288 | ll.setMinimumHeight((int) (LARGE_MIN_HEIGHT * density)); 289 | } else { 290 | ll.setMinimumHeight((int) (SMALL_MIN_HEIGHT * density)); 291 | } 292 | 293 | if (mDistributeEvenly) { 294 | LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) ll.getLayoutParams(); 295 | lp.width = 0; 296 | lp.weight = 1; 297 | } 298 | } 299 | 300 | private void populateTabStrip() { 301 | final PagerAdapter adapter = mViewPager.getAdapter(); 302 | final OnClickListener tabClickListener = new TabClickListener(); 303 | 304 | for (int i = 0; i < adapter.getCount(); i++) { 305 | View tabView = null; 306 | 307 | TabItemSpec tabItem; 308 | if (this.mTabItems != null && this.mTabItems.length > i) { 309 | tabItem = this.mTabItems[i]; 310 | } else { 311 | tabItem = new TabItemSpec(); 312 | tabItem.title = adapter.getPageTitle(i).toString(); 313 | } 314 | 315 | tabView = createDefaultTabView(getContext(), tabItem); 316 | 317 | tabView.setOnClickListener(tabClickListener); 318 | String desc = mContentDescriptions.get(i, null); 319 | if (desc != null) { 320 | tabView.setContentDescription(desc); 321 | } 322 | 323 | mTabStrip.addView(tabView); 324 | if (i == mViewPager.getCurrentItem()) { 325 | tabView.setSelected(true); 326 | } 327 | } 328 | } 329 | 330 | public void setContentDescription(int i, String desc) { 331 | mContentDescriptions.put(i, desc); 332 | } 333 | 334 | @Override 335 | protected void onAttachedToWindow() { 336 | super.onAttachedToWindow(); 337 | 338 | if (mViewPager != null) { 339 | scrollToTab(mViewPager.getCurrentItem(), 0); 340 | } 341 | } 342 | 343 | private void scrollToTab(int tabIndex, int positionOffset) { 344 | final int tabStripChildCount = mTabStrip.getChildCount(); 345 | if (tabStripChildCount == 0 || tabIndex < 0 || tabIndex >= tabStripChildCount) { 346 | return; 347 | } 348 | 349 | View selectedChild = mTabStrip.getChildAt(tabIndex); 350 | if (selectedChild != null) { 351 | int targetScrollX = selectedChild.getLeft() + positionOffset; 352 | 353 | if (tabIndex > 0 || positionOffset > 0) { 354 | // If we're not at the first child and are mid-scroll, make sure 355 | // we obey the offset 356 | targetScrollX -= mTitleOffset; 357 | } 358 | 359 | scrollTo(targetScrollX, 0); 360 | } 361 | } 362 | 363 | private class InternalViewPagerListener implements ViewPager.OnPageChangeListener { 364 | private int mScrollState; 365 | 366 | @Override 367 | public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 368 | int tabStripChildCount = mTabStrip.getChildCount(); 369 | if ((tabStripChildCount == 0) || (position < 0) || (position >= tabStripChildCount)) { 370 | return; 371 | } 372 | 373 | mTabStrip.onViewPagerPageChanged(position, positionOffset); 374 | 375 | View selectedTitle = mTabStrip.getChildAt(position); 376 | int extraOffset = (selectedTitle != null) ? (int) (positionOffset * selectedTitle.getWidth()) : 0; 377 | scrollToTab(position, extraOffset); 378 | 379 | if (mViewPagerPageChangeListener != null) { 380 | mViewPagerPageChangeListener.onPageScrolled(position, positionOffset, positionOffsetPixels); 381 | } 382 | } 383 | 384 | @Override 385 | public void onPageScrollStateChanged(int state) { 386 | mScrollState = state; 387 | 388 | if (mViewPagerPageChangeListener != null) { 389 | mViewPagerPageChangeListener.onPageScrollStateChanged(state); 390 | } 391 | } 392 | 393 | @Override 394 | public void onPageSelected(int position) { 395 | if (mScrollState == ViewPager.SCROLL_STATE_IDLE) { 396 | mTabStrip.onViewPagerPageChanged(position, 0f); 397 | scrollToTab(position, 0); 398 | } 399 | for (int i = 0; i < mTabStrip.getChildCount(); i++) { 400 | mTabStrip.getChildAt(i).setSelected(position == i); 401 | } 402 | if (mViewPagerPageChangeListener != null) { 403 | mViewPagerPageChangeListener.onPageSelected(position); 404 | } 405 | } 406 | 407 | } 408 | 409 | private class TabClickListener implements OnClickListener { 410 | @Override 411 | public void onClick(View v) { 412 | for (int i = 0; i < mTabStrip.getChildCount(); i++) { 413 | if (v == mTabStrip.getChildAt(i)) { 414 | mViewPager.setCurrentItem(i); 415 | return; 416 | } 417 | } 418 | } 419 | } 420 | } -------------------------------------------------------------------------------- /android/widgets/src/main/java/org/nativescript/widgets/TabStrip.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Google Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.nativescript.widgets; 18 | 19 | 20 | import android.content.Context; 21 | import android.graphics.Canvas; 22 | import android.graphics.Color; 23 | import android.graphics.Paint; 24 | import android.util.AttributeSet; 25 | import android.util.TypedValue; 26 | import android.view.View; 27 | import android.widget.LinearLayout; 28 | import android.widget.TextView; 29 | 30 | class TabStrip extends LinearLayout { 31 | 32 | private static final int DEFAULT_BOTTOM_BORDER_THICKNESS_DIPS = 0; 33 | private static final byte DEFAULT_BOTTOM_BORDER_COLOR_ALPHA = 0x26; 34 | private static final int SELECTED_INDICATOR_THICKNESS_DIPS = 3; 35 | private static final int DEFAULT_SELECTED_INDICATOR_COLOR = 0xFF33B5E5; 36 | 37 | private final int mBottomBorderThickness; 38 | private final Paint mBottomBorderPaint; 39 | 40 | private final int mSelectedIndicatorThickness; 41 | private final Paint mSelectedIndicatorPaint; 42 | 43 | private final int mDefaultBottomBorderColor; 44 | 45 | private int mSelectedPosition; 46 | private float mSelectionOffset; 47 | 48 | private TabLayout.TabColorizer mCustomTabColorizer; 49 | private final SimpleTabColorizer mDefaultTabColorizer; 50 | 51 | private int mTabTextColor; 52 | private int mSelectedTabTextColor; 53 | private float mTabTextFontSize; 54 | 55 | TabStrip(Context context) { 56 | this(context, null); 57 | } 58 | 59 | TabStrip(Context context, AttributeSet attrs) { 60 | super(context, attrs); 61 | 62 | setWillNotDraw(false); 63 | 64 | final float density = getResources().getDisplayMetrics().density; 65 | 66 | TypedValue outValue = new TypedValue(); 67 | context.getTheme().resolveAttribute(android.R.attr.colorForeground, outValue, true); 68 | final int themeForegroundColor = outValue.data; 69 | 70 | mDefaultBottomBorderColor = setColorAlpha(themeForegroundColor, 71 | DEFAULT_BOTTOM_BORDER_COLOR_ALPHA); 72 | 73 | mDefaultTabColorizer = new SimpleTabColorizer(); 74 | mDefaultTabColorizer.setIndicatorColors(DEFAULT_SELECTED_INDICATOR_COLOR); 75 | 76 | mBottomBorderThickness = (int) (DEFAULT_BOTTOM_BORDER_THICKNESS_DIPS * density); 77 | mBottomBorderPaint = new Paint(); 78 | mBottomBorderPaint.setColor(mDefaultBottomBorderColor); 79 | 80 | mSelectedIndicatorThickness = (int) (SELECTED_INDICATOR_THICKNESS_DIPS * density); 81 | mSelectedIndicatorPaint = new Paint(); 82 | 83 | TextView defaultTextView = new TextView(context); 84 | mTabTextColor = defaultTextView.getTextColors().getDefaultColor(); 85 | mTabTextFontSize = defaultTextView.getTextSize(); 86 | 87 | // Default selected color is the same as mTabTextColor 88 | mSelectedTabTextColor = mTabTextColor; 89 | 90 | setMeasureWithLargestChildEnabled(true); 91 | } 92 | 93 | void setCustomTabColorizer(TabLayout.TabColorizer customTabColorizer) { 94 | mCustomTabColorizer = customTabColorizer; 95 | invalidate(); 96 | } 97 | 98 | void setSelectedIndicatorColors(int... colors) { 99 | // Make sure that the custom colorizer is removed 100 | mCustomTabColorizer = null; 101 | mDefaultTabColorizer.setIndicatorColors(colors); 102 | invalidate(); 103 | } 104 | 105 | void setTabTextColor(int color){ 106 | mTabTextColor = color; 107 | updateTabsTextColor(); 108 | } 109 | 110 | int getTabTextColor(){ 111 | return mTabTextColor; 112 | } 113 | 114 | void setSelectedTabTextColor(int color){ 115 | mSelectedTabTextColor = color; 116 | updateTabsTextColor(); 117 | } 118 | 119 | int getSelectedTabTextColor(){ 120 | return mSelectedTabTextColor; 121 | } 122 | 123 | private void updateTabsTextColor(){ 124 | final int childCount = getChildCount(); 125 | for (int i = 0; i < childCount; i++){ 126 | LinearLayout linearLayout = (LinearLayout)getChildAt(i); 127 | TextView textView = (TextView)linearLayout.getChildAt(1); 128 | if (i == mSelectedPosition){ 129 | textView.setTextColor(mSelectedTabTextColor); 130 | } 131 | else { 132 | textView.setTextColor(mTabTextColor); 133 | } 134 | } 135 | } 136 | 137 | void setTabTextFontSize(float fontSize){ 138 | mTabTextFontSize = fontSize; 139 | updateTabsTextFontSize(); 140 | } 141 | 142 | float getTabTextFontSize(){ 143 | return mTabTextFontSize; 144 | } 145 | 146 | private void updateTabsTextFontSize(){ 147 | final int childCount = getChildCount(); 148 | for (int i = 0; i < childCount; i++){ 149 | LinearLayout linearLayout = (LinearLayout)getChildAt(i); 150 | TextView textView = (TextView)linearLayout.getChildAt(1); 151 | textView.setTextSize(mTabTextFontSize); 152 | } 153 | } 154 | 155 | void onViewPagerPageChanged(int position, float positionOffset) { 156 | mSelectedPosition = position; 157 | mSelectionOffset = positionOffset; 158 | invalidate(); 159 | updateTabsTextColor(); 160 | } 161 | 162 | @Override 163 | protected void onDraw(Canvas canvas) { 164 | final int height = getHeight(); 165 | final int childCount = getChildCount(); 166 | final TabLayout.TabColorizer tabColorizer = mCustomTabColorizer != null 167 | ? mCustomTabColorizer 168 | : mDefaultTabColorizer; 169 | 170 | // Thick colored underline below the current selection 171 | if (childCount > 0) { 172 | View selectedTitle = getChildAt(mSelectedPosition); 173 | int left = selectedTitle.getLeft(); 174 | int right = selectedTitle.getRight(); 175 | int color = tabColorizer.getIndicatorColor(mSelectedPosition); 176 | 177 | if (mSelectionOffset > 0f && mSelectedPosition < (getChildCount() - 1)) { 178 | int nextColor = tabColorizer.getIndicatorColor(mSelectedPosition + 1); 179 | if (color != nextColor) { 180 | color = blendColors(nextColor, color, mSelectionOffset); 181 | } 182 | 183 | // Draw the selection partway between the tabs 184 | View nextTitle = getChildAt(mSelectedPosition + 1); 185 | left = (int) (mSelectionOffset * nextTitle.getLeft() + 186 | (1.0f - mSelectionOffset) * left); 187 | right = (int) (mSelectionOffset * nextTitle.getRight() + 188 | (1.0f - mSelectionOffset) * right); 189 | } 190 | 191 | mSelectedIndicatorPaint.setColor(color); 192 | 193 | canvas.drawRect(left, height - mSelectedIndicatorThickness, right, 194 | height, mSelectedIndicatorPaint); 195 | } 196 | 197 | // Thin underline along the entire bottom edge 198 | canvas.drawRect(0, height - mBottomBorderThickness, getWidth(), height, mBottomBorderPaint); 199 | } 200 | 201 | /** 202 | * Set the alpha value of the {@code color} to be the given {@code alpha} value. 203 | */ 204 | private static int setColorAlpha(int color, byte alpha) { 205 | return Color.argb(alpha, Color.red(color), Color.green(color), Color.blue(color)); 206 | } 207 | 208 | /** 209 | * Blend {@code color1} and {@code color2} using the given ratio. 210 | * 211 | * @param ratio of which to blend. 1.0 will return {@code color1}, 0.5 will give an even blend, 212 | * 0.0 will return {@code color2}. 213 | */ 214 | private static int blendColors(int color1, int color2, float ratio) { 215 | final float inverseRation = 1f - ratio; 216 | float r = (Color.red(color1) * ratio) + (Color.red(color2) * inverseRation); 217 | float g = (Color.green(color1) * ratio) + (Color.green(color2) * inverseRation); 218 | float b = (Color.blue(color1) * ratio) + (Color.blue(color2) * inverseRation); 219 | return Color.rgb((int) r, (int) g, (int) b); 220 | } 221 | 222 | private static class SimpleTabColorizer implements TabLayout.TabColorizer { 223 | private int[] mIndicatorColors; 224 | 225 | @Override 226 | public final int getIndicatorColor(int position) { 227 | return mIndicatorColors[position % mIndicatorColors.length]; 228 | } 229 | 230 | void setIndicatorColors(int... colors) { 231 | mIndicatorColors = colors; 232 | } 233 | } 234 | } -------------------------------------------------------------------------------- /android/widgets/src/main/java/org/nativescript/widgets/TabViewPager.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package org.nativescript.widgets; 5 | 6 | import android.content.Context; 7 | import android.support.v4.view.ViewPager; 8 | import android.util.AttributeSet; 9 | import android.view.View; 10 | import android.view.MotionEvent; 11 | import android.view.KeyEvent; 12 | 13 | // See this thread for more information https://stackoverflow.com/questions/9650265 14 | public class TabViewPager extends ViewPager { 15 | private boolean swipePageEnabled = true; 16 | 17 | public TabViewPager(Context context) { 18 | super(context); 19 | } 20 | 21 | public TabViewPager(Context context, AttributeSet attrs) { 22 | super(context, attrs); 23 | } 24 | 25 | public void setSwipePageEnabled(boolean enabled) { 26 | this.swipePageEnabled = enabled; 27 | } 28 | 29 | @Override 30 | public boolean onInterceptTouchEvent(MotionEvent event) { 31 | if (this.swipePageEnabled) { 32 | return super.onInterceptTouchEvent(event); 33 | } 34 | 35 | return false; 36 | } 37 | 38 | @Override 39 | public boolean onTouchEvent(MotionEvent event) { 40 | if (this.swipePageEnabled) { 41 | return super.onTouchEvent(event); 42 | } 43 | 44 | return false; 45 | } 46 | 47 | @Override 48 | public boolean executeKeyEvent(KeyEvent event) { 49 | if (this.swipePageEnabled) { 50 | return super.executeKeyEvent(event); 51 | } 52 | 53 | return false; 54 | } 55 | 56 | @Override 57 | public void setCurrentItem(int item) { 58 | super.setCurrentItem(item, this.swipePageEnabled); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /android/widgets/src/main/java/org/nativescript/widgets/VerticalScrollView.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package org.nativescript.widgets; 5 | 6 | import org.nativescript.widgets.HorizontalScrollView.SavedState; 7 | import android.content.Context; 8 | import android.graphics.Rect; 9 | import android.os.Parcelable; 10 | import android.util.AttributeSet; 11 | import android.view.MotionEvent; 12 | import android.view.View; 13 | import android.view.ViewGroup; 14 | import android.widget.FrameLayout; 15 | import android.widget.ScrollView; 16 | 17 | /** 18 | * @author hhristov 19 | * 20 | */ 21 | public class VerticalScrollView extends ScrollView { 22 | 23 | private final Rect mTempRect = new Rect(); 24 | 25 | private int contentMeasuredWidth = 0; 26 | private int contentMeasuredHeight = 0; 27 | private int scrollableLength = 0; 28 | private SavedState mSavedState; 29 | private boolean isFirstLayout = true; 30 | private boolean scrollEnabled = true; 31 | 32 | /** 33 | * True when the layout has changed but the traversal has not come through yet. 34 | * Ideally the view hierarchy would keep track of this for us. 35 | */ 36 | private boolean mIsLayoutDirty = true; 37 | 38 | /** 39 | * The child to give focus to in the event that a child has requested focus while the 40 | * layout is dirty. This prevents the scroll from being wrong if the child has not been 41 | * laid out before requesting focus. 42 | */ 43 | private View mChildToScrollTo = null; 44 | 45 | public VerticalScrollView(Context context) { 46 | super(context); 47 | } 48 | 49 | public int getScrollableLength() { 50 | return this.scrollableLength; 51 | } 52 | 53 | public boolean getScrollEnabled() { 54 | return this.scrollEnabled; 55 | } 56 | 57 | public void setScrollEnabled(boolean value) { 58 | this.scrollEnabled = value; 59 | } 60 | 61 | @Override 62 | public boolean onInterceptTouchEvent(MotionEvent ev) { 63 | // Do nothing with intercepted touch events if we are not scrollable 64 | if (!this.scrollEnabled) { 65 | return false; 66 | } 67 | 68 | return super.onInterceptTouchEvent(ev); 69 | } 70 | 71 | @Override 72 | public boolean onTouchEvent(MotionEvent ev) { 73 | if (!this.scrollEnabled && ev.getAction() == MotionEvent.ACTION_DOWN) { 74 | return false; 75 | } 76 | 77 | return super.onTouchEvent(ev); 78 | } 79 | 80 | @Override 81 | public void requestLayout() { 82 | this.mIsLayoutDirty = true; 83 | super.requestLayout(); 84 | } 85 | 86 | @Override 87 | protected CommonLayoutParams generateDefaultLayoutParams() { 88 | return new CommonLayoutParams(); 89 | } 90 | 91 | /** 92 | * {@inheritDoc} 93 | */ 94 | @Override 95 | public CommonLayoutParams generateLayoutParams(AttributeSet attrs) { 96 | return new CommonLayoutParams(); 97 | } 98 | 99 | /** 100 | * {@inheritDoc} 101 | */ 102 | @Override 103 | protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { 104 | return p instanceof CommonLayoutParams; 105 | } 106 | 107 | @Override 108 | protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams from) { 109 | if (from instanceof CommonLayoutParams) 110 | return new CommonLayoutParams((CommonLayoutParams)from); 111 | 112 | if (from instanceof FrameLayout.LayoutParams) 113 | return new CommonLayoutParams((FrameLayout.LayoutParams)from); 114 | 115 | if (from instanceof ViewGroup.MarginLayoutParams) 116 | return new CommonLayoutParams((ViewGroup.MarginLayoutParams)from); 117 | 118 | return new CommonLayoutParams(from); 119 | } 120 | 121 | @Override 122 | public void requestChildFocus(View child, View focused) { 123 | if (!this.mIsLayoutDirty) { 124 | this.scrollToChild(focused); 125 | } 126 | else { 127 | // The child may not be laid out yet, we can't compute the scroll yet 128 | this.mChildToScrollTo = focused; 129 | } 130 | super.requestChildFocus(child, focused); 131 | } 132 | 133 | @Override 134 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 135 | CommonLayoutParams.adjustChildrenLayoutParams(this, widthMeasureSpec, heightMeasureSpec); 136 | 137 | // Don't call measure because it will measure content twice. 138 | // ScrollView is expected to have single child so we measure only the first child. 139 | View child = this.getChildCount() > 0 ? this.getChildAt(0) : null; 140 | if (child == null) { 141 | this.scrollableLength = 0; 142 | this.contentMeasuredWidth = 0; 143 | this.contentMeasuredHeight = 0; 144 | this.setPadding(0, 0, 0, 0); 145 | } 146 | else { 147 | CommonLayoutParams.measureChild(child, widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); 148 | this.contentMeasuredWidth = CommonLayoutParams.getDesiredWidth(child); 149 | this.contentMeasuredHeight = CommonLayoutParams.getDesiredHeight(child); 150 | 151 | // Android ScrollView does not account to child margins so we set them as paddings. Otherwise you can never scroll to bottom. 152 | CommonLayoutParams lp = (CommonLayoutParams)child.getLayoutParams(); 153 | this.setPadding(lp.leftMargin, lp.topMargin, lp.rightMargin, lp.bottomMargin); 154 | } 155 | 156 | // Don't add in our paddings because they are already added as child margins. (we will include them twice if we add them). 157 | // check the previous line - this.setPadding(lp.leftMargin, lp.topMargin, lp.rightMargin, lp.bottomMargin); 158 | // this.contentMeasuredWidth += this.getPaddingLeft() + this.getPaddingRight(); 159 | // this.contentMeasuredHeight += this.getPaddingTop() + this.getPaddingBottom(); 160 | 161 | // Check against our minimum height 162 | this.contentMeasuredWidth = Math.max(this.contentMeasuredWidth, this.getSuggestedMinimumWidth()); 163 | this.contentMeasuredHeight = Math.max(this.contentMeasuredHeight, this.getSuggestedMinimumHeight()); 164 | 165 | int widthSizeAndState = resolveSizeAndState(this.contentMeasuredWidth, widthMeasureSpec, 0); 166 | int heightSizeAndState = resolveSizeAndState(this.contentMeasuredHeight, heightMeasureSpec, 0); 167 | 168 | this.setMeasuredDimension(widthSizeAndState, heightSizeAndState); 169 | } 170 | 171 | @Override 172 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 173 | int childHeight = 0; 174 | if (this.getChildCount() > 0) { 175 | View child = this.getChildAt(0); 176 | childHeight = child.getMeasuredHeight(); 177 | 178 | int width = right - left; 179 | int height = bottom - top; 180 | 181 | this.scrollableLength = this.contentMeasuredHeight - height; 182 | CommonLayoutParams.layoutChild(child, 0, 0, width, Math.max(this.contentMeasuredHeight, height)); 183 | this.scrollableLength = Math.max(0, this.scrollableLength); 184 | } 185 | 186 | this.mIsLayoutDirty = false; 187 | // Give a child focus if it needs it 188 | if (this.mChildToScrollTo != null && HorizontalScrollView.isViewDescendantOf(this.mChildToScrollTo, this)) { 189 | this.scrollToChild(this.mChildToScrollTo); 190 | } 191 | 192 | this.mChildToScrollTo = null; 193 | 194 | int scrollX = this.getScrollX(); 195 | int scrollY = this.getScrollY(); 196 | if (this.isFirstLayout) { 197 | this.isFirstLayout = false; 198 | 199 | final int scrollRange = Math.max(0, childHeight - (bottom - top - this.getPaddingTop() - this.getPaddingBottom())); 200 | if (this.mSavedState != null) { 201 | scrollY = mSavedState.scrollPosition; 202 | mSavedState = null; 203 | } 204 | 205 | // Don't forget to clamp 206 | if (scrollY > scrollRange) { 207 | scrollY = scrollRange; 208 | } else if (scrollY < 0) { 209 | scrollY = 0; 210 | } 211 | } 212 | 213 | // Calling this with the present values causes it to re-claim them 214 | this.scrollTo(scrollX, scrollY); 215 | 216 | CommonLayoutParams.restoreOriginalParams(this); 217 | } 218 | 219 | @Override 220 | protected void onAttachedToWindow() { 221 | super.onAttachedToWindow(); 222 | this.isFirstLayout = true; 223 | } 224 | 225 | @Override 226 | protected void onDetachedFromWindow() { 227 | super.onDetachedFromWindow(); 228 | this.isFirstLayout = true; 229 | } 230 | 231 | @Override 232 | protected void onRestoreInstanceState(Parcelable state) { 233 | SavedState ss = (SavedState) state; 234 | super.onRestoreInstanceState(ss.getSuperState()); 235 | this.mSavedState = ss; 236 | this.requestLayout(); 237 | } 238 | 239 | @Override 240 | protected Parcelable onSaveInstanceState() { 241 | Parcelable superState = super.onSaveInstanceState(); 242 | SavedState ss = new SavedState(superState); 243 | ss.scrollPosition = this.getScrollY(); 244 | return ss; 245 | } 246 | 247 | private void scrollToChild(View child) { 248 | child.getDrawingRect(mTempRect); 249 | 250 | /* Offset from child's local coordinates to ScrollView coordinates */ 251 | offsetDescendantRectToMyCoords(child, mTempRect); 252 | 253 | int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect); 254 | if (scrollDelta != 0) { 255 | this.scrollBy(scrollDelta, 0); 256 | } 257 | } 258 | } -------------------------------------------------------------------------------- /android/widgets/src/main/java/org/nativescript/widgets/WrapLayout.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | */ 4 | package org.nativescript.widgets; 5 | 6 | import java.util.ArrayList; 7 | import android.content.Context; 8 | import android.view.View; 9 | 10 | /** 11 | * @author hhristov 12 | * 13 | */ 14 | public class WrapLayout extends LayoutBase { 15 | 16 | private int _itemWidth = -1; 17 | private int _itemHeight = -1; 18 | private Orientation _orientation = Orientation.horizontal; 19 | private ArrayList _lengths = new ArrayList(); 20 | 21 | public WrapLayout(Context context) { 22 | super(context); 23 | } 24 | 25 | public Orientation getOrientation() { 26 | return this._orientation; 27 | } 28 | public void setOrientation(Orientation value) { 29 | this._orientation = value; 30 | this.requestLayout(); 31 | } 32 | 33 | public int getItemWidth() { 34 | return this._itemWidth; 35 | } 36 | public void setItemWidth(int value) { 37 | this._itemWidth = value; 38 | this.requestLayout(); 39 | } 40 | 41 | public int getItemHeight() { 42 | return this._itemHeight; 43 | } 44 | public void setItemHeight(int value) { 45 | this._itemHeight = value; 46 | this.requestLayout(); 47 | } 48 | 49 | private static int getViewMeasureSpec(int parentMode, int parentLength, int itemLength) { 50 | if (itemLength > 0) { 51 | return MeasureSpec.makeMeasureSpec(itemLength, MeasureSpec.EXACTLY); 52 | } 53 | else if (parentMode == MeasureSpec.UNSPECIFIED) { 54 | return MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); 55 | } 56 | else { 57 | return MeasureSpec.makeMeasureSpec(parentLength, MeasureSpec.AT_MOST); 58 | } 59 | } 60 | 61 | private int getDesiredWidth(View child) { 62 | if (this._itemWidth > 0) { 63 | return this._itemWidth; 64 | } 65 | 66 | // Add margins because layoutChild will subtract them. 67 | return CommonLayoutParams.getDesiredWidth(child); 68 | } 69 | 70 | private int getDesiredHeight(View child) { 71 | if (this._itemHeight > 0) { 72 | return this._itemHeight; 73 | } 74 | 75 | // Add margins because layoutChild will subtract them. 76 | return CommonLayoutParams.getDesiredHeight(child); 77 | } 78 | 79 | @Override 80 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 81 | CommonLayoutParams.adjustChildrenLayoutParams(this, widthMeasureSpec, heightMeasureSpec); 82 | 83 | int measureWidth = 0; 84 | int measureHeight = 0; 85 | 86 | boolean isVertical = this._orientation == Orientation.vertical; 87 | int verticalPadding = this.getPaddingTop() + this.getPaddingBottom(); 88 | int horizontalPadding = this.getPaddingLeft() + this.getPaddingRight(); 89 | 90 | int widthMode = MeasureSpec.getMode(widthMeasureSpec); 91 | int heightMode = MeasureSpec.getMode(heightMeasureSpec); 92 | 93 | int availableWidth = widthMode == MeasureSpec.UNSPECIFIED ? Integer.MAX_VALUE : MeasureSpec.getSize(widthMeasureSpec) - horizontalPadding; 94 | int availableHeight = heightMode == MeasureSpec.UNSPECIFIED ? Integer.MAX_VALUE : MeasureSpec.getSize(heightMeasureSpec) - verticalPadding; 95 | 96 | int childWidthMeasureSpec = getViewMeasureSpec(widthMode, availableWidth, this._itemWidth); 97 | int childHeightMeasureSpec = getViewMeasureSpec(heightMode, availableHeight, this._itemHeight); 98 | 99 | int remainingWidth = availableWidth; 100 | int remainingHeight = availableHeight; 101 | 102 | this._lengths.clear(); 103 | int rowOrColumn = 0; 104 | int maxLength = 0; 105 | 106 | for (int i = 0, count = this.getChildCount(); i < count; i++) { 107 | View child = this.getChildAt(i); 108 | if (child.getVisibility() == View.GONE) { 109 | continue; 110 | } 111 | 112 | CommonLayoutParams.measureChild(child, childWidthMeasureSpec, childHeightMeasureSpec); 113 | final int childMeasuredWidth = this.getDesiredWidth(child); 114 | final int childMeasuredHeight = this.getDesiredHeight(child); 115 | final boolean isFirst = this._lengths.size() <= rowOrColumn; 116 | 117 | if (isVertical) { 118 | if (childMeasuredHeight > remainingHeight) { 119 | rowOrColumn++; 120 | maxLength = Math.max(maxLength, measureHeight); 121 | measureHeight = childMeasuredHeight; 122 | remainingHeight = availableHeight - childMeasuredHeight; 123 | this._lengths.add(isFirst ? rowOrColumn - 1 : rowOrColumn, childMeasuredWidth); 124 | } 125 | else { 126 | remainingHeight -= childMeasuredHeight; 127 | measureHeight += childMeasuredHeight; 128 | } 129 | } 130 | else { 131 | if (childMeasuredWidth > remainingWidth) { 132 | rowOrColumn++; 133 | maxLength = Math.max(maxLength, measureWidth); 134 | measureWidth = childMeasuredWidth; 135 | remainingWidth = availableWidth - childMeasuredWidth; 136 | this._lengths.add(isFirst ? rowOrColumn - 1 : rowOrColumn, childMeasuredHeight); 137 | } 138 | else { 139 | remainingWidth -= childMeasuredWidth; 140 | measureWidth += childMeasuredWidth; 141 | } 142 | } 143 | 144 | if(isFirst) { 145 | this._lengths.add(rowOrColumn, isVertical ? childMeasuredWidth : childMeasuredHeight); 146 | } 147 | else { 148 | this._lengths.set(rowOrColumn, Math.max(this._lengths.get(rowOrColumn), isVertical ? childMeasuredWidth : childMeasuredHeight)); 149 | } 150 | } 151 | 152 | if (isVertical) { 153 | measureHeight = Math.max(maxLength, measureHeight); 154 | for (int i = 0, count = this._lengths.size(); i < count; i++) { 155 | measureWidth += this._lengths.get(i); 156 | } 157 | } 158 | else { 159 | measureWidth = Math.max(maxLength, measureWidth); 160 | for (int i = 0, count = this._lengths.size(); i < count; i++) { 161 | measureHeight += this._lengths.get(i); 162 | } 163 | } 164 | 165 | // Add in our padding 166 | measureWidth += horizontalPadding; 167 | measureHeight += verticalPadding; 168 | 169 | // Check against our minimum sizes 170 | measureWidth = Math.max(measureWidth, this.getSuggestedMinimumWidth()); 171 | measureHeight = Math.max(measureHeight, this.getSuggestedMinimumHeight()); 172 | 173 | int widthSizeAndState = resolveSizeAndState(measureWidth, widthMeasureSpec, 0); 174 | int heightSizeAndState = resolveSizeAndState(measureHeight, heightMeasureSpec, 0); 175 | 176 | this.setMeasuredDimension(widthSizeAndState, heightSizeAndState); 177 | } 178 | 179 | @Override 180 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 181 | boolean isVertical = this._orientation == Orientation.vertical; 182 | int paddingLeft = this.getPaddingLeft(); 183 | int paddingRight = this.getPaddingRight(); 184 | int paddingTop = this.getPaddingTop(); 185 | int paddingBottom = this.getPaddingBottom(); 186 | 187 | int childLeft = paddingLeft; 188 | int childTop = paddingTop; 189 | int childrenLength = isVertical ? bottom - top - paddingBottom : right - left - paddingRight; 190 | 191 | int rowOrColumn = 0; 192 | for (int i = 0, count = this.getChildCount(); i < count; i++) { 193 | View child = this.getChildAt(i); 194 | if (child.getVisibility() == View.GONE) { 195 | continue; 196 | } 197 | 198 | int childWidth = this.getDesiredWidth(child); 199 | int childHeight = this.getDesiredHeight(child); 200 | 201 | int length = this._lengths.get(rowOrColumn); 202 | if (isVertical) { 203 | childWidth = length; 204 | final boolean isFirst = childTop == paddingTop; 205 | if (childTop + childHeight > childrenLength) { 206 | // Move to top. 207 | childTop = paddingTop; 208 | 209 | if (!isFirst){ 210 | // Move to right with current column width. 211 | childLeft += length; 212 | } 213 | 214 | // Move to next column. 215 | rowOrColumn++; 216 | 217 | // Take respective column width. 218 | childWidth = this._lengths.get(isFirst ? rowOrColumn - 1 : rowOrColumn); 219 | } 220 | } 221 | else { 222 | childHeight = length; 223 | final boolean isFirst = childLeft == paddingLeft; 224 | if (childLeft + childWidth > childrenLength) { 225 | // Move to left. 226 | childLeft = paddingLeft; 227 | 228 | if (!isFirst) { 229 | // Move to bottom with current row height. 230 | childTop += length; 231 | } 232 | 233 | // Move to next row. 234 | rowOrColumn++; 235 | 236 | // Take respective row height. 237 | childHeight = this._lengths.get(isFirst ? rowOrColumn - 1 : rowOrColumn); 238 | } 239 | } 240 | 241 | CommonLayoutParams.layoutChild(child, childLeft, childTop, childLeft + childWidth, childTop + childHeight); 242 | 243 | if (isVertical) { 244 | // Move next child Top position to bottom. 245 | childTop += childHeight; 246 | } 247 | else { 248 | // Move next child Left position to right. 249 | childLeft += childWidth; 250 | } 251 | } 252 | 253 | CommonLayoutParams.restoreOriginalParams(this); 254 | } 255 | } -------------------------------------------------------------------------------- /android/widgets/src/main/java/org/nativescript/widgets/image/BitmapOwner.java: -------------------------------------------------------------------------------- 1 | package org.nativescript.widgets.image; 2 | 3 | import android.graphics.Bitmap; 4 | import android.graphics.drawable.Drawable; 5 | 6 | /** 7 | * Created by hhristov on 4/18/17. 8 | */ 9 | 10 | public interface BitmapOwner { 11 | void setBitmap(Bitmap value); 12 | void setDrawable(Drawable asyncDrawable); 13 | Drawable getDrawable(); 14 | 15 | } 16 | -------------------------------------------------------------------------------- /build.android.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "Set exit on simple errors" 4 | set -e 5 | 6 | echo "Use dumb gradle terminal" 7 | export TERM=dumb 8 | 9 | echo "Clean dist" 10 | rm -rf dist 11 | mkdir dist 12 | mkdir dist/package 13 | mkdir dist/package/platforms 14 | 15 | echo "Build android" 16 | mkdir dist/package/platforms/android 17 | cd android 18 | ./gradlew --quiet assembleRelease 19 | cd .. 20 | cp android/widgets/build/outputs/aar/widgets-release.aar dist/package/platforms/android/widgets-release.aar 21 | 22 | echo "Copy NPM artefacts" 23 | cp LICENSE dist/package/LICENSE 24 | cp README.md dist/package/README.md 25 | cp package.json dist/package/package.json 26 | if [ "$1" ] 27 | then 28 | echo "Suffix package.json's version with tag: $1" 29 | sed -i.bak 's/\(\"version\"\:[[:space:]]*\"[^\"]*\)\"/\1-'$1'"/g' ./dist/package/package.json 30 | fi 31 | 32 | echo "NPM pack" 33 | cd dist/package 34 | PACKAGE="$(npm pack)" 35 | cd ../.. 36 | mv dist/package/$PACKAGE dist/$PACKAGE 37 | echo "Output: dist/$PACKAGE" 38 | -------------------------------------------------------------------------------- /build.ios.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "Set exit on simple errors" 4 | set -e 5 | 6 | echo "Use dumb terminal" 7 | export TERM=dumb 8 | 9 | echo "Clean dist" 10 | rm -rf dist 11 | mkdir dist 12 | mkdir dist/package 13 | mkdir dist/package/platforms 14 | 15 | echo "Build iOS" 16 | mkdir dist/package/platforms/ios 17 | cd ios 18 | ./build.sh 19 | cd .. 20 | cp -r ios/TNSWidgets/build/TNSWidgets.framework dist/package/platforms/ios/TNSWidgets.framework 21 | 22 | echo "Copy NPM artefacts" 23 | cp LICENSE dist/package/LICENSE 24 | cp README.md dist/package/README.md 25 | cp package.json dist/package/package.json 26 | if [ "$1" ] 27 | then 28 | echo "Suffix package.json's version with tag: $1" 29 | sed -i.bak 's/\(\"version\"\:[[:space:]]*\"[^\"]*\)\"/\1-'$1'"/g' ./dist/package/package.json 30 | fi 31 | 32 | echo "NPM pack" 33 | cd dist/package 34 | PACKAGE="$(npm pack)" 35 | cd ../.. 36 | mv dist/package/$PACKAGE dist/$PACKAGE 37 | echo "Output: dist/$PACKAGE" 38 | 39 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "Set exit on simple errors" 4 | set -e 5 | 6 | echo "Use dumb gradle terminal" 7 | export TERM=dumb 8 | 9 | echo "Clean dist" 10 | rm -rf dist 11 | mkdir dist 12 | mkdir dist/package 13 | mkdir dist/package/platforms 14 | 15 | echo "Build android" 16 | mkdir dist/package/platforms/android 17 | cd android 18 | ./gradlew --quiet assembleRelease 19 | cd .. 20 | cp android/widgets/build/outputs/aar/widgets-release.aar dist/package/platforms/android/widgets-release.aar 21 | 22 | echo "Build iOS" 23 | mkdir dist/package/platforms/ios 24 | cd ios 25 | ./build.sh 26 | cd .. 27 | cp -r ios/TNSWidgets/build/TNSWidgets.framework dist/package/platforms/ios/TNSWidgets.framework 28 | 29 | echo "Copy NPM artefacts" 30 | cp LICENSE dist/package/LICENSE 31 | cp README.md dist/package/README.md 32 | cp package.json dist/package/package.json 33 | if [ "$1" ] 34 | then 35 | echo "Suffix package.json's version with tag: $1" 36 | sed -i.bak 's/\(\"version\"\:[[:space:]]*\"[^\"]*\)\"/\1-'$1'"/g' ./dist/package/package.json 37 | fi 38 | 39 | echo "NPM pack" 40 | cd dist/package 41 | PACKAGE="$(npm pack)" 42 | cd ../.. 43 | mv dist/package/$PACKAGE dist/$PACKAGE 44 | echo "Output: dist/$PACKAGE" 45 | 46 | -------------------------------------------------------------------------------- /ios/README.md: -------------------------------------------------------------------------------- 1 | ### iOS 2 | 3 | The `TNSWidgets` directory contains a Xcode project. 4 | 5 | ### How to open? 6 | * In Xcode choose: File -> Open 7 | * Navigate to `tns-core-modules-widgets/ios/TNSWidgetes/` folder 8 | * On the left side of the screen choose the Project navigator and select `TNSWidgets` 9 | -------------------------------------------------------------------------------- /ios/TNSWidgets/TNSProcess.h: -------------------------------------------------------------------------------- 1 | // 2 | // TNSProcess.h 3 | // TNSWidgets 4 | // 5 | // Created by Panayot Cankov on 15/05/2017. 6 | // Copyright © 2017 Telerik A D. All rights reserved. 7 | // 8 | 9 | #ifndef TNSProcess_h 10 | #define TNSProcess_h 11 | 12 | #import 13 | 14 | /** 15 | * Get the milliseconds since the process started. 16 | */ 17 | double __tns_uptime(); 18 | 19 | /** 20 | * Provides access to NSLog. The runtime implementation of console.log is filtered in release. 21 | * We rarely need to log in release but in cases such as when logging app startup times in release, 22 | * this will be convenient shortcut to NSLog, NSLog is not exposed. 23 | * 24 | * Please note the {N} CLI may be filtering app output, prefixing the message with "CONSOLE LOG" 25 | * will make the logs visible in "tns run ios --release" builds. 26 | */ 27 | void __nslog(NSString* message); 28 | 29 | #endif /* TNSProcess_h */ 30 | -------------------------------------------------------------------------------- /ios/TNSWidgets/TNSProcess.m: -------------------------------------------------------------------------------- 1 | // 2 | // TNSProcess.c 3 | // TNSWidgets 4 | // 5 | // Created by Panayot Cankov on 15/05/2017. 6 | // Copyright © 2017 Telerik A D. All rights reserved. 7 | // 8 | 9 | #include "TNSProcess.h" 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | double __tns_uptime() { 16 | pid_t pid = [[NSProcessInfo processInfo] processIdentifier]; 17 | int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, pid }; 18 | struct kinfo_proc proc; 19 | size_t size = sizeof(proc); 20 | sysctl(mib, 4, &proc, &size, NULL, 0); 21 | 22 | struct timeval current; 23 | gettimeofday(¤t, NULL); 24 | 25 | return (double)(current.tv_sec - proc.kp_proc.p_starttime.tv_sec) * 1000.0 + (double)(current.tv_usec - proc.kp_proc.p_starttime.tv_usec) / 1000.0; 26 | } 27 | 28 | void __nslog(NSString* message) { 29 | NSLog(@"%@", message); 30 | } 31 | -------------------------------------------------------------------------------- /ios/TNSWidgets/TNSWidgets.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/TNSWidgets/TNSWidgets.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BuildSystemType 6 | Original 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/TNSWidgets/TNSWidgets.xcodeproj/project.xcworkspace/xcuserdata/cankov.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NativeScript/tns-core-modules-widgets/6e1fbfa70801c808f2557468bf2ce656c96d3ecc/ios/TNSWidgets/TNSWidgets.xcodeproj/project.xcworkspace/xcuserdata/cankov.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /ios/TNSWidgets/TNSWidgets.xcodeproj/xcuserdata/cankov.xcuserdatad/xcschemes/TNSWidgets.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 65 | 71 | 72 | 73 | 74 | 75 | 76 | 82 | 83 | 89 | 90 | 91 | 92 | 94 | 95 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /ios/TNSWidgets/TNSWidgets.xcodeproj/xcuserdata/cankov.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | TNSWidgets.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | F98F5CAE1CD0EFEA00978308 16 | 17 | primary 18 | 19 | 20 | F98F5CB81CD0EFEA00978308 21 | 22 | primary 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /ios/TNSWidgets/TNSWidgets/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/TNSWidgets/TNSWidgets/NSObject+PropertyBag.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSObject+PropertyBag.h 3 | // TNSWidgets 4 | // 5 | // Created by Manol Donev on 21.08.18. 6 | // Copyright © 2018 Telerik A D. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | 12 | @interface NSObject (PropertyBag) 13 | 14 | - (id) propertyValueForKey:(NSString*) key; 15 | - (void) setPropertyValue:(id) value forKey:(NSString*) key; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /ios/TNSWidgets/TNSWidgets/NSObject+PropertyBag.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSObject+PropertyBag.m 3 | // TNSWidgets 4 | // 5 | // Created by Manol Donev on 21.08.18. 6 | // Copyright © 2018 Telerik A D. All rights reserved. 7 | // 8 | 9 | #import "NSObject+PropertyBag.h" 10 | #import "NSObject+Swizzling.h" 11 | 12 | 13 | @implementation NSObject (PropertyBag) 14 | 15 | + (void) load{ 16 | [self loadPropertyBag]; 17 | } 18 | 19 | + (void) loadPropertyBag{ 20 | @autoreleasepool { 21 | static dispatch_once_t onceToken; 22 | dispatch_once(&onceToken, ^{ 23 | const SEL deallocSelector = NSSelectorFromString(@"dealloc"); //ARC forbids use of 'dealloc' in a @selector 24 | [self swizzleInstanceMethodWithOriginalSelector:deallocSelector fromClass:self.class withSwizzlingSelector:@selector(propertyBag_dealloc)]; 25 | }); 26 | } 27 | } 28 | 29 | __strong NSMutableDictionary *_propertyBagHolder; // Properties for every class will go in this property bag 30 | - (id) propertyValueForKey:(NSString*) key { 31 | return [[self propertyBag] valueForKey:key]; 32 | } 33 | 34 | - (void) setPropertyValue:(id) value forKey:(NSString*) key { 35 | [[self propertyBag] setValue:value forKey:key]; 36 | } 37 | 38 | - (NSMutableDictionary*) propertyBag { 39 | if (_propertyBagHolder == nil) _propertyBagHolder = [[NSMutableDictionary alloc] initWithCapacity:100]; 40 | NSMutableDictionary *propBag = [_propertyBagHolder valueForKey:[[NSString alloc] initWithFormat:@"%p", self]]; 41 | if (propBag == nil) { 42 | propBag = [NSMutableDictionary dictionary]; 43 | [self setPropertyBag:propBag]; 44 | } 45 | 46 | return propBag; 47 | } 48 | 49 | - (void) setPropertyBag:(NSDictionary*) propertyBag { 50 | if (_propertyBagHolder == nil) { 51 | _propertyBagHolder = [[NSMutableDictionary alloc] initWithCapacity:100]; 52 | } 53 | 54 | [_propertyBagHolder setValue:propertyBag forKey:[[NSString alloc] initWithFormat:@"%p", self]]; 55 | } 56 | 57 | - (void)propertyBag_dealloc { 58 | [self setPropertyBag:nil]; 59 | [self propertyBag_dealloc]; // swizzled 60 | } 61 | 62 | @end 63 | -------------------------------------------------------------------------------- /ios/TNSWidgets/TNSWidgets/NSObject+Swizzling.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSObject+Swizzling.h 3 | // TNSWidgets 4 | // 5 | // Created by Manol Donev on 21.08.18. 6 | // Copyright © 2018 Telerik A D. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | 13 | @interface NSObject (Swizzling) 14 | 15 | + (void)swizzleInstanceMethodWithOriginalSelector:(SEL)originalSelector fromClass:(Class)classContainigOriginalSel withSwizzlingSelector:(SEL)swizzlingSelector; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /ios/TNSWidgets/TNSWidgets/NSObject+Swizzling.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSObject+Swizzling.m 3 | // TNSWidgets 4 | // 5 | // Created by Manol Donev on 21.08.18. 6 | // Copyright © 2018 Telerik A D. All rights reserved. 7 | // 8 | 9 | #import "NSObject+Swizzling.h" 10 | 11 | @implementation NSObject (Swizzling) 12 | 13 | #pragma mark - Method Swizzling 14 | 15 | + (void)swizzleInstanceMethodWithOriginalSelector:(SEL)originalSelector 16 | fromClass:(Class)classContainigOriginalSel 17 | withSwizzlingSelector:(SEL)swizzlingSelector { 18 | Method originalMethod = class_getInstanceMethod(classContainigOriginalSel, originalSelector); 19 | Method swizzlingMethod = class_getInstanceMethod(self.class, swizzlingSelector); 20 | [self swizzleMethodWithOriginalSelector:originalSelector 21 | originalMethod:originalMethod 22 | fromClass:classContainigOriginalSel 23 | withSwizzlingSelector:swizzlingSelector 24 | swizzlingMethod:swizzlingMethod]; 25 | } 26 | 27 | //MARK: Utilities 28 | 29 | + (void)swizzleMethodWithOriginalSelector:(SEL)originalSelector 30 | originalMethod:(Method)originalMethod 31 | fromClass:(Class)classContainigOriginalSel 32 | withSwizzlingSelector:(SEL)swizzlingSelector 33 | swizzlingMethod:(Method)swizzlingMethod { 34 | if (self == classContainigOriginalSel) { 35 | BOOL didAddMethod = class_addMethod(classContainigOriginalSel, 36 | originalSelector, 37 | method_getImplementation(swizzlingMethod), 38 | method_getTypeEncoding(swizzlingMethod)); 39 | 40 | if (didAddMethod) { 41 | class_replaceMethod(self.class, 42 | swizzlingSelector, 43 | method_getImplementation(originalMethod), 44 | method_getTypeEncoding(originalMethod)); 45 | } else { 46 | method_exchangeImplementations(originalMethod, swizzlingMethod); 47 | } 48 | 49 | return; 50 | } 51 | 52 | class_addMethod(classContainigOriginalSel, 53 | swizzlingSelector, 54 | method_getImplementation(originalMethod), 55 | method_getTypeEncoding(originalMethod)); 56 | 57 | class_replaceMethod(classContainigOriginalSel, 58 | originalSelector, 59 | method_getImplementation(swizzlingMethod), 60 | method_getTypeEncoding(swizzlingMethod)); 61 | 62 | class_replaceMethod(self, 63 | swizzlingSelector, 64 | method_getImplementation(originalMethod), 65 | method_getTypeEncoding(originalMethod)); 66 | } 67 | 68 | @end 69 | -------------------------------------------------------------------------------- /ios/TNSWidgets/TNSWidgets/TNSLabel.h: -------------------------------------------------------------------------------- 1 | // 2 | // TNSLabel.h 3 | // TNSWidgets 4 | // 5 | // Created by Hristo Hristov on 6/9/16. 6 | // Copyright © 2016 Telerik A D. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface TNSLabel : UILabel 12 | 13 | @property(nonatomic) UIEdgeInsets padding; 14 | @property(nonatomic) UIEdgeInsets borderThickness; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /ios/TNSWidgets/TNSWidgets/TNSLabel.m: -------------------------------------------------------------------------------- 1 | // 2 | // TNSLabel.m 3 | // TNSWidgets 4 | // 5 | // Created by Hristo Hristov on 6/9/16. 6 | // Copyright © 2016 Telerik A D. All rights reserved. 7 | // 8 | 9 | #import "TNSLabel.h" 10 | 11 | @implementation TNSLabel 12 | 13 | 14 | - (CGRect)textRectForBounds:(CGRect)bounds limitedToNumberOfLines:(NSInteger)numberOfLines { 15 | // UILabel.textRectForBounds:limitedToNumberOfLines: returns rect with CGSizeZero when empty 16 | if (self.text.length == 0) { 17 | return [super textRectForBounds:bounds limitedToNumberOfLines:numberOfLines]; 18 | } 19 | 20 | // 1. Subtract the insets (border thickness & padding) 21 | // 2. Calculate the original label bounds 22 | // 3. Add the insets again 23 | UIEdgeInsets insets = UIEdgeInsetsMake(self.borderThickness.top + self.padding.top, 24 | self.borderThickness.left + self.padding.left, 25 | self.borderThickness.bottom + self.padding.bottom, 26 | self.borderThickness.right + self.padding.right); 27 | 28 | CGRect rect = [super textRectForBounds:UIEdgeInsetsInsetRect(bounds, insets) limitedToNumberOfLines:numberOfLines]; 29 | 30 | UIEdgeInsets inverseInsets = UIEdgeInsetsMake(-(self.borderThickness.top + self.padding.top), 31 | -(self.borderThickness.left + self.padding.left), 32 | -(self.borderThickness.bottom + self.padding.bottom), 33 | -(self.borderThickness.right + self.padding.right)); 34 | 35 | return UIEdgeInsetsInsetRect(rect, inverseInsets); 36 | } 37 | 38 | -(void)drawTextInRect:(CGRect)rect { 39 | [super drawTextInRect:UIEdgeInsetsInsetRect(UIEdgeInsetsInsetRect(rect, self.borderThickness), self.padding)]; 40 | } 41 | 42 | @end 43 | -------------------------------------------------------------------------------- /ios/TNSWidgets/TNSWidgets/TNSWidgets.h: -------------------------------------------------------------------------------- 1 | // 2 | // TNSWidgets.h 3 | // TNSWidgets 4 | // 5 | // Created by Panayot Cankov on 4/27/16. 6 | // Copyright © 2016 Telerik A D. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for TNSWidgets. 12 | FOUNDATION_EXPORT double TNSWidgetsVersionNumber; 13 | 14 | //! Project version string for TNSWidgets. 15 | FOUNDATION_EXPORT const unsigned char TNSWidgetsVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | #import "UIImage+TNSBlocks.h" 20 | #import "UIView+PassThroughParent.h" 21 | #import "TNSLabel.h" 22 | #import "TNSProcess.h" 23 | -------------------------------------------------------------------------------- /ios/TNSWidgets/TNSWidgets/UIImage+TNSBlocks.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+UIImage_Async.h 3 | // TKImageAsync 4 | // 5 | // Created by Panayot Cankov on 4/18/16. 6 | // Copyright © 2016 Telerik A D. All rights reserved. 7 | // 8 | 9 | @interface UIImage (TNSBlocks) 10 | 11 | /** 12 | * Similar to imageNamed: however it runs on a separate queue so the UI thread is not blocked. 13 | * It also draws the UIImage in a small thumb to force decoding potentially avoiding UI hicckups when displayed. 14 | */ 15 | + (void) tns_safeDecodeImageNamed: (NSString*) name completion: (void (^) (UIImage*))callback; 16 | 17 | /** 18 | * Same as imageNamed, however calls to this method are sinchronized to be thread safe in iOS8 along with calls to tns_safeImageNamed and tns_safeDecodeImageNamed:completion: 19 | * imageNamed is thread safe in iOS 9 and later so in later versions this methods simply fallbacks to imageNamed: 20 | */ 21 | + (UIImage*) tns_safeImageNamed: (NSString*) name; 22 | 23 | + (void) tns_decodeImageWithData: (NSData*) data completion: (void (^) (UIImage*))callback; 24 | + (void) tns_decodeImageWidthContentsOfFile: (NSString*) file completion: (void (^) (UIImage*))callback; 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /ios/TNSWidgets/TNSWidgets/UIImage+TNSBlocks.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+UIImage_Async.m 3 | // TKImageAsync 4 | // 5 | // Created by Panayot Cankov on 4/18/16. 6 | // Copyright © 2016 Telerik A D. All rights reserved. 7 | // 8 | #import 9 | #import 10 | #import "UIImage+TNSBlocks.h" 11 | 12 | @implementation UIImage (TNSBlocks) 13 | 14 | static dispatch_queue_t image_queue; 15 | static NSLock* image_lock_handle; 16 | 17 | + (void) initialize { 18 | image_queue = dispatch_queue_create("org.nativescript.TNSWidgets.image", NULL); 19 | if ([[NSProcessInfo processInfo] operatingSystemVersion].majorVersion >= 9) { 20 | // UIImage imageNamed: is said to be thread safe, in iOS9 and later, in offical Apple reference. 21 | image_lock_handle = nil; 22 | } else { 23 | image_lock_handle = [NSLock new]; 24 | } 25 | } 26 | 27 | - (void) tns_forceDecode { 28 | CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); 29 | CGContextRef context = CGBitmapContextCreate(NULL, 1, 1, 8, 0, colorSpace, kCGImageAlphaPremultipliedFirst); 30 | CGContextDrawImage(context, CGRectMake(0, 0, 1, 1), [self CGImage]); 31 | CGContextRelease(context); 32 | CGColorSpaceRelease(colorSpace); 33 | } 34 | 35 | + (void) tns_safeDecodeImageNamed: (NSString*) name completion: (void (^) (UIImage*))callback { 36 | dispatch_async(image_queue, ^(void){ 37 | [image_lock_handle lock]; 38 | UIImage* image = [UIImage imageNamed: name]; 39 | [image_lock_handle unlock]; 40 | [image tns_forceDecode]; 41 | 42 | dispatch_async(dispatch_get_main_queue(), ^(void) { 43 | callback(image); 44 | }); 45 | }); 46 | } 47 | 48 | + (UIImage*) tns_safeImageNamed: (NSString*) name { 49 | [image_lock_handle lock]; 50 | UIImage* image = [UIImage imageNamed: name]; 51 | [image_lock_handle unlock]; 52 | return image; 53 | } 54 | 55 | + (void) tns_decodeImageWithData: (NSData*) data completion: (void (^) (UIImage*))callback { 56 | dispatch_async(image_queue, ^(void) { 57 | UIImage* image = [UIImage imageWithData: data]; 58 | [image tns_forceDecode]; 59 | 60 | dispatch_async(dispatch_get_main_queue(), ^(void) { 61 | callback(image); 62 | }); 63 | }); 64 | } 65 | 66 | + (void) tns_decodeImageWidthContentsOfFile: (NSString*) file completion: (void (^) (UIImage*))callback { 67 | dispatch_async(image_queue, ^(void) { 68 | UIImage* image = [UIImage imageWithContentsOfFile: file]; 69 | [image tns_forceDecode]; 70 | 71 | dispatch_async(dispatch_get_main_queue(), ^(void) { 72 | callback(image); 73 | }); 74 | }); 75 | } 76 | 77 | @end 78 | -------------------------------------------------------------------------------- /ios/TNSWidgets/TNSWidgets/UIView+PassThroughParent.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+PassThroughParent.h 3 | // TNSWidgets 4 | // 5 | // Created by Manol Donev on 21.08.18. 6 | // Copyright © 2018 Telerik A D. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | 12 | @interface UIView (PassThroughParent) 13 | 14 | - (BOOL) passThroughParent; 15 | - (void) setPassThroughParent:(BOOL) passThroughParent; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /ios/TNSWidgets/TNSWidgets/UIView+PassThroughParent.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+PassThroughParent.m 3 | // TNSWidgets 4 | // 5 | // Created by Manol Donev on 21.08.18. 6 | // Copyright © 2018 Telerik A D. All rights reserved. 7 | // 8 | 9 | #import "UIView+PassThroughParent.h" 10 | #import "NSObject+Swizzling.h" 11 | #import "NSObject+PropertyBag.h" 12 | 13 | 14 | NSString * const TLKPassThroughParentKey = @"passThroughParent"; 15 | 16 | @implementation UIView (PassThroughParent) 17 | 18 | + (void) load { 19 | [self loadHitTest]; 20 | } 21 | 22 | + (void) loadHitTest { 23 | @autoreleasepool { 24 | static dispatch_once_t onceToken; 25 | dispatch_once(&onceToken, ^{ 26 | [self swizzleInstanceMethodWithOriginalSelector:@selector(hitTest:withEvent:) fromClass:self.class withSwizzlingSelector:@selector(passThrough_hitTest:withEvent:)]; 27 | }); 28 | } 29 | } 30 | 31 | - (BOOL)passThroughParent { 32 | NSNumber *passthrough = [self propertyValueForKey:TLKPassThroughParentKey]; 33 | if (passthrough) { 34 | return passthrough.boolValue; 35 | }; 36 | 37 | return NO; 38 | } 39 | 40 | - (void)setPassThroughParent:(BOOL)passThroughParent { 41 | [self setPropertyValue:[NSNumber numberWithBool:passThroughParent] forKey:TLKPassThroughParentKey]; 42 | } 43 | 44 | - (UIView *)passThrough_hitTest:(CGPoint)point withEvent:(UIEvent *)event { 45 | UIView *hitTestView = [self passThrough_hitTest:point withEvent:event]; // swizzled 46 | if (hitTestView == self && self.passThroughParent) { 47 | hitTestView = nil; 48 | } 49 | 50 | return hitTestView; 51 | } 52 | 53 | @end 54 | -------------------------------------------------------------------------------- /ios/TNSWidgets/TNSWidgetsTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /ios/TNSWidgets/TNSWidgetsTests/TNSWidgetsTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // TNSWidgetsTests.m 3 | // TNSWidgetsTests 4 | // 5 | // Created by Panayot Cankov on 4/27/16. 6 | // Copyright © 2016 Telerik A D. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface TNSWidgetsTests : XCTestCase 12 | 13 | @end 14 | 15 | @implementation TNSWidgetsTests 16 | 17 | - (void)setUp { 18 | [super setUp]; 19 | // Put setup code here. This method is called before the invocation of each test method in the class. 20 | } 21 | 22 | - (void)tearDown { 23 | // Put teardown code here. This method is called after the invocation of each test method in the class. 24 | [super tearDown]; 25 | } 26 | 27 | - (void)testExample { 28 | // This is an example of a functional test case. 29 | // Use XCTAssert and related functions to verify your tests produce the correct results. 30 | } 31 | 32 | - (void)testPerformanceExample { 33 | // This is an example of a performance test case. 34 | [self measureBlock:^{ 35 | // Put the code you want to measure the time of here. 36 | }]; 37 | } 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /ios/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "Set exit on simple errors" 4 | set -e 5 | 6 | echo "Build for iphonesimulator" 7 | xcodebuild -project TNSWidgets/TNSWidgets.xcodeproj -sdk iphonesimulator -target TNSWidgets -configuration Release clean build CONFIGURATION_BUILD_DIR=build/Release-iphonesimulator -quiet 8 | 9 | echo "Build for iphoneos" 10 | xcodebuild -project TNSWidgets/TNSWidgets.xcodeproj -sdk iphoneos -target TNSWidgets -configuration Release clean build CONFIGURATION_BUILD_DIR=build/Release-iphoneos CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO -quiet 11 | 12 | echo "Build fat framework at TNSWidgets/build/TNSWidgets.framework" 13 | rm -rf TNSWidgets/build/TNSWidgets.framework 14 | mkdir TNSWidgets/build/TNSWidgets.framework 15 | 16 | cp -r TNSWidgets/build/Release-iphoneos/TNSWidgets.framework/Headers TNSWidgets/build/TNSWidgets.framework/Headers 17 | cp -r TNSWidgets/build/Release-iphoneos/TNSWidgets.framework/Modules TNSWidgets/build/TNSWidgets.framework/Modules 18 | cp -r TNSWidgets/build/Release-iphoneos/TNSWidgets.framework/Info.plist TNSWidgets/build/TNSWidgets.framework/Info.plist 19 | 20 | lipo -create TNSWidgets/build/Release-iphoneos/TNSWidgets.framework/TNSWidgets TNSWidgets/build/Release-iphonesimulator/TNSWidgets.framework/TNSWidgets -o TNSWidgets/build/TNSWidgets.framework/TNSWidgets 21 | file TNSWidgets/build/TNSWidgets.framework/TNSWidgets 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tns-core-modules-widgets", 3 | "version": "5.3.0", 4 | "description": "Native widgets used in the NativeScript framework.", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1" 7 | }, 8 | "repository": { 9 | "type": "git", 10 | "url": "git+https://github.com/NativeScript/android-widgets.git" 11 | }, 12 | "author": "NativeScript team", 13 | "license": "Apache-2.0", 14 | "bugs": { 15 | "url": "https://github.com/NativeScript/android-widgets/issues" 16 | }, 17 | "homepage": "https://github.com/NativeScript/android-widgets#readme", 18 | "nativescript": { 19 | "platforms": { 20 | "ios": "4.0.0", 21 | "android": "4.0.0" 22 | } 23 | } 24 | } 25 | --------------------------------------------------------------------------------