Repository: uber/artist Branch: master Commit: 000f33e4812d Files: 129 Total size: 327.5 KB Directory structure: gitextract_ob0qypit/ ├── .buildscript/ │ └── deploy_snapshot.sh ├── .editorconfig ├── .github/ │ ├── ISSUE_TEMPLATE.md │ ├── PULL_REQUEST_TEMPLATE.md │ └── workflows/ │ └── ci.yml ├── .gitignore ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE.txt ├── README.md ├── RELEASING.md ├── artist/ │ ├── build.gradle │ ├── gradle.properties │ └── src/ │ └── main/ │ ├── kotlin/ │ │ └── com/ │ │ └── uber/ │ │ └── artist/ │ │ ├── ArtistExtension.kt │ │ ├── ArtistPlugin.kt │ │ ├── ArtistTask.kt │ │ └── internal/ │ │ └── util/ │ │ └── Util.kt │ └── resources/ │ └── META-INF/ │ └── gradle-plugins/ │ └── com.uber.artist.properties ├── artist-api/ │ ├── build.gradle │ ├── gradle.properties │ └── src/ │ └── main/ │ └── kotlin/ │ └── com/ │ └── uber/ │ └── artist/ │ └── api/ │ ├── JavaTrait.kt │ ├── JavaTraitService.kt │ ├── JavaTypeNames.kt │ ├── JavaViewStencil.kt │ ├── JavaViewStencilProvider.kt │ ├── JavaViewStencilService.kt │ ├── KotlinTrait.kt │ ├── KotlinTraitService.kt │ ├── KotlinTypeNames.kt │ ├── KotlinViewStencil.kt │ ├── KotlinViewStencilProvider.kt │ ├── KotlinViewStencilService.kt │ ├── Trait.kt │ ├── TraitService.kt │ ├── TypeNames.kt │ ├── ViewStencil.kt │ ├── ViewStencilProvider.kt │ ├── ViewStencilService.kt │ └── alias/ │ └── AliasTypeNames.kt ├── artist-core/ │ ├── build.gradle │ ├── gradle.properties │ └── src/ │ ├── main/ │ │ └── kotlin/ │ │ └── com/ │ │ └── uber/ │ │ └── artist/ │ │ ├── Artist.kt │ │ ├── ArtistCodeGenerator.kt │ │ ├── FormattingFileWriter.kt │ │ ├── JavaArtistCodeGenerator.kt │ │ ├── JavaFormattingFileWriter.kt │ │ └── KotlinArtistCodeGenerator.kt │ └── test/ │ └── kotlin/ │ └── com/ │ └── uber/ │ └── artist/ │ └── ArtistTest.kt ├── artist-traits/ │ ├── build.gradle │ ├── gradle.properties │ └── src/ │ └── main/ │ └── kotlin/ │ └── com/ │ └── uber/ │ └── artist/ │ └── traits/ │ ├── JavaForegroundTrait.kt │ ├── JavaSuppressNullabilityInitializerTrait.kt │ ├── JavaVisibilityTrait.kt │ ├── KotlinForegroundTrait.kt │ ├── KotlinSuppressNullabilityInitializerTrait.kt │ └── KotlinVisibilityTrait.kt ├── artist-traits-rx/ │ ├── build.gradle │ ├── gradle.properties │ └── src/ │ └── main/ │ └── kotlin/ │ └── com/ │ └── uber/ │ └── artist/ │ └── traits/ │ └── rx/ │ ├── JavaApiHelper.kt │ ├── JavaCheckableTrait.kt │ ├── JavaRxTypeNames.kt │ ├── JavaScrollableTrait.kt │ ├── JavaTextInputTrait.kt │ ├── JavaViewTrait.kt │ ├── KotlinApiHelper.kt │ ├── KotlinCheckableTrait.kt │ ├── KotlinRxTypeNames.kt │ ├── KotlinScrollableTrait.kt │ ├── KotlinTextInputTrait.kt │ ├── KotlinViewTrait.kt │ └── config/ │ ├── ArtistRxConfig.kt │ ├── ArtistRxConfigService.kt │ ├── JavaArtistRxConfig.kt │ ├── JavaArtistRxConfigService.kt │ ├── JavaDefaultArtistRxConfig.kt │ ├── KotlinArtistRxConfig.kt │ ├── KotlinArtistRxConfigService.kt │ └── KotlinDefaultArtistRxConfig.kt ├── build.gradle ├── buildSrc/ │ ├── build.gradle │ └── settings.gradle ├── config/ │ ├── checkstyle/ │ │ ├── checkstyle-suppressions.xml │ │ ├── checkstyle-test.xml │ │ └── checkstyle.xml │ └── lint/ │ └── lint.xml ├── gradle/ │ ├── dependencies.gradle │ ├── gradle-mvn-push.gradle │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── gradlew ├── sample/ │ ├── app/ │ │ ├── build.gradle │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ ├── java/ │ │ │ └── com/ │ │ │ └── uber/ │ │ │ └── artist/ │ │ │ └── myapplication/ │ │ │ └── MainActivity.kt │ │ └── res/ │ │ ├── drawable/ │ │ │ └── divider.xml │ │ ├── layout/ │ │ │ └── activity_main.xml │ │ ├── mipmap-anydpi-v26/ │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ └── values/ │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── ic_launcher_background.xml │ │ ├── strings.xml │ │ └── styles.xml │ ├── demo/ │ │ └── java/ │ │ ├── MyButton.java │ │ ├── MyEditText.java │ │ ├── MyImageView.java │ │ ├── MyLinearLayout.java │ │ ├── MyNestedScrollView.java │ │ ├── MySwitch.java │ │ └── MyTextView.java │ ├── library/ │ │ ├── build.gradle │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ ├── java/ │ │ │ └── com/ │ │ │ └── uber/ │ │ │ └── artist/ │ │ │ └── mylibrary/ │ │ │ ├── MyUtils.java │ │ │ ├── MyView.java │ │ │ └── Signal.java │ │ └── res/ │ │ └── values/ │ │ ├── attrs_foreground_view.xml │ │ └── strings.xml │ ├── providers/ │ │ ├── build.gradle │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── com/ │ │ └── uber/ │ │ └── artist/ │ │ └── myproviders/ │ │ ├── JavaSampleRxConfig.java │ │ ├── JavaSampleTypeNames.java │ │ ├── JavaSampleViewStencilProvider.java │ │ └── trait/ │ │ └── JavaSampleTrait.java │ └── providers-kotlin/ │ ├── build.gradle │ └── src/ │ └── main/ │ └── kotlin/ │ └── com/ │ └── uber/ │ └── artist/ │ └── myproviders/ │ ├── KotlinSampleRxConfig.kt │ ├── KotlinSampleTypeNames.kt │ ├── KotlinSampleViewStencilProvider.kt │ └── trait/ │ └── KotlinSampleTrait.kt └── settings.gradle ================================================ FILE CONTENTS ================================================ ================================================ FILE: .buildscript/deploy_snapshot.sh ================================================ #!/bin/bash # # Deploy a jar, source jar, and javadoc jar to Sonatype's snapshot repo. # # Adapted from https://coderwall.com/p/9b_lfq and # http://benlimmer.com/2013/12/26/automatically-publish-javadoc-to-gh-pages-with-travis-ci/ SLUG="uber/artist" JDK="oraclejdk8" BRANCH="master" set -e if [ "$TRAVIS_REPO_SLUG" != "$SLUG" ]; then echo "Skipping snapshot deployment: wrong repository. Expected '$SLUG' but was '$TRAVIS_REPO_SLUG'." elif [ "$TRAVIS_JDK_VERSION" != "$JDK" ]; then echo "Skipping snapshot deployment: wrong JDK. Expected '$JDK' but was '$TRAVIS_JDK_VERSION'." elif [ "$TRAVIS_PULL_REQUEST" != "false" ]; then echo "Skipping snapshot deployment: was pull request." elif [ "$TRAVIS_BRANCH" != "$BRANCH" ]; then echo "Skipping snapshot deployment: wrong branch. Expected '$BRANCH' but was '$TRAVIS_BRANCH'." else echo "Deploying snapshot..." ./gradlew clean uploadArchives -Dorg.gradle.parallel=false echo "Snapshot deployed!" fi ================================================ FILE: .editorconfig ================================================ [*.{kt,kts}] # possible values: number (e.g. 2), "unset" (makes ktlint ignore indentation completely) indent_size=2 continuation_indent_size=4 insert_final_newline=true # possible values: number (e.g. 120) (package name, imports & comments are ignored), "off" max_line_length=off ================================================ FILE: .github/ISSUE_TEMPLATE.md ================================================ Thanks for using Artist. Before you create an issue, please consider the following points: - [ ] If you think you found a bug, please include a code sample that reproduces the problem. A test case that reproduces the issue is preferred. A stack trace alone is ok but may not contain enough context for us to address the issue. - [ ] Please include the library version number, including the minor and patch version, in the issue text. ================================================ FILE: .github/PULL_REQUEST_TEMPLATE.md ================================================ Thank you for contributing to Artist. Before pressing the "Create Pull Request" button, please provide the following: - [ ] A description about what and why you are contributing, even if it's trivial. - [ ] The issue number(s) or PR number(s) in the description if you are contributing in response to those. - [ ] If applicable, unit tests. ================================================ FILE: .github/workflows/ci.yml ================================================ name: CI on: [push, pull_request] jobs: build: name: JDK ${{ matrix.java_version }} runs-on: ubuntu-latest strategy: matrix: java_version: [8] steps: - name: Checkout uses: actions/checkout@v2 - name: Install JDK ${{ matrix.java_version }} uses: actions/setup-java@v2 with: distribution: 'zulu' java-version: ${{ matrix.java_version }} - name: Install Android SDK uses: malinskiy/action-android/install-sdk@release/0.1.1 - name: Gradle Wrapper Validation uses: gradle/wrapper-validation-action@v1 - name: Configure Gradle # Install Gradle Deps run: ./gradlew help - name: Build project run: ./gradlew assemble --stacktrace - name: Run tests run: ./gradlew test --stacktrace - name: Gradle Check run: ./gradlew check --stacktrace - name: Upload snapshot (main only) run: ./gradlew uploadArchives -PSONATYPE_NEXUS_USERNAME="$SONATYPE_NEXUS_USERNAME" -PSONATYPE_NEXUS_PASSWORD="$SONATYPE_NEXUS_PASSWORD" env: SONATYPE_NEXUS_USERNAME: ${{ secrets.SonatypeUsername }} SONATYPE_NEXUS_PASSWORD: ${{ secrets.SonatypePassword }} if: success() && github.ref == 'refs/heads/master' && github.event_name != 'pull_request' && matrix.java_version == '8' ================================================ FILE: .gitignore ================================================ ###OSX### .DS_Store .AppleDouble .LSOverride # Icon must ends with two \r. Icon # Thumbnails ._* # Files that might appear on external disk .Spotlight-V100 .Trashes ###Linux### *~ # KDE directory preferences .directory ###Android### # Built application files *.apk *.ap_ # Files for ART and Dalvik VM *.dex # Java class files *.class # Generated files bin/ gen/ # Gradle files .gradle/ .gradletasknamecache build/ # Local configuration file (sdk path, etc) local.properties # Proguard folder generated by Eclipse proguard/ # Lint lint-report.html lint-report_files/ lint_result.txt # Mobile Tools for Java (J2ME) .mtj.tmp/ # Package Files # *.war *.ear # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* ###IntelliJ### *.iml *.ipr *.iws .idea/ ###Eclipse### *.pydevproject .metadata tmp/ *.tmp *.bak *.swp *~.nib .settings/ .loadpath # External tool builders .externalToolBuilders/ # Locally stored "Eclipse launch configurations" *.launch # CDT-specific .cproject # PDT-specific .buildpath # sbteclipse plugin .target # TeXlipse plugin .texlipse # kotlin annotations/ ================================================ FILE: CHANGELOG.md ================================================ Changelog ========= Version 0.4.9 ------------- _2023-07-27_ * Update version upstreaming unmerged fixes Version 0.4.8 ------------- _2023-07-18_ * Fixes Rx Kotlin nulls [issue](https://github.com/uber/artist/issues/49) Version 0.4.7 ------------- _2021-02-06_ * Add support for AndroidX Version 0.4.6 ------------- _2021-02-08_ Version 0.4.5 ------------- _2018-12-19_ * **Breaking change:** API classes renamed with "Java" prefixes. Class names and AutoService annotations will need to be updated * Added Kotlin-prefixed API classes modules for generating Kotlin views Version 0.3.0 ------------- _2018-12-03_ * **Breaking change:** Project migrated to [AndroidX](https://developer.android.com/jetpack/androidx/). See the [class and package mappings](https://developer.android.com/jetpack/androidx/migrate) for help migrating Version 0.2.2 ------------- _2018-11-07_ * **Note:** This is the final version that uses the non-AndroidX Support Library * Annotate underlying `setOnClickListener(listener)` method param as `@Nullable` to match AOSP * Dependency updates including using Support Library, Kotlin, RxJava, and RxBinding Version 0.2.1 ------------- _2018-07-01_ * Separated core artist functionality into a module Version 0.2.0 ------------- _2018-04-23_ * **Breaking change:** Removed `TraitProvider` in favor of annotating with `AutoService(Trait.class)` * `ViewStencilProvider` and `ArtistRxConfig` implementations can also be annotated with `AutoService` Version 0.1.0 ------------- _2017-12-11_ * Initial release ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at mobile-open-source@uber.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] [homepage]: http://contributor-covenant.org [version]: http://contributor-covenant.org/version/1/4/ ================================================ FILE: CONTRIBUTING.md ================================================ Contributing to Uber's Android Template ======================= Uber welcomes contributions of all kinds and sizes. This includes everything from from simple bug reports to large features. Workflow -------- We love GitHub issues! For small feature requests, an issue first proposing it for discussion or demo implementation in a PR suffice. For big features, please open an issue so that we can agree on the direction, and hopefully avoid investing a lot of time on a feature that might need reworking. Small pull requests for things like typos, bug fixes, etc are always welcome. DOs and DON'Ts -------------- * DO follow our [coding style](https://github.com/uber/java-code-styles) * DO include tests when adding new features. When fixing bugs, start with adding a test that highlights how the current behavior is broken. * DO keep the discussions focused. When a new or related topic comes up it's often better to create new issue than to side track the discussion. * DON'T submit PRs that alter licensing related files or headers. If you believe there's a problem with them, file an issue and we'll be happy to discuss it. Guiding Principles ------------------ * We allow anyone to participate in our projects. Tasks can be carried out by anyone that demonstrates the capability to complete them * Always be respectful of one another. Assume the best in others and act with empathy at all times * Collaborate closely with individuals maintaining the project or experienced users. Getting ideas out in the open and seeing a proposal before it's a pull request helps reduce redundancy and ensures we're all connected to the decision making process ================================================ FILE: LICENSE.txt ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: README.md ================================================ # Artist [![Build Status](https://travis-ci.org/uber/artist.svg?branch=master)](https://travis-ci.org/uber/artist) As Android apps grow, providing common features and consistent functionality across Views becomes challenging. Typically, this results in copy-pasting features across views, monolithic classes, or complicated inheritance trees. Artist is a highly-extensible platform for creating and maintaining an app’s base set of Android views. ## Overview Artist is a Gradle plugin written in Kotlin that generates a base set of Android `View`s. Artist-generated views are created using a stencil and trait system. Each view type is declared with a single stencil, which is comprised of a set of traits. All of this comes together to create an easily maintainable system of stencils and traits. *Stencils*: A `Stencil` defines a View class to be generated. Each `Stencil` has some properties that can be configured and declares a set of traits they exhibit. *Traits*: A `Trait` defines the new functionality that should be added to a view. It is a hook into the `Stencil`’s codegen process that is called during each `Stencil`’s generation. It is responsible for generating the code that implements `Trait`'s functionality. This could be used to do things like add automatic view analytics to every view or add first-party support for RxBinding APIs (clicks, attach events, visibility changes, etc.) on all your views. A simple `Trait` that adds visibility helper methods would look like: ```kotlin @AutoService(JavaTrait::class) class VisibilityTrait : JavaTrait { override fun generateFor(type: Builder, initMethod: MethodSpec.Builder, rClass: ClassName, baseType: String) { arrayOf("visible", "invisible", "gone") .forEach { type.addMethod(createVisibilityConvenienceMethod(it)) } } private fun createVisibilityConvenienceMethod(type: String): MethodSpec { return MethodSpec.methodBuilder("is${type.capitalize()}") .addModifiers(Modifier.PUBLIC) .returns(TypeName.BOOLEAN) .addStatement("return getVisibility() == \$T.${type.toUpperCase()}", TypeNames.Android.View) .build() } } ``` A simple `ViewStencil` to generate a `Switch` with visibility helper methods would look like: ```kotlin class SwitchStencil : JavaViewStencil( extendedType = "android.support.v7.widget.SwitchCompat", constructorCount = 3, defaultAttrRes = "switchStyle", addedTraits = VisibilityTrait::class.java) { override fun name() = "MySwitch" } ``` Finally leaving you with a generated view like this: ```java public class MySwitch extends SwitchCompat { // Constructors // protected init method - provided in every stencil public boolean isVisible() { return getVisibility() == View.VISIBLE; } public boolean isGone() { return getVisibility() == View.GONE; } public boolean isInvisible() { return getVisibility() == View.INVISIBLE; } } ``` This may look like a lot of boilerplate for simple helpers, but it scales quite well when you want to have these methods on _all_ your base views. ## Motivation #### Common Façade Everything is behind the façade of commonly named classes, basically "[YOUR_PREFIX]ViewName". This allows you to push as much functionality as you want behind them whilst not changing the front facing entry point. Things we can push behind them include new functionality, other base classes, framework bug fixes, etc. #### Sane, simple maintainability The stencil and trait system ensures that base views are defined in one place and that extra functionality is divided up into single-focus traits. #### Reactive Semantics Artist-generated views can have [RxBinding](https://github.com/JakeWharton/RxBinding) APIs as first class citizens in their public APIs. In a increasingly reactive world, this gracefully bridges common UI listener interactions to RxJava streams. This can optionally be brought in via the `artist-traits-rx` module. #### Intelligence Artist-generated views have deep internal knowledge of their internal state and interactions. This gives you flexibility to do a number of interesting, contextual actions under the hood. *Automatic Instrumentation*: Artist-generated views know when they're being attached, changed to visible, clicked, etc. This allows you to do automatic instrumentation of impressions and taps in views when they occur, provided the developer has provided an ID. You can also detect and signal a developer if an ID is missing where there should be one. *Accessibility*: This intelligence gives you enough insight into the state of the view hierarchy to make accessibility a first class citizen in the daily development cycle. Artist-generated views can intelligently infer if there are content description errors associated with them, and signal them to developers in the apps. For more examples of things you can do with Artist, check out the [Recipes](https://github.com/uber/artist/wiki/Recipes) wiki page. ## Usage #### Create the Provider module - Create a new plain Java/Kotlin module (non-Android) - Add Artist dependencies (API, Traits, Traits-Rx) #### Implement the Stencil Provider - Create a class that implements `JavaViewStencilProvider` - Annotate your class with `@AutoService(JavaViewStencilProvider::class)` #### Implement Custom Traits (Optional) - If you have custom traits, then create classes that implement `JavaTrait` - Annotate those classes with `@AutoService(JavaTrait::class)` #### Add Provider module to Plugin Classpath _Option #1_ If your provider module is in it's own project, then you can add the JAR to the buildscript classpath in your main project's root `build.gradle` like: ```groovy buildscript { dependencies { classpath } } ``` _Option #2_ Otherwise, if your provider module is in your primary project, then in order for Artist to find the classes on the plugin classpath during code generation, we must leverage Gradle's `buildSrc`. We use this project within your project to build the classes that will be added to the plugin classpath. This will run before your primary project is built. - Create a dir at root of project named `buildSrc` - Navigate to `buildSrc` and add a relative symlink to the provider module `cd $PROJECT_ROOT/buildSrc; ln -s ../path/to/provider/module/root custom-artist-providers` - Create a `settings.gradle` in `buildSrc` and add `include :custom-artist-providers` - Update the `build.gradle` for the `buildSrc` project to ensure that the `custom-artist-providers` module is added the buildScript classpath so it is available to the Artist plugin: ```groovy subprojects { subproject -> if (subproject.buildFile.exists()) { repositories { jcenter() google() } rootProject.dependencies { runtime project(path) } } subproject.afterEvaluate { // Disable useless tasks in buildSrc if (subproject.plugins.hasPlugin("kotlin")) { subproject.tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { kotlinOptions.suppressWarnings = true } } subproject.tasks.findAll { it.name.toLowerCase().contains("test") || it.name.toLowerCase().contains("lint") || it.name.toLowerCase().contains("checkstyle") }.each { it.enabled = false } } } ``` #### Use the Generated Views The [generated views](https://github.com/uber/artist/tree/master/sample/demo/java) will be added to the library's source files. They can then be consumed as regular views. To add even more consistency, you can write a lint rule or ErrorProne check to ensure that all `View` subclasses use your Artist-generated views. ## Further examples The set of `JavaViewStencil`s that Artist should process are provided via the `JavaViewStencilProvider`. The [sample's ViewStencilProvider](https://github.com/uber/artist/blob/master/sample/providers/src/main/java/com/uber/artist/myproviders/SampleViewStencilProvider.java) would configure Artist to generate [these Views](https://github.com/uber/artist/tree/master/sample/library/build/generated/source/artist/release/com/uber/artist/mylibrary). ## Download Artist Plugin [![Maven Central](https://img.shields.io/maven-central/v/com.uber.artist/artist.svg)](https://mvnrepository.com/artifact/com.uber.artist/artist) ```gradle classpath 'com.uber.artist:artist:0.4.9' ``` Artist API [![Maven Central](https://img.shields.io/maven-central/v/com.uber.artist/artist-api.svg)](https://mvnrepository.com/artifact/com.uber.artist/artist-api) ```gradle classpath 'com.uber.artist:artist-api:0.4.9' ``` Artist Traits [![Maven Central](https://img.shields.io/maven-central/v/com.uber.artist/artist-traits.svg)](https://mvnrepository.com/artifact/com.uber.artist/artist-traits) ```gradle classpath 'com.uber.artist:artist-traits:0.4.9' ``` Artist Rx Traits [![Maven Central](https://img.shields.io/maven-central/v/com.uber.artist/artist-traits-rx.svg)](https://mvnrepository.com/artifact/com.uber.artist/artist-traits-rx) ```gradle classpath 'com.uber.artist:artist-traits-rx:0.4.9' ``` ## License ``` Copyright (C) 2017 Uber Technologies Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ``` ================================================ FILE: RELEASING.md ================================================ Releasing ========= 1. Change the version in `gradle.properties` to a non-SNAPSHOT version. 2. Update the `CHANGELOG.md` for the impending release. 3. Update the `README.md` with the new version. 4. `git commit -am "Prepare for release X.Y.Z."` (where X.Y.Z is the new version) 5. `git tag -a X.Y.Z -m "Version X.Y.Z"` (where X.Y.Z is the new version) 6. `./gradlew clean uploadArchives` 7. Update the `gradle.properties` to the next SNAPSHOT version. 8. `git commit -am "Prepare next development version."` 9. `git push && git push --tags` 10. Visit [Sonatype Nexus](https://oss.sonatype.org/) and promote the artifact. ================================================ FILE: artist/build.gradle ================================================ apply plugin: "org.jetbrains.kotlin.jvm" sourceCompatibility = JavaVersion.VERSION_1_7 targetCompatibility = JavaVersion.VERSION_1_7 dependencies { compileOnly gradleApi() implementation deps.build.androidPlugin implementation deps.kotlin.stdLibJdk7 implementation project(":artist-core") } if (rootProject.projectDir.name != "buildSrc") { apply from: rootProject.file('gradle/gradle-mvn-push.gradle') } ================================================ FILE: artist/gradle.properties ================================================ # # Copyright (C) 2017. Uber Technologies # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # POM_NAME=artist POM_ARTIFACT_ID=artist POM_PACKAGING=jar ================================================ FILE: artist/src/main/kotlin/com/uber/artist/ArtistExtension.kt ================================================ /* * Copyright (C) 2017. Uber Technologies * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.uber.artist class ArtistExtension { /** * Optional override of the package name for the generated views. Defaults to variant.applicationId */ var viewPackageName: String? = null /** * Optional override of the consuming module's package name. Defaults to variant.applicationId */ var rPackageName: String? = null /** * Optional fully qualified class name for an interface that all generated views should be marked as implementing */ var interfaceClassName: String? = null /** * Optional prefix to append to the beginning of each generated view's name. */ var viewNamePrefix: String = "" /** * Optional setting to control whether the source is formatted with Google Java Format. Defaults to true. */ var formatSource: Boolean = true /** * Optional setting to control whether the source is generated in Java or Kotlin. Defaults to false for Java. */ var generateKotlin: Boolean = false } ================================================ FILE: artist/src/main/kotlin/com/uber/artist/ArtistPlugin.kt ================================================ /* * Copyright (C) 2017. Uber Technologies * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package com.uber.artist import com.android.build.gradle.AppExtension import com.android.build.gradle.AppPlugin import com.android.build.gradle.LibraryExtension import com.android.build.gradle.LibraryPlugin import com.android.build.gradle.api.BaseVariant import com.uber.artist.internal.util.resolveVariantOutputDir import org.gradle.api.DomainObjectSet import org.gradle.api.Plugin import org.gradle.api.Project class ArtistPlugin : Plugin { companion object { private const val ARTIST = "artist" } private val artistExtension = ArtistExtension() override fun apply(project: Project) { project.extensions.add(ARTIST, artistExtension) project.afterEvaluate { project.plugins.all { when (it) { is AppPlugin -> with(project.extensions.getByType(AppExtension::class.java)) { configureAndroid(project, applicationVariants) } is LibraryPlugin -> with(project.extensions.getByType(LibraryExtension::class.java)) { configureAndroid(project, libraryVariants) } } } } } private fun configureAndroid( project: Project, variants: DomainObjectSet) { variants.all { variant -> val outputDir = resolveVariantOutputDir(project, variant, ARTIST) val artistTask = project.tasks.create( "generate${variant.name.capitalize()}Views", ArtistTask::class.java) .apply { group = ARTIST outputDirectory = outputDir description = "Generate ${variant.name} base views." viewPackageName = artistExtension.viewPackageName ?: variant.applicationId rPackageName = artistExtension.rPackageName ?: (artistExtension.viewPackageName ?: variant.applicationId) superinterfaceClassName = artistExtension.interfaceClassName viewNamePrefix = artistExtension.viewNamePrefix formatSource = artistExtension.formatSource generateKotlin = artistExtension.generateKotlin } artistTask.outputs.dir(outputDir) if (artistExtension.generateKotlin) { val kotlinCompileTask = project.tasks.findByName("compile${variant.name.capitalize()}Kotlin") if (kotlinCompileTask != null) { kotlinCompileTask.dependsOn(artistTask) } } variant.registerJavaGeneratingTask(artistTask, artistTask.outputDirectory) } } } ================================================ FILE: artist/src/main/kotlin/com/uber/artist/ArtistTask.kt ================================================ /* * Copyright (C) 2017. Uber Technologies * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.uber.artist import org.gradle.api.DefaultTask import org.gradle.api.tasks.Input import org.gradle.api.tasks.TaskAction import org.gradle.api.tasks.incremental.IncrementalTaskInputs import java.io.File open class ArtistTask : DefaultTask() { @Input lateinit var outputDirectory: File @Input lateinit var viewPackageName: String @Input lateinit var rPackageName: String @Input var superinterfaceClassName: String? = null @Input var viewNamePrefix: String = "" @Input var formatSource: Boolean = true @Input var generateKotlin: Boolean = false @TaskAction fun execute(inputs: IncrementalTaskInputs) { generateViewsFor( outputDirectory, viewPackageName, rPackageName, superinterfaceClassName, viewNamePrefix, formatSource, generateKotlin ) } } ================================================ FILE: artist/src/main/kotlin/com/uber/artist/internal/util/Util.kt ================================================ /* * Copyright (C) 2017. Uber Technologies * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.uber.artist.internal.util import com.android.build.gradle.api.BaseVariant import org.gradle.api.Project import java.io.File fun resolveVariantOutputDir(project: Project, variant: BaseVariant, plugin: String): File = project.file( "${project.projectDir}/build/generated/source/$plugin/${variant.flavorName}/${variant.buildType.name}".sanitize() ) fun String.sanitize(): String = replace('/', File.separatorChar) ================================================ FILE: artist/src/main/resources/META-INF/gradle-plugins/com.uber.artist.properties ================================================ implementation-class=com.uber.artist.ArtistPlugin ================================================ FILE: artist-api/build.gradle ================================================ apply plugin: "java-library" apply plugin: "org.jetbrains.kotlin.jvm" sourceCompatibility = JavaVersion.VERSION_1_7 targetCompatibility = JavaVersion.VERSION_1_7 dependencies { api deps.apt.javapoet api deps.apt.kotlinPoet implementation deps.kotlin.stdLibJdk7 } if (rootProject.projectDir.name != "buildSrc") { apply from: rootProject.file('gradle/gradle-mvn-push.gradle') } ================================================ FILE: artist-api/gradle.properties ================================================ # # Copyright (C) 2017. Uber Technologies # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # POM_NAME=artist-api POM_ARTIFACT_ID=artist-api POM_PACKAGING=jar ================================================ FILE: artist-api/src/main/kotlin/com/uber/artist/api/JavaTrait.kt ================================================ /* * Copyright (C) 2017. Uber Technologies * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.uber.artist.api import com.squareup.javapoet.ClassName import com.squareup.javapoet.MethodSpec import com.squareup.javapoet.TypeSpec /** * A [JavaTrait] defines code that must be generated in order for a [View] to receive new functionality. * Each [JavaTrait] can be declared by multiple [ViewStencil]s, and generate otherwise duplicate code across all views * that exhibit them. Common examples include clicks, attach events, visibility changes, etc. They are a hook into the * [ViewStencil]’s code gen process that are called during each [ViewStencil]’s generation. */ interface JavaTrait : Trait { override fun generateFor(type: TypeSpec.Builder, initMethod: MethodSpec.Builder, rClass: ClassName, sourceType: String) } ================================================ FILE: artist-api/src/main/kotlin/com/uber/artist/api/JavaTraitService.kt ================================================ /* * Copyright (C) 2018. Uber Technologies * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.uber.artist.api import java.util.ServiceLoader class JavaTraitService private constructor() : TraitService { private val serviceLoader = ServiceLoader.load(JavaTrait::class.java) /** * Gets the [Trait] implementations loaded. * * @return The located [Trait]s. */ override fun get(): Set { val traits = LinkedHashSet() serviceLoader.iterator() .forEach { traits.add(it) } return traits } companion object { fun newInstance(): JavaTraitService { return JavaTraitService() } } } ================================================ FILE: artist-api/src/main/kotlin/com/uber/artist/api/JavaTypeNames.kt ================================================ @file:Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN") /* * Copyright (C) 2017. Uber Technologies * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.uber.artist.api import com.squareup.javapoet.ClassName class JavaTypeNames { class Android { companion object { val AccessibilityEvent: ClassName = ClassName.get("android.view.accessibility", "AccessibilityEvent") val AccessibilityNodeInfo: ClassName = ClassName.get("android.view.accessibility", "AccessibilityNodeInfo") val AttributeSet: ClassName = ClassName.get("android.util", "AttributeSet") val Canvas: ClassName = ClassName.get("android.graphics", "Canvas") val Context: ClassName = ClassName.get("android.content", "Context") val Drawable: ClassName = ClassName.get("android.graphics.drawable", "Drawable") val Gravity: ClassName = ClassName.get("android.view", "Gravity") val GravityCompat: ClassName = ClassName.get("androidx.core.view", "GravityCompat") val MenuItem: ClassName = ClassName.get("android.view", "MenuItem") val Rect: ClassName = ClassName.get("android.graphics", "Rect") val TabLayout: ClassName = ClassName.get("com.google.android.material.tabs", "TabLayout") val TabLayoutTab: ClassName = TabLayout.nestedClass("Tab") val TypedArray: ClassName = ClassName.get("android.content.res", "TypedArray") val View: ClassName = ClassName.get("android.view", "View") val MotionEvent: ClassName = ClassName.get("android.view", "MotionEvent") } } class Annotations { companion object { val AttrRes: ClassName = ClassName.get("androidx.annotation", "AttrRes") val CallSuper: ClassName = ClassName.get("androidx.annotation", "CallSuper") val IdRes: ClassName = ClassName.get("androidx.annotation", "IdRes") val Nullable: ClassName = ClassName.get("androidx.annotation", "Nullable") val StyleRes: ClassName = ClassName.get("androidx.annotation", "StyleRes") val TargetApi: ClassName = ClassName.get("android.annotation", "TargetApi") val VisibleForTesting: ClassName = ClassName.get("androidx.annotation", "VisibleForTesting") } } class Java { companion object { val Map: ClassName = ClassName.get(java.util.Map::class.java) val String: ClassName = ClassName.get(java.lang.String::class.java) } } } ================================================ FILE: artist-api/src/main/kotlin/com/uber/artist/api/JavaViewStencil.kt ================================================ /* * Copyright (C) 2018. Uber Technologies * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.uber.artist.api import com.squareup.javapoet.ClassName import com.squareup.javapoet.CodeBlock import com.squareup.javapoet.MethodSpec import com.squareup.javapoet.TypeSpec open class JavaViewStencil( extendedType: String, constructorCount: Int = 4, defaultAttrRes: String? = null, vararg addedTraits: Class = emptyArray() ) : ViewStencil( extendedType, constructorCount, defaultAttrRes, addedTraits.toSet() ) { val sourceType = ClassName.get( extendedType.substringBeforeLast('.'), extendedType.split('.').last() ) /** * Hook for when attributes are being pulled out of the attribute set. * Can safely assume the following values exist: * - context: Context * - attrs: AttributeSet * - defStyleAttr: int * - a: TypedArray * * Should *not* recycle `a`. Safe to assume `a` is null-checked before code would execute. */ override fun attrsHook(type: TypeSpec.Builder, initMethod: MethodSpec.Builder): CodeBlock? = null /** * Hook for implementing the `init()` method. */ override fun initMethodHook(type: TypeSpec.Builder, initMethod: MethodSpec.Builder) {} /** * Hook for the type builder implementation. */ override fun typeHook(type: TypeSpec.Builder) {} } ================================================ FILE: artist-api/src/main/kotlin/com/uber/artist/api/JavaViewStencilProvider.kt ================================================ /* * Copyright (C) 2018. Uber Technologies * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.uber.artist.api interface JavaViewStencilProvider : ViewStencilProvider { /** * Provide a set of [ViewStencil]s to be used during code generation. * * @return The set of [ViewStencil]s. */ override fun stencils(): Set /** * Provide a set of [Trait] classes that should be applied to all [ViewStencil]s. * * @return The set of [Trait] classes. */ override fun globalTraits(): Set> } ================================================ FILE: artist-api/src/main/kotlin/com/uber/artist/api/JavaViewStencilService.kt ================================================ /* * Copyright (C) 2018. Uber Technologies * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.uber.artist.api import java.util.ServiceLoader class JavaViewStencilService private constructor() : ViewStencilService { private val serviceLoader = ServiceLoader.load(JavaViewStencilProvider::class.java) /** * Gets the [ViewStencil] implementations loaded. * * @return The located [ViewStencil]s. */ override fun getStencils(): Set { val stencils = LinkedHashSet() serviceLoader.iterator() .forEach { stencils.addAll(it.stencils()) } return stencils } /** * Gets the [Trait] implementations that should be applied to every [ViewStencil]. * * @return The located global [Trait]s. */ override fun getGlobalTraits(): Set> { val globalTraits = LinkedHashSet>() serviceLoader.iterator() .forEach { globalTraits.addAll(it.globalTraits()) } return globalTraits } companion object { fun newInstance(): JavaViewStencilService { return JavaViewStencilService() } } } ================================================ FILE: artist-api/src/main/kotlin/com/uber/artist/api/KotlinTrait.kt ================================================ /* * Copyright (C) 2018. Uber Technologies * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.uber.artist.api import com.squareup.kotlinpoet.ClassName import com.squareup.kotlinpoet.FunSpec import com.squareup.kotlinpoet.TypeSpec /** * A [KotlinTrait] defines code that must be generated in order for a [View] to receive new functionality. * Each [KotlinTrait] can be declared by multiple [ViewStencil]s, and generate otherwise duplicate code across all views * that exhibit them. Common examples include clicks, attach events, visibility changes, etc. They are a hook into the * [ViewStencil]’s code gen process that are called during each [ViewStencil]’s generation. */ interface KotlinTrait : Trait { override fun generateFor(type: TypeSpec.Builder, initMethod: FunSpec.Builder, rClass: ClassName, sourceType: String) } ================================================ FILE: artist-api/src/main/kotlin/com/uber/artist/api/KotlinTraitService.kt ================================================ /* * Copyright (C) 2018. Uber Technologies * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.uber.artist.api import java.util.ServiceLoader class KotlinTraitService private constructor() : TraitService { private val serviceLoader = ServiceLoader.load(KotlinTrait::class.java) /** * Gets the [Trait] implementations loaded. * * @return The located [Trait]s. */ override fun get(): Set { val traits = LinkedHashSet() serviceLoader.iterator() .forEach { traits.add(it) } return traits } companion object { fun newInstance(): KotlinTraitService { return KotlinTraitService() } } } ================================================ FILE: artist-api/src/main/kotlin/com/uber/artist/api/KotlinTypeNames.kt ================================================ @file:Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN") /* * Copyright (C) 2018. Uber Technologies * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.uber.artist.api import com.squareup.kotlinpoet.ClassName import com.squareup.kotlinpoet.asClassName class KotlinTypeNames { class Android { companion object { val AccessibilityEvent: ClassName = ClassName("android.view.accessibility", "AccessibilityEvent") val AccessibilityNodeInfo: ClassName = ClassName("android.view.accessibility", "AccessibilityNodeInfo") val AttributeSet: ClassName = ClassName("android.util", "AttributeSet") val Canvas: ClassName = ClassName("android.graphics", "Canvas") val Context: ClassName = ClassName("android.content", "Context") val Drawable: ClassName = ClassName("android.graphics.drawable", "Drawable") val Gravity: ClassName = ClassName("android.view", "Gravity") val GravityCompat: ClassName = ClassName("androidx.core.view", "GravityCompat") val MenuItem: ClassName = ClassName("android.view", "MenuItem") val Rect: ClassName = ClassName("android.graphics", "Rect") val TabLayout: ClassName = ClassName("com.google.android.material.tabs", "TabLayout") val TabLayoutTab: ClassName = TabLayout.nestedClass("Tab") val TypedArray: ClassName = ClassName("android.content.res", "TypedArray") val View: ClassName = ClassName("android.view", "View") val MotionEvent: ClassName = ClassName("android.view", "MotionEvent") } } class Annotations { companion object { val AttrRes: ClassName = ClassName("androidx.annotation", "AttrRes") val CallSuper: ClassName = ClassName("androidx.annotation", "CallSuper") val IdRes: ClassName = ClassName("androidx.annotation", "IdRes") val StyleRes: ClassName = ClassName("androidx.annotation", "StyleRes") val TargetApi: ClassName = ClassName("android.annotation", "TargetApi") val VisibleForTesting: ClassName = ClassName("androidx.annotation", "VisibleForTesting") } } class Java { companion object { val Map: ClassName = java.util.Map::class.asClassName() val Object: ClassName = java.lang.Object::class.asClassName() val String: ClassName = java.lang.String::class.asClassName() } } } ================================================ FILE: artist-api/src/main/kotlin/com/uber/artist/api/KotlinViewStencil.kt ================================================ /* * Copyright (C) 2018. Uber Technologies * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.uber.artist.api import AliasTypeNames.Rx.Companion.rxExtensionFunctionToAlias import com.squareup.kotlinpoet.ClassName import com.squareup.kotlinpoet.CodeBlock import com.squareup.kotlinpoet.FunSpec import com.squareup.kotlinpoet.TypeSpec open class KotlinViewStencil( extendedType: String, constructorCount: Int = 4, defaultAttrRes: String? = null, vararg addedTraits: Class = emptyArray() ) : ViewStencil( extendedType, constructorCount, defaultAttrRes, addedTraits.toSet() ) { val sourceType = ClassName( extendedType.substringBeforeLast('.'), extendedType.split('.').last() ) /** * Hook for when attributes are being pulled out of the attribute set. * Can safely assume the following values exist: * - context: Context * - attrs: AttributeSet * - defStyleAttr: int * - a: TypedArray * * Should *not* recycle `a`. Safe to assume `a` is null-checked before code would execute. */ override fun attrsHook(type: TypeSpec.Builder, initMethod: FunSpec.Builder): CodeBlock? = null /** * Hook for implementing the `init()` method. */ override fun initMethodHook(type: TypeSpec.Builder, initMethod: FunSpec.Builder) {} /** * Hook for the type builder implementation. */ override fun typeHook(type: TypeSpec.Builder) {} /** * In an attempt to allow for newly created extension functions to co-exist alongside legacy * code and not conflict in the naming, especially for the migration from rxbinding2 to * rxbinding3, we provide an ability for stencils to provide their own aliases. * * This corresponds to the syntax import x as y where x is the extension function and y is the * alias */ fun extensionFunctionToAlias(): Map { return rxExtensionFunctionToAlias } } ================================================ FILE: artist-api/src/main/kotlin/com/uber/artist/api/KotlinViewStencilProvider.kt ================================================ /* * Copyright (C) 2018. Uber Technologies * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.uber.artist.api interface KotlinViewStencilProvider : ViewStencilProvider { /** * Provide a set of [ViewStencil]s to be used during code generation. * * @return The set of [ViewStencil]s. */ override fun stencils(): Set /** * Provide a set of [Trait] classes that should be applied to all [ViewStencil]s. * * @return The set of [Trait] classes. */ override fun globalTraits(): Set> } ================================================ FILE: artist-api/src/main/kotlin/com/uber/artist/api/KotlinViewStencilService.kt ================================================ /* * Copyright (C) 2018. Uber Technologies * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.uber.artist.api import java.util.ServiceLoader class KotlinViewStencilService private constructor() : ViewStencilService { private val serviceLoader = ServiceLoader.load(KotlinViewStencilProvider::class.java) /** * Gets the [ViewStencil] implementations loaded. * * @return The located [ViewStencil]s. */ override fun getStencils(): Set { val stencils = LinkedHashSet() serviceLoader.iterator() .forEach { stencils.addAll(it.stencils()) } return stencils } /** * Gets the [Trait] implementations that should be applied to every [ViewStencil]. * * @return The located global [Trait]s. */ override fun getGlobalTraits(): Set> { val globalTraits = LinkedHashSet>() serviceLoader.iterator() .forEach { globalTraits.addAll(it.globalTraits()) } return globalTraits } companion object { fun newInstance(): KotlinViewStencilService { return KotlinViewStencilService() } } } ================================================ FILE: artist-api/src/main/kotlin/com/uber/artist/api/Trait.kt ================================================ /* * Copyright (C) 2017. Uber Technologies * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.uber.artist.api /** * A [Trait] defines code that must be generated in order for a [View] to receive new functionality. * Each [Trait] can be declared by multiple [ViewStencil]s, and generate otherwise duplicate code across all views * that exhibit them. Common examples include clicks, attach events, visibility changes, etc. They are a hook into the * [ViewStencil]’s code gen process that are called during each [ViewStencil]’s generation. */ interface Trait { fun generateFor(type: OutputType, initMethod: FunType, rClass: ClassType, sourceType: String) } ================================================ FILE: artist-api/src/main/kotlin/com/uber/artist/api/TraitService.kt ================================================ /* * Copyright (C) 2017. Uber Technologies * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.uber.artist.api interface TraitService { /** * Gets the [Trait] implementations loaded. * * @return The located [Trait]s. */ fun get(): Set } ================================================ FILE: artist-api/src/main/kotlin/com/uber/artist/api/TypeNames.kt ================================================ @file:Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN") /* * Copyright (C) 2017. Uber Technologies * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.uber.artist.api import com.squareup.javapoet.ClassName class TypeNames { class Android { companion object { val AccessibilityEvent: ClassName = ClassName.get("android.view.accessibility", "AccessibilityEvent") val AccessibilityNodeInfo: ClassName = ClassName.get("android.view.accessibility", "AccessibilityNodeInfo") val AttributeSet: ClassName = ClassName.get("android.util", "AttributeSet") val Canvas: ClassName = ClassName.get("android.graphics", "Canvas") val Context: ClassName = ClassName.get("android.content", "Context") val Drawable: ClassName = ClassName.get("android.graphics.drawable", "Drawable") val Gravity: ClassName = ClassName.get("android.view", "Gravity") val GravityCompat: ClassName = ClassName.get("androidx.core.view", "GravityCompat") val MenuItem: ClassName = ClassName.get("android.view", "MenuItem") val Rect: ClassName = ClassName.get("android.graphics", "Rect") val TabLayout: ClassName = ClassName.get("com.google.android.material.tabs", "TabLayout") val TabLayoutTab: ClassName = TabLayout.nestedClass("Tab") val TypedArray: ClassName = ClassName.get("android.content.res", "TypedArray") val View: ClassName = ClassName.get("android.view", "View") val MotionEvent: ClassName = ClassName.get("android.view", "MotionEvent") } } class Annotations { companion object { val AttrRes: ClassName = ClassName.get("androidx.annotation", "AttrRes") val CallSuper: ClassName = ClassName.get("androidx.annotation", "CallSuper") val IdRes: ClassName = ClassName.get("androidx.annotation", "IdRes") val Nullable: ClassName = ClassName.get("androidx.annotation", "Nullable") val StyleRes: ClassName = ClassName.get("androidx.annotation", "StyleRes") val TargetApi: ClassName = ClassName.get("android.annotation", "TargetApi") val VisibleForTesting: ClassName = ClassName.get("androidx.annotation", "VisibleForTesting") } } class Java { companion object { val Map: ClassName = ClassName.get(java.util.Map::class.java) val String: ClassName = ClassName.get(java.lang.String::class.java) } } } ================================================ FILE: artist-api/src/main/kotlin/com/uber/artist/api/ViewStencil.kt ================================================ /* * Copyright (C) 2017. Uber Technologies * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.uber.artist.api abstract class ViewStencil( val extendedType: String, val constructorCount: Int = 4, val defaultAttrRes: String? = null, protected val addedTraits: Set>> ) { val globalTraits = mutableSetOf>>() var namePrefix: String = "" open fun traits(): Set>> = globalTraits.plus(addedTraits) /** * The name of the view class. */ open fun name(): String { val sourceName = extendedType.split('.').last() return "$namePrefix${sourceName.removePrefix("AppCompat")}" } /** * Hook for when attributes are being pulled out of the attribute set. * Can safely assume the following values exist: * - context: Context * - attrs: AttributeSet * - defStyleAttr: int * - a: TypedArray * * Should *not* recycle `a`. Safe to assume `a` is null-checked before code would execute. */ abstract fun attrsHook(type: OutputType, initMethod: FunType): CodeBlock? /** * Hook for implementing the `init()` method. */ abstract fun initMethodHook(type: OutputType, initMethod: FunType) /** * Hook for the type builder implementation. */ abstract fun typeHook(type: OutputType) fun setGlobalTraits(traits: Set>>) { globalTraits.addAll(traits) } fun setPrefix(namePrefix: String) { this.namePrefix = namePrefix } } ================================================ FILE: artist-api/src/main/kotlin/com/uber/artist/api/ViewStencilProvider.kt ================================================ /* * Copyright (C) 2017. Uber Technologies * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.uber.artist.api interface ViewStencilProvider { /** * Provide a set of [ViewStencil]s to be used during code generation. * * @return The set of [ViewStencil]s. */ fun stencils(): Set /** * Provide a set of [Trait] classes that should be applied to all [ViewStencil]s. * * @return The set of [Trait] classes. */ fun globalTraits(): Set> } ================================================ FILE: artist-api/src/main/kotlin/com/uber/artist/api/ViewStencilService.kt ================================================ /* * Copyright (C) 2017. Uber Technologies * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.uber.artist.api interface ViewStencilService { /** * Gets the [ViewStencil] implementations loaded. * * @return The located [ViewStencil]s. */ fun getStencils(): Set /** * Gets the [Trait] implementations that should be applied to every [ViewStencil]. * * @return The located global [Trait]s. */ fun getGlobalTraits(): Set> } ================================================ FILE: artist-api/src/main/kotlin/com/uber/artist/api/alias/AliasTypeNames.kt ================================================ import com.squareup.kotlinpoet.ClassName class AliasTypeNames { open class Rx { companion object { val RecyclerViewScrollEvent = ClassName("com.jakewharton.rxbinding3.recyclerview", "RecyclerViewScrollEvent") val RxView = ClassName("com.jakewharton.rxbinding3.view", "RxView") val RxCompoundButton = ClassName("com.jakewharton.rxbinding3.widget", "RxCompoundButton") val RxNestedScrollView = ClassName("com.jakewharton.rxbinding3.core", "RxNestedScrollView") val RxRecyclerView = ClassName("com.jakewharton.rxbinding3.recyclerview", "RxRecyclerView") val RxSearchView = ClassName("com.jakewharton.rxbinding3.appcompat", "RxSearchView") val RxSeekBar = ClassName("com.jakewharton.rxbinding3.widget", "RxSeekBar") val SeekBarChangeEvent = ClassName("com.jakewharton.rxbinding3.widget", "SeekBarChangeEvent") val SeekBarProgressChangeEvent = ClassName("com.jakewharton.rxbinding3.widget", "SeekBarProgressChangeEvent") val SeekBarStartChangeEvent = ClassName("com.jakewharton.rxbinding3.widget", "SeekBarStartChangeEvent") val RxSwipeRefreshLayout = ClassName("com.jakewharton.rxbinding3.swiperefreshlayout", "RxSwipeRefreshLayout") val RxTabLayout = ClassName("com.jakewharton.rxbinding3.material", "RxTabLayout") val RxTextView = ClassName("com.jakewharton.rxbinding3.widget", "RxTextView") val RxToolbar = ClassName("com.jakewharton.rxbinding3.appcompat", "RxToolbar") val RxViewPager = ClassName("com.jakewharton.rxbinding3.viewpager", "RxViewPager") val RxViewAttachEvent = ClassName("com.jakewharton.rxbinding3.view", "ViewAttachEvent") val RxViewAttachAttachedEvent = ClassName("com.jakewharton.rxbinding3.view", "ViewAttachAttachedEvent") val RxViewAttachDetachedEvent = ClassName("com.jakewharton.rxbinding3.view", "ViewAttachDetachedEvent") val SearchViewQueryTextEvent = ClassName("com.jakewharton.rxbinding3.appcompat", "SearchViewQueryTextEvent") val ViewScrollChangeEvent = ClassName("com.jakewharton.rxbinding3.view", "ViewScrollChangeEvent") data class ExtensionFunctionAlias( val className: ClassName, val methodName: String ) val list = listOf( ExtensionFunctionAlias(RxToolbar, "itemClicks"), ExtensionFunctionAlias(RxView, "layoutChanges"), ExtensionFunctionAlias(RxView, "clicks"), ExtensionFunctionAlias(RxView, "attachEvents"), ExtensionFunctionAlias(RxView, "longClicks"), ExtensionFunctionAlias(RxToolbar, "navigationClicks"), ExtensionFunctionAlias(RxView, "scrollChangeEvents"), ExtensionFunctionAlias(RxNestedScrollView, "scrollChangeEvents"), ExtensionFunctionAlias(RxCompoundButton, "checkedChanges"), ExtensionFunctionAlias(RxSwipeRefreshLayout, "refreshes"), ExtensionFunctionAlias(RxSeekBar, "changeEvents"), ExtensionFunctionAlias(RxTabLayout, "selections"), ExtensionFunctionAlias(RxSearchView, "queryTextChangeEvents"), ExtensionFunctionAlias(RxTabLayout, "selections"), ExtensionFunctionAlias(RxRecyclerView, "scrollEvents"), ExtensionFunctionAlias(RxTextView, "textChanges"), ExtensionFunctionAlias(RxView, "attachEvents") ) val rxExtensionFunctionToAlias = list.map { it to it.className.simpleName.toLowerCase() + "_" + it .methodName.dropLast(4) }.toMap() } } } ================================================ FILE: artist-core/build.gradle ================================================ apply plugin: "java-library" apply plugin: "org.jetbrains.kotlin.jvm" sourceCompatibility = JavaVersion.VERSION_1_7 targetCompatibility = JavaVersion.VERSION_1_7 dependencies { implementation deps.apt.javapoet implementation deps.apt.kotlinPoet implementation deps.build.googleJavaFormatter implementation deps.kotlin.stdLibJdk7 implementation project(":artist-api") // Dont need to run tests in buildSrc if (rootProject.projectDir.name != "buildSrc") { testImplementation deps.androidx.annotations testImplementation deps.test.compileTesting testImplementation deps.test.junit testImplementation deps.test.robolectric testImplementation deps.test.truth } } if (rootProject.projectDir.name != "buildSrc") { apply from: rootProject.file('gradle/gradle-mvn-push.gradle') } ================================================ FILE: artist-core/gradle.properties ================================================ # # Copyright (C) 2017. Uber Technologies # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # POM_NAME=artist-core POM_ARTIFACT_ID=artist-core POM_PACKAGING=jar ================================================ FILE: artist-core/src/main/kotlin/com/uber/artist/Artist.kt ================================================ /* * Copyright (C) 2017. Uber Technologies * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.uber.artist import java.io.File fun generateViewsFor( outputDir: File, viewPackageName: String, rPackageName: String, superinterfaceClassName: String?, viewNamePrefix: String, formatSource: Boolean, generateKotlin: Boolean = false) { val artistCodeGenerator = if (generateKotlin) KotlinArtistCodeGenerator() else JavaArtistCodeGenerator() artistCodeGenerator.generateViews(outputDir, viewPackageName, rPackageName, superinterfaceClassName, viewNamePrefix, formatSource) } ================================================ FILE: artist-core/src/main/kotlin/com/uber/artist/ArtistCodeGenerator.kt ================================================ /* * Copyright (C) 2018. Uber Technologies * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.uber.artist import com.google.common.annotations.VisibleForTesting import com.uber.artist.api.KotlinViewStencil import com.uber.artist.api.Trait import com.uber.artist.api.ViewStencil import java.io.File abstract class ArtistCodeGenerator< OutputFileType, OutputType, FunType, ClassName, CodeBlock, ViewStencilType : ViewStencil, TraitType : Trait> { abstract val viewStencils: Set abstract val traits: Set abstract val globalTraits: Set> fun generateViews( outputDir: File, viewPackageName: String, rPackageName: String, superinterfaceClassName: String?, viewNamePrefix: String, formatSource: Boolean ) { generateViewsForStencils(viewStencils, traits, globalTraits, outputDir, viewPackageName, rPackageName, superinterfaceClassName, viewNamePrefix, formatSource) } @VisibleForTesting fun generateViewsForStencils( viewStencils: Set, traits: Set, globalTraits: Set>, outputDir: File, viewPackageName: String, rPackageName: String, superinterfaceClassName: String?, viewNamePrefix: String, formatSource: Boolean) { val traitMap: Map, TraitType> = traits.associateBy { it.javaClass } viewStencils.forEach { it.setGlobalTraits(globalTraits) it.setPrefix(viewNamePrefix) val typeSpecBuilder = generateTypeSpecFor(it, rPackageName, traitMap, superinterfaceClassName) val fileSpec = generateFileSpecFor(it, viewPackageName, typeSpecBuilder) if (formatSource) { writeFileWithFormatting(fileSpec, outputDir, typeSpecBuilder, viewPackageName) } else { writeFile(fileSpec, outputDir) } } } protected abstract fun generateFileSpecFor(stencil: ViewStencilType, viewPackageName: String, typeSpecBuilder: OutputType): OutputFileType protected abstract fun generateTypeSpecFor( stencil: ViewStencilType, rPackageName: String, traitMap: Map, TraitType>, superinterfaceClassName: String?): OutputType protected abstract fun createInitBuilderFor(stencil: ViewStencilType, type: OutputType): FunType protected abstract fun generateConstructorsFor(stencil: ViewStencilType, type: OutputType, rClass: ClassName) protected abstract fun superinterface(className: String): ClassName protected abstract fun writeFile(fileSpec: OutputFileType, outputDir: File) protected abstract fun writeFileWithFormatting(fileSpec: OutputFileType, outputDir: File, outputType: OutputType, viewPackageName: String) protected fun superConstructorStatement(count: Int): String { when (count) { 1 -> return "super(context)" 2 -> return "super(context, attrs)" 3 -> return "super(context, attrs, defStyleAttr)" 4 -> return "super(context, attrs, defStyleAttr, defStyleRes)" } throw IllegalArgumentException() } protected fun initStatement(count: Int): String { when (count) { 1 -> return "init(context, null, 0, 0)" 2 -> return "init(context, attrs, 0, 0)" 3 -> return "init(context, attrs, defStyleAttr, 0)" 4 -> return "init(context, attrs, defStyleAttr, defStyleRes)" } throw IllegalArgumentException() } } ================================================ FILE: artist-core/src/main/kotlin/com/uber/artist/FormattingFileWriter.kt ================================================ /* * Copyright (C) 2018. Uber Technologies * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.uber.artist import java.io.File abstract class FormattingFileWriter { protected val packageSplitRegex = "\\.".toRegex() /** * A rough estimate of the average file size: 80 chars per line, 500 lines. */ protected val defaultFileSize = 80 * 500 /** * A file writer function that formats the code before writing out to the file system. */ abstract fun writeWithFormattingTo(directory: File) } ================================================ FILE: artist-core/src/main/kotlin/com/uber/artist/JavaArtistCodeGenerator.kt ================================================ /* * Copyright (C) 2018. Uber Technologies * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.uber.artist import com.squareup.javapoet.AnnotationSpec import com.squareup.javapoet.ClassName import com.squareup.javapoet.CodeBlock import com.squareup.javapoet.JavaFile import com.squareup.javapoet.MethodSpec import com.squareup.javapoet.ParameterSpec import com.squareup.javapoet.TypeSpec import com.uber.artist.api.JavaTrait import com.uber.artist.api.JavaTraitService import com.uber.artist.api.JavaViewStencil import com.uber.artist.api.JavaViewStencilService import com.uber.artist.api.TypeNames import java.io.File import javax.lang.model.element.Modifier class JavaArtistCodeGenerator : ArtistCodeGenerator() { override val viewStencils: Set get() = JavaViewStencilService.newInstance().getStencils() override val traits: Set get() = JavaTraitService.newInstance().get() override val globalTraits: Set> get() = JavaViewStencilService.newInstance().getGlobalTraits() override fun generateFileSpecFor(stencil: JavaViewStencil, viewPackageName: String, typeSpecBuilder: TypeSpec.Builder): JavaFile { return JavaFile.builder(viewPackageName, typeSpecBuilder.build()).build() } override fun generateTypeSpecFor( stencil: JavaViewStencil, rPackageName: String, traitMap: Map, JavaTrait>, superinterfaceClassName: String?): TypeSpec.Builder { val rClass = ClassName.get(rPackageName, "R") val typeBuilder = TypeSpec.classBuilder(stencil.name()) .addModifiers(Modifier.PUBLIC) .superclass(stencil.sourceType) superinterfaceClassName?.let { typeBuilder.addSuperinterface(superinterface(superinterfaceClassName)) } generateConstructorsFor(stencil, typeBuilder, rClass) val initMethod = createInitBuilderFor(stencil, typeBuilder) stencil.traits() .map { traitName -> traitMap[traitName] } .forEach { it?.generateFor(typeBuilder, initMethod, rClass, stencil.name()) } typeBuilder.addMethod(initMethod.build()) stencil.typeHook(typeBuilder) return typeBuilder } override fun createInitBuilderFor( stencil: JavaViewStencil, type: TypeSpec.Builder): MethodSpec.Builder { val initMethod = MethodSpec.methodBuilder("init") .addAnnotation(TypeNames.Annotations.CallSuper) .addModifiers(Modifier.PROTECTED) .addParameter(ParameterSpec.builder(TypeNames.Android.Context, "context") .build()) .addParameter(ParameterSpec.builder(TypeNames.Android.AttributeSet, "attrs") .addAnnotation(TypeNames.Annotations.Nullable) .build()) .addParameter(ParameterSpec.builder(ClassName.INT, "defStyleAttr") .addAnnotation(TypeNames.Annotations.AttrRes) .build()) .addParameter(ParameterSpec.builder(ClassName.INT, "defStyleRes") .addAnnotation(TypeNames.Annotations.StyleRes) .build()) stencil.initMethodHook(type, initMethod) return initMethod } override fun generateConstructorsFor(stencil: JavaViewStencil, type: TypeSpec.Builder, rClass: ClassName) { val count = stencil.constructorCount for (i in 1..count) { when (i) { 1 -> // Context constructor type.addMethod(MethodSpec.constructorBuilder() .addModifiers(Modifier.PUBLIC) .addParameter(ParameterSpec.builder(TypeNames.Android.Context, "context") .build()) .addCode(constructorBlock(stencil, rClass, count, i)) .build()) 2 -> // Context, AttributeSet constructor type.addMethod(MethodSpec.constructorBuilder() .addModifiers(Modifier.PUBLIC) .addParameter(ParameterSpec.builder(TypeNames.Android.Context, "context") .build()) .addParameter(ParameterSpec.builder(TypeNames.Android.AttributeSet, "attrs") .addAnnotation(TypeNames.Annotations.Nullable) .build()) .addCode(constructorBlock(stencil, rClass, count, i)) .build()) 3 -> // Context, AttributeSet, defStyleAttr constructor type.addMethod(MethodSpec.constructorBuilder() .addModifiers(Modifier.PUBLIC) .addParameter(ParameterSpec.builder(TypeNames.Android.Context, "context") .build()) .addParameter(ParameterSpec.builder(TypeNames.Android.AttributeSet, "attrs") .addAnnotation(TypeNames.Annotations.Nullable) .build()) .addParameter(ParameterSpec.builder(ClassName.INT, "defStyleAttr") .addAnnotation(TypeNames.Annotations.AttrRes) .build()) .addCode(constructorBlock(stencil, rClass, count, i)) .build()) 4 -> // Context, AttributeSet, defStyleAttr, defStyleRes constructor type.addMethod(MethodSpec.constructorBuilder() .addModifiers(Modifier.PUBLIC) .addParameter(ParameterSpec.builder(TypeNames.Android.Context, "context") .build()) .addParameter(ParameterSpec.builder(TypeNames.Android.AttributeSet, "attrs") .addAnnotation(TypeNames.Annotations.Nullable) .build()) .addParameter(ParameterSpec.builder(ClassName.INT, "defStyleAttr") .addAnnotation(TypeNames.Annotations.AttrRes) .build()) .addParameter(ParameterSpec.builder(ClassName.INT, "defStyleRes") .addAnnotation(TypeNames.Annotations.StyleRes) .build()) .addAnnotation(AnnotationSpec.builder(TypeNames.Annotations.TargetApi) .addMember("value", "\$T.\$L.\$L", ClassName.get("android.os", "Build"), "VERSION_CODES", "LOLLIPOP") .build()) .addCode(constructorBlock(stencil, rClass, count, i)) .build()) } } } private fun constructorBlock(stencil: JavaViewStencil, rClass: ClassName, total: Int, currentIndex: Int): CodeBlock { val builder = CodeBlock.builder() if (currentIndex == total || currentIndex == 3) { builder.addStatement(superConstructorStatement(currentIndex)) builder.addStatement(initStatement(currentIndex)) } else { builder.add(fallthroughConstructorStatement(stencil, rClass, currentIndex)) } return builder.build() } private fun fallthroughConstructorStatement(stencil: JavaViewStencil, rClass: ClassName, count: Int): CodeBlock { when (count) { 1 -> return CodeBlock.of("this(context, null);\n") 2 -> { return if (stencil.defaultAttrRes != null) { if ((stencil.defaultAttrRes as String).startsWith(prefix = "android.R")) { CodeBlock.of("this(context, attrs, ${stencil.defaultAttrRes});\n") } else { CodeBlock.of("this(context, attrs, \$T.attr.${stencil.defaultAttrRes});\n", rClass) } } else { CodeBlock.of("this(context, attrs, 0);\n") } } 3 -> return CodeBlock.of("this(context, attrs, defStyleAttr, 0);\n") } throw IllegalArgumentException(count.toString()) } override fun superinterface(className: String): ClassName { val packageName = className.substring(0, className.lastIndexOf('.')) val simpleName = className.substring(className.lastIndexOf('.') + 1) return ClassName.get(packageName, simpleName) } override fun writeFile(fileSpec: JavaFile, outputDir: File) { fileSpec.writeTo(outputDir) } override fun writeFileWithFormatting(fileSpec: JavaFile, outputDir: File, outputType: TypeSpec.Builder, packageName: String) { JavaFormattingFileWriter(fileSpec, outputType, packageName).writeWithFormattingTo(outputDir) } } ================================================ FILE: artist-core/src/main/kotlin/com/uber/artist/JavaFormattingFileWriter.kt ================================================ /* * Copyright (C) 2018. Uber Technologies * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.uber.artist import com.google.common.base.Preconditions.checkArgument import com.google.common.io.CharSink import com.google.common.io.CharSource import com.google.googlejavaformat.java.Formatter import com.google.googlejavaformat.java.FormatterException import com.squareup.javapoet.JavaFile import com.squareup.javapoet.TypeSpec import java.io.File import java.io.IOException import java.io.OutputStreamWriter import java.io.Writer import java.nio.charset.StandardCharsets.UTF_8 import java.nio.file.Files class JavaFormattingFileWriter(val outputFile: JavaFile, val typeSpecBuilder: TypeSpec.Builder, val packageName: String) : FormattingFileWriter() { private val formatter: Formatter = Formatter() /** * A file writer function that formats the code before writing out to the file system. */ override fun writeWithFormattingTo(directory: File) { val directoryPath = directory.toPath() checkArgument(Files.notExists(directoryPath) || Files.isDirectory(directoryPath), "path %s exists but is not a directoryPath.", directoryPath) var outputDirectory = directoryPath if (!packageName.isEmpty()) { for (packageComponent in packageName.split(packageSplitRegex) .filter { !it.isEmpty() }.toTypedArray()) { outputDirectory = outputDirectory.resolve(packageComponent) } Files.createDirectories(outputDirectory) } val typeSpec = typeSpecBuilder.build() val outputPath = outputDirectory.resolve(typeSpec.name + ".java") try { OutputStreamWriter(Files.newOutputStream(outputPath), UTF_8).use { writer -> val stringBuilder = StringBuilder(defaultFileSize) outputFile.writeTo(stringBuilder) formatter.formatSource( CharSource.wrap(stringBuilder), object : CharSink() { @Throws(IOException::class) override fun openStream(): Writer { return writer } }) } } catch (e: FormatterException) { throw IOException("Error formatting " + outputPath.fileName.toString(), e) } } } ================================================ FILE: artist-core/src/main/kotlin/com/uber/artist/KotlinArtistCodeGenerator.kt ================================================ /* * Copyright (C) 2018. Uber Technologies * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.uber.artist import AliasTypeNames.Rx.Companion.rxExtensionFunctionToAlias import com.squareup.kotlinpoet.AnnotationSpec import com.squareup.kotlinpoet.ClassName import com.squareup.kotlinpoet.CodeBlock import com.squareup.kotlinpoet.FileSpec import com.squareup.kotlinpoet.FunSpec import com.squareup.kotlinpoet.INT import com.squareup.kotlinpoet.KModifier import com.squareup.kotlinpoet.ParameterSpec import com.squareup.kotlinpoet.TypeSpec import com.uber.artist.api.KotlinTrait import com.uber.artist.api.KotlinTraitService import com.uber.artist.api.KotlinTypeNames import com.uber.artist.api.KotlinViewStencil import com.uber.artist.api.KotlinViewStencilService import java.io.File class KotlinArtistCodeGenerator : ArtistCodeGenerator() { override val viewStencils: Set get() = KotlinViewStencilService.newInstance().getStencils() override val traits: Set get() = KotlinTraitService.newInstance().get() override val globalTraits: Set> get() = KotlinViewStencilService.newInstance().getGlobalTraits() override fun generateFileSpecFor(stencil: KotlinViewStencil, viewPackageName: String, typeSpecBuilder: TypeSpec.Builder): FileSpec { val typeSpec = typeSpecBuilder.build() var builder = FileSpec.builder(viewPackageName, typeSpec.name ?: throw IllegalStateException("No name for type: $typeSpec")) for ((extensionFunctionAlias, alias) in stencil.extensionFunctionToAlias()) { builder = builder.addAliasedImport(extensionFunctionAlias.toClassName(), "", alias) } return builder.addType(typeSpec).build() } fun AliasTypeNames.Rx.Companion.ExtensionFunctionAlias.toClassName(): ClassName { return ClassName(this.className.packageName, this.methodName) } override fun generateTypeSpecFor(stencil: KotlinViewStencil, rPackageName: String, traitMap: Map, KotlinTrait>, superinterfaceClassName: String?): TypeSpec.Builder { val rClass = ClassName(rPackageName, "R") val typeBuilder = TypeSpec.classBuilder(stencil.name()) .addModifiers(KModifier.OPEN) .superclass(stencil.sourceType) superinterfaceClassName?.let { typeBuilder.addSuperinterface(superinterface(superinterfaceClassName)) } generateConstructorsFor(stencil, typeBuilder, rClass) val initMethod = createInitBuilderFor(stencil, typeBuilder) stencil.traits() .map { traitName -> traitMap[traitName] } .forEach { it?.generateFor(typeBuilder, initMethod, rClass, stencil.name()) } typeBuilder.addFunction(initMethod.build()) stencil.typeHook(typeBuilder) return typeBuilder } override fun createInitBuilderFor(stencil: KotlinViewStencil, type: TypeSpec.Builder): FunSpec.Builder { return FunSpec.builder("init") .addAnnotation(KotlinTypeNames.Annotations.CallSuper) .addModifiers(KModifier.PROTECTED, KModifier.OPEN) .addParameter(ParameterSpec.builder("context", KotlinTypeNames.Android.Context) .build()) .addParameter(ParameterSpec.builder("attrs", KotlinTypeNames.Android.AttributeSet.copy(nullable = true)) .build()) .addParameter(ParameterSpec.builder("defStyleAttr", INT) .addAnnotation(KotlinTypeNames.Annotations.AttrRes) .build()) .addParameter(ParameterSpec.builder("defStyleRes", INT) .addAnnotation(KotlinTypeNames.Annotations.StyleRes) .build()) .also { stencil.initMethodHook(type, it) } } override fun generateConstructorsFor(stencil: KotlinViewStencil, type: TypeSpec.Builder, rClass: ClassName) { val paramContext = ParameterSpec.builder("context", KotlinTypeNames.Android.Context) .build() val paramAttrs = ParameterSpec.builder("attrs", KotlinTypeNames.Android.AttributeSet.copy(nullable = true)) .defaultValue("null") .build() val paramDefStyleAttr = ParameterSpec.builder("defStyleAttr", INT) .addAnnotation(KotlinTypeNames.Annotations.AttrRes) .defaultValue(stencil.defaultAttrRes?.let { if ("." in it) { CodeBlock.of(it) } else { CodeBlock.of("%T.attr.$it", rClass) } } ?: CodeBlock.of("0") ) .build() val paramDefStyleRes = ParameterSpec.builder("defStyleRes", INT) .addAnnotation(KotlinTypeNames.Annotations.StyleRes) .defaultValue("0") .build() val params = listOf(paramContext, paramAttrs, paramDefStyleAttr, paramDefStyleRes) val superConstructorArgs = listOf("context", "attrs", "defStyleAttr", "defStyleRes") val ctorOverloadsCount = stencil.constructorCount.coerceAtMost(3) val overloadsConstructor = FunSpec.constructorBuilder() .addAnnotation(JvmOverloads::class) .addParameters(params.subList(0, ctorOverloadsCount)) .callSuperConstructor(*superConstructorArgs.subList(0, ctorOverloadsCount).toTypedArray()) .addStatement(initStatement(ctorOverloadsCount)) .build() val targetApiConstructor = FunSpec.constructorBuilder() .addAnnotation(AnnotationSpec.builder(KotlinTypeNames.Annotations.TargetApi) .addMember("%T.VERSION_CODES.LOLLIPOP", ClassName("android.os", "Build")) .build()) .addParameters(params) .callSuperConstructor(*superConstructorArgs.toTypedArray()) .addStatement(initStatement(4)) .build() type .addFunction(overloadsConstructor) .apply { if (stencil.constructorCount > 3) addFunction(targetApiConstructor) } } override fun superinterface(className: String) = ClassName( className.substringBeforeLast('.'), className.substringAfterLast('.') ) override fun writeFile(fileSpec: FileSpec, outputDir: File) { fileSpec.writeTo(outputDir) } override fun writeFileWithFormatting(fileSpec: FileSpec, outputDir: File, outputType: TypeSpec.Builder, packageName: String) { fileSpec.writeTo(outputDir) } } ================================================ FILE: artist-core/src/test/kotlin/com/uber/artist/ArtistTest.kt ================================================ package com.uber.artist import com.google.common.io.Files import com.google.common.truth.Truth.assertAbout import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertWithMessage import com.google.testing.compile.JavaFileObjects.forSourceString import com.google.testing.compile.JavaSourceSubjectFactory.javaSource import com.squareup.javapoet.ClassName import com.squareup.javapoet.MethodSpec import com.squareup.javapoet.TypeSpec import com.uber.artist.api.JavaTrait import com.uber.artist.api.JavaViewStencil import org.junit.Test import javax.lang.model.element.Modifier class ArtistTest { companion object { const val TEST_PACKAGE_NAME = "foo.bar" val TRAITS = setOf(TestTrait()) const val IMAGE_VIEW_SOURCE_NO_TRAITS = """package foo.bar; import android.content.Context; import android.util.AttributeSet; import android.widget.ImageView; import androidx.annotation.AttrRes; import androidx.annotation.CallSuper; import androidx.annotation.Nullable; import androidx.annotation.StyleRes; public class MyImageView extends ImageView { public MyImageView(Context context) { this(context, null); } public MyImageView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public MyImageView(Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) { super(context, attrs, defStyleAttr); init(context, attrs, defStyleAttr, 0); } @CallSuper protected void init( Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {} } """ val IMAGE_VIEW_WITH_TEST_TRAIT = """package foo.bar; import android.content.Context; import android.util.AttributeSet; import android.widget.ImageView; import androidx.annotation.AttrRes; import androidx.annotation.CallSuper; import androidx.annotation.Nullable; import androidx.annotation.StyleRes; import java.lang.String; public class MyImageView extends ImageView { public MyImageView(Context context) { this(context, null); } public MyImageView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public MyImageView(Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) { super(context, attrs, defStyleAttr); init(context, attrs, defStyleAttr, 0); } public String testMethod() { return "foo"; } @CallSuper protected void init( Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {} } """ } @Test fun testArtist_withNoTraits_shouldGenerateViews() { val outputDir = Files.createTempDir() val stencils: Set = setOf( JavaViewStencil("android.widget.Button", 3), JavaViewStencil("android.widget.ImageView", 3), JavaViewStencil("android.widget.TextView", 3)) JavaArtistCodeGenerator().generateViewsForStencils(stencils, TRAITS, emptySet(), outputDir, TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, null, "My", true) val viewOutputDir = outputDir.resolve(TEST_PACKAGE_NAME.replace('.', '/')) val viewNames = viewOutputDir.listFiles() .map { it.name } .toList() assertThat(viewNames).containsExactly("MyButton.java", "MyImageView.java", "MyTextView.java") } @Test fun testArtist_withNoTraits_shouldGenerateExpectedSource() { val outputDir = Files.createTempDir() val stencils: Set = setOf( JavaViewStencil("android.widget.ImageView", 3)) JavaArtistCodeGenerator().generateViewsForStencils(stencils, TRAITS, emptySet(), outputDir, TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, null, "My", true) val viewOutputDir = outputDir.resolve(TEST_PACKAGE_NAME.replace('.', '/')) assertWithMessage("$viewOutputDir does not exist").that(viewOutputDir.exists()).isTrue() val generatedFileName = viewOutputDir.listFiles().first() val generatedViewContent = generatedFileName.readText() assertAbout(javaSource()) .that(forSourceString("$TEST_PACKAGE_NAME.MyImageView", generatedViewContent)) .compilesWithoutError() assertWithMessage("$generatedFileName did not match expected file content") .that(generatedViewContent) .isEqualTo(IMAGE_VIEW_SOURCE_NO_TRAITS) } @Test fun testArtist_withAddedTrait_shouldGenerateExpectedSource() { val outputDir = Files.createTempDir() val stencils: Set = setOf( JavaViewStencil("android.widget.ImageView", 3, addedTraits = *arrayOf(TestTrait::class.java))) JavaArtistCodeGenerator().generateViewsForStencils(stencils, TRAITS, emptySet(), outputDir, TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, null, "My", true) val viewOutputDir = outputDir.resolve(TEST_PACKAGE_NAME.replace('.', '/')) assertWithMessage("$viewOutputDir does not exist").that(viewOutputDir.exists()).isTrue() val generatedFileName = viewOutputDir.listFiles().first() val generatedViewContent = generatedFileName.readText() assertAbout(javaSource()) .that(forSourceString("$TEST_PACKAGE_NAME.MyImageView", generatedViewContent)) .compilesWithoutError() assertWithMessage("$generatedFileName did not match expected file content") .that(generatedViewContent) .isEqualTo(IMAGE_VIEW_WITH_TEST_TRAIT) } @Test fun testArtist_withGlobalTrait_shouldGenerateExpectedSource() { val outputDir = Files.createTempDir() val globalTraits: Set> = setOf(TestTrait::class.java) val stencils: Set = setOf( JavaViewStencil("android.widget.ImageView", 3)) JavaArtistCodeGenerator().generateViewsForStencils(stencils, TRAITS, globalTraits, outputDir, TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, null, "My", true) val viewOutputDir = outputDir.resolve(TEST_PACKAGE_NAME.replace('.', '/')) assertWithMessage("$viewOutputDir does not exist").that(viewOutputDir.exists()).isTrue() val generatedFileName = viewOutputDir.listFiles().first() val generatedViewContent = generatedFileName.readText() assertAbout(javaSource()) .that(forSourceString("$TEST_PACKAGE_NAME.MyImageView", generatedViewContent)) .compilesWithoutError() assertWithMessage("$generatedFileName did not match expected file content") .that(generatedViewContent) .isEqualTo(IMAGE_VIEW_WITH_TEST_TRAIT) } class TestTrait : JavaTrait { override fun generateFor(type: TypeSpec.Builder, initMethod: MethodSpec.Builder, rClass: ClassName, sourceType: String) { type.addMethod(MethodSpec.methodBuilder("testMethod") .addModifiers(Modifier.PUBLIC) .returns(ClassName.get(String::class.java)) .addStatement("return \$S", "foo") .build()) } } } ================================================ FILE: artist-traits/build.gradle ================================================ apply plugin: "java-library" apply plugin: "org.jetbrains.kotlin.jvm" apply plugin: "org.jetbrains.kotlin.kapt" sourceCompatibility = JavaVersion.VERSION_1_7 targetCompatibility = JavaVersion.VERSION_1_7 dependencies { kapt deps.apt.autoService compileOnly deps.apt.autoService api project(":artist-api") implementation deps.kotlin.stdLibJdk7 } if (rootProject.projectDir.name != "buildSrc") { apply from: rootProject.file('gradle/gradle-mvn-push.gradle') } ================================================ FILE: artist-traits/gradle.properties ================================================ # # Copyright (C) 2017. Uber Technologies # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # POM_NAME=artist-traits POM_ARTIFACT_ID=artist-traits POM_PACKAGING=jar ================================================ FILE: artist-traits/src/main/kotlin/com/uber/artist/traits/JavaForegroundTrait.kt ================================================ /* * Copyright (C) 2017. Uber Technologies * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.uber.artist.traits import com.google.auto.service.AutoService import com.squareup.javapoet.AnnotationSpec import com.squareup.javapoet.ClassName import com.squareup.javapoet.FieldSpec import com.squareup.javapoet.MethodSpec import com.squareup.javapoet.TypeName import com.squareup.javapoet.TypeSpec import com.uber.artist.api.JavaTrait import com.uber.artist.api.Trait import com.uber.artist.api.TypeNames import javax.lang.model.element.Modifier /** * This [Trait] ports [FrameLayout]'s foreground functionality to other views. In order to use this, the module that * applies that [Artist] plugin must declare the ForegroundView styleable in res/values/attrs_foreground_view.xml. * * * * * * * * * */ @AutoService(JavaTrait::class) class JavaForegroundTrait : JavaTrait { override fun generateFor( type: TypeSpec.Builder, initMethod: MethodSpec.Builder, rClass: ClassName, sourceType: String) { val isLayout = sourceType.endsWith("Layout") // The field type.addField(TypeNames.Android.Drawable, "foreground", Modifier.PRIVATE) if (isLayout) { type.addField(FieldSpec.builder(TypeNames.Android.Rect, "selfBounds", Modifier.PRIVATE, Modifier.FINAL) .initializer("new \$T()", TypeNames.Android.Rect) .build()) type.addField(FieldSpec.builder(TypeNames.Android.Rect, "overlayBounds", Modifier.PRIVATE, Modifier.FINAL) .initializer("new \$T()", TypeNames.Android.Rect) .build()) type.addField(FieldSpec.builder(TypeName.BOOLEAN, "foregroundInPadding", Modifier.PRIVATE) .initializer("true") .build()) type.addField(FieldSpec.builder(TypeName.BOOLEAN, "foregroundBoundsChanged", Modifier.PRIVATE) .initializer("false") .build()) type.addField(FieldSpec.builder(TypeName.INT, "foregroundGravity", Modifier.PRIVATE) .initializer("\$T.FILL", TypeNames.Android.Gravity) .build()) } // Pull out the value initMethod.addStatement( "\$T foregroundTA = context.obtainStyledAttributes(attrs, \$T.styleable.ForegroundView)", TypeNames.Android.TypedArray, rClass) initMethod.addStatement( "final \$T localForeground = foregroundTA.getDrawable(\$T.styleable.ForegroundView_android_foreground)", TypeNames.Android.Drawable, rClass) initMethod.beginControlFlow("if (localForeground != null)") initMethod.addCode("//noinspection AndroidLintNewApi\n") initMethod.addStatement("setForeground(localForeground)") initMethod.endControlFlow() if (isLayout) { initMethod.addStatement( "foregroundGravity = foregroundTA.getInt(\$T.styleable.ForegroundView_android_foregroundGravity, " + "foregroundGravity)", rClass) initMethod.addStatement( "foregroundInPadding = foregroundTA.getBoolean(" + "\$T.styleable.ForegroundView_foregroundInsidePadding, " + "true)", rClass) } initMethod.addStatement("foregroundTA.recycle()") val onSizeChangedMethod = MethodSpec.methodBuilder("onSizeChanged") .addAnnotation(Override::class.java) .addModifiers(Modifier.PROTECTED) .addParameter(TypeName.INT, "w") .addParameter(TypeName.INT, "h") .addParameter(TypeName.INT, "oldw") .addParameter(TypeName.INT, "oldh") .addStatement("super.onSizeChanged(w, h, oldw, oldh)") if (isLayout) { onSizeChangedMethod.addStatement("foregroundBoundsChanged = true") } else { onSizeChangedMethod.beginControlFlow("if (foreground != null)") .addStatement("foreground.setBounds(0, 0, w, h)") .endControlFlow() } type.addMethod(onSizeChangedMethod.build()) if (sourceType.endsWith("ImageView")) { type.addMethod(MethodSpec.methodBuilder("hasOverlappingRendering") .addAnnotation(Override::class.java) .addModifiers(Modifier.PUBLIC) .returns(TypeName.BOOLEAN) .addStatement("return false") .build()) } if (isLayout) { type.addMethod(MethodSpec.methodBuilder("getForegroundGravity") .addJavadoc("""Describes how the foreground is positioned. @return foreground gravity. @see #setForegroundGravity(int) """) .addAnnotation(AnnotationSpec.builder(SuppressWarnings::class.java).addMember("value", "\$S", "MissingOverride").build()) .addModifiers(Modifier.PUBLIC) .returns(TypeName.INT) .addStatement("return foregroundGravity") .build()) type.addMethod(MethodSpec.methodBuilder("setForegroundGravity") .addJavadoc("""Describes how the foreground is positioned. Defaults to START and TOP. @param foregroundGravity See {@link android.view.Gravity} @see #getForegroundGravity() """) .addAnnotation(AnnotationSpec.builder(SuppressWarnings::class.java).addMember("value", "\$S", "MissingOverride").build()) .addModifiers(Modifier.PUBLIC) .addParameter(TypeName.INT, "foregroundGravity") .beginControlFlow("if (this.foregroundGravity != foregroundGravity)") .beginControlFlow("if ((foregroundGravity & \$T.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0)", TypeNames.Android.Gravity) .addStatement("foregroundGravity |= \$T.START", TypeNames.Android.GravityCompat) .endControlFlow() .beginControlFlow("if ((foregroundGravity & \$T.VERTICAL_GRAVITY_MASK) == 0)", TypeNames.Android.Gravity) .addStatement("foregroundGravity |= \$T.TOP", TypeNames.Android.Gravity) .endControlFlow() .addStatement("this.foregroundGravity = foregroundGravity") .beginControlFlow("if (this.foregroundGravity == \$T.FILL && foreground != null)", TypeNames.Android.Gravity) .addStatement("\$T padding = new \$T()", TypeNames.Android.Rect, TypeNames.Android.Rect) .addStatement("foreground.getPadding(padding)") .endControlFlow() .addStatement("requestLayout()") .endControlFlow() .build()) } type.addMethod(MethodSpec.methodBuilder("verifyDrawable") .addAnnotation(Override::class.java) .addModifiers(Modifier.PROTECTED) .returns(TypeName.BOOLEAN) .addParameter(TypeNames.Android.Drawable, "who") .addStatement("return super.verifyDrawable(who) || (who == foreground)") .build()) type.addMethod(MethodSpec.methodBuilder("jumpDrawablesToCurrentState") .addAnnotation(Override::class.java) .addModifiers(Modifier.PUBLIC) .addStatement("super.jumpDrawablesToCurrentState()") .beginControlFlow("if (foreground != null)") .addStatement("foreground.jumpToCurrentState()") .endControlFlow() .build()) type.addMethod(MethodSpec.methodBuilder("drawableStateChanged") .addAnnotation(Override::class.java) .addModifiers(Modifier.PROTECTED) .addStatement("super.drawableStateChanged()") .beginControlFlow("if (foreground != null && foreground.isStateful())") .addStatement("foreground.setState(getDrawableState())") .endControlFlow() .build()) type.addMethod(MethodSpec.methodBuilder("getForeground") .addJavadoc("""Returns the drawable used as the foreground of this view. The foreground drawable, if non-null, is always drawn on top of the children. @return A Drawable or null if no foreground was set. """) .addAnnotation(AnnotationSpec.builder(SuppressWarnings::class.java).addMember("value", "\$S", "MissingOverride").build()) .addModifiers(Modifier.PUBLIC) .returns(TypeNames.Android.Drawable) .addStatement("return foreground") .build()) val setForegroundMethod = MethodSpec.methodBuilder("setForeground") .addJavadoc("""Supply a Drawable that is to be rendered on top of all of the child views in this layout. Any padding in the Drawable will be taken into account by ensuring that the children are inset to be placed inside of the padding area. @param drawable The Drawable to be drawn on top of the children. """) .addAnnotation(AnnotationSpec.builder(SuppressWarnings::class.java).addMember("value", "\$S", "MissingOverride").build()) .addAnnotation(AnnotationSpec.builder(ClassName.get("android.annotation", "SuppressLint")) .addMember("value", "\"NewApi\"") .build()) .addModifiers(Modifier.PUBLIC) .addParameter(TypeNames.Android.Drawable, "drawable") .beginControlFlow("if (foreground != drawable)") .beginControlFlow("if (foreground != null)") .addStatement("foreground.setCallback(null)") .addStatement("unscheduleDrawable(foreground)") .endControlFlow() .addStatement("foreground = drawable") .beginControlFlow("if (drawable != null)") if (!isLayout) { setForegroundMethod.addStatement("foreground.setBounds(0, 0, getWidth(), getHeight())") } setForegroundMethod.addStatement("setWillNotDraw(false)") .addStatement("drawable.setCallback(this)") .beginControlFlow("if (drawable.isStateful())") .addStatement("drawable.setState(getDrawableState())") .endControlFlow() if (isLayout) { setForegroundMethod.beginControlFlow("if (foregroundGravity == \$T.FILL)", TypeNames.Android.Gravity) setForegroundMethod.addStatement("\$T padding = new \$T()", TypeNames.Android.Rect, TypeNames.Android.Rect) setForegroundMethod.addStatement("drawable.getPadding(padding)") setForegroundMethod.endControlFlow() } setForegroundMethod.nextControlFlow("else") .addStatement("setWillNotDraw(true)") .endControlFlow() if (isLayout) { setForegroundMethod.addStatement("requestLayout()") } setForegroundMethod.addStatement("invalidate()") .endControlFlow() type.addMethod(setForegroundMethod.build()) if (isLayout) { type.addMethod(MethodSpec.methodBuilder("onLayout") .addAnnotation(Override::class.java) .addModifiers(Modifier.PROTECTED) .addParameter(TypeName.BOOLEAN, "changed") .addParameter(TypeName.INT, "left") .addParameter(TypeName.INT, "top") .addParameter(TypeName.INT, "right") .addParameter(TypeName.INT, "bottom") .addStatement("super.onLayout(changed, left, top, right, bottom)") .beginControlFlow("if (changed)") .addStatement("foregroundBoundsChanged = true") .endControlFlow() .build()) } val drawMethod = MethodSpec.methodBuilder("draw") .addAnnotation(Override::class.java) .addModifiers(Modifier.PUBLIC) .addParameter(TypeNames.Android.Canvas, "canvas") .addStatement("super.draw(canvas)") .beginControlFlow("if (foreground != null)") if (isLayout) { drawMethod.addStatement("final \$T localForeground = foreground", TypeNames.Android.Drawable) .beginControlFlow("if (foregroundBoundsChanged)") .addStatement("foregroundBoundsChanged = false") .addStatement("final \$T localSelfBounds = selfBounds", TypeNames.Android.Rect) .addStatement("final \$T localOverlayBounds = overlayBounds", TypeNames.Android.Rect) .addStatement("final int w = getRight() - getLeft()") .addStatement("final int h = getBottom() - getTop()") .beginControlFlow("if (foregroundInPadding)") .addStatement("localSelfBounds.set(0, 0, w, h)") .nextControlFlow("else") .addStatement("localSelfBounds.set(getPaddingLeft(), getPaddingTop(), w - getPaddingRight(), h - " + "getPaddingBottom())") .endControlFlow() .addStatement("\$T.apply(foregroundGravity, localForeground.getIntrinsicWidth(), localForeground" + ".getIntrinsicHeight(), localSelfBounds, localOverlayBounds)", TypeNames.Android.Gravity) .addStatement("localForeground.setBounds(localOverlayBounds)") .endControlFlow() .addStatement("localForeground.draw(canvas)") } else { drawMethod.addStatement("foreground.draw(canvas)") } drawMethod.endControlFlow() type.addMethod(drawMethod.build()) type.addMethod(MethodSpec.methodBuilder("drawableHotspotChanged") .addAnnotation(AnnotationSpec.builder(TypeNames.Annotations.TargetApi) .addMember("value", "\$L", "android.os.Build.VERSION_CODES.LOLLIPOP") .build()) .addAnnotation(Override::class.java) .addModifiers(Modifier.PUBLIC) .addParameter(TypeName.FLOAT, "x") .addParameter(TypeName.FLOAT, "y") .addStatement("super.drawableHotspotChanged(x, y)") .beginControlFlow("if (foreground != null)") .addStatement("foreground.setHotspot(x, y)") .endControlFlow() .build()) } } ================================================ FILE: artist-traits/src/main/kotlin/com/uber/artist/traits/JavaSuppressNullabilityInitializerTrait.kt ================================================ package com.uber.artist.traits import com.google.auto.service.AutoService import com.squareup.javapoet.AnnotationSpec import com.squareup.javapoet.ClassName import com.squareup.javapoet.MethodSpec import com.squareup.javapoet.TypeSpec.Builder import com.uber.artist.api.JavaTrait @AutoService(JavaTrait::class) class JavaSuppressNullabilityInitializerTrait : JavaTrait { override fun generateFor( type: Builder, initMethod: MethodSpec.Builder, rClass: ClassName, sourceType: String) { initMethod.addAnnotation(AnnotationSpec.builder(SuppressWarnings::class.java) .addMember("value", "\$S", "CheckNullabilityTypes") .build()) } } ================================================ FILE: artist-traits/src/main/kotlin/com/uber/artist/traits/JavaVisibilityTrait.kt ================================================ package com.uber.artist.traits import com.google.auto.service.AutoService import com.squareup.javapoet.ClassName import com.squareup.javapoet.MethodSpec import com.squareup.javapoet.TypeName import com.squareup.javapoet.TypeSpec.Builder import com.uber.artist.api.JavaTrait import com.uber.artist.api.TypeNames import javax.lang.model.element.Modifier @AutoService(JavaTrait::class) class JavaVisibilityTrait : JavaTrait { override fun generateFor( type: Builder, initMethod: MethodSpec.Builder, rClass: ClassName, sourceType: String) { // Visibility convenience methods arrayOf("visible", "invisible", "gone") .forEach { type.addMethod(createVisibilityConvenienceMethod(it)) } } private fun createVisibilityConvenienceMethod(type: String): MethodSpec { return MethodSpec.methodBuilder("is${type.capitalize()}") .addModifiers(Modifier.PUBLIC) .returns(TypeName.BOOLEAN) .addStatement("return getVisibility() == \$T.${type.toUpperCase()}", TypeNames.Android.View) .build() } } ================================================ FILE: artist-traits/src/main/kotlin/com/uber/artist/traits/KotlinForegroundTrait.kt ================================================ /* * Copyright (C) 2018. Uber Technologies * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.uber.artist.traits import com.google.auto.service.AutoService import com.squareup.kotlinpoet.AnnotationSpec import com.squareup.kotlinpoet.BOOLEAN import com.squareup.kotlinpoet.ClassName import com.squareup.kotlinpoet.FLOAT import com.squareup.kotlinpoet.FunSpec import com.squareup.kotlinpoet.INT import com.squareup.kotlinpoet.KModifier import com.squareup.kotlinpoet.PropertySpec import com.squareup.kotlinpoet.TypeSpec import com.uber.artist.api.KotlinTrait import com.uber.artist.api.KotlinTypeNames import com.uber.artist.api.Trait /** * This [Trait] ports [FrameLayout]'s foreground functionality to other views. In order to use this, the module that * applies that [Artist] plugin must declare the ForegroundView styleable in res/values/attrs_foreground_view.xml. * * * * * * * * * */ @AutoService(KotlinTrait::class) class KotlinForegroundTrait : KotlinTrait { override fun generateFor( type: TypeSpec.Builder, initMethod: FunSpec.Builder, rClass: ClassName, sourceType: String) { val isLayout = sourceType.endsWith("Layout") // The field type.addProperty(PropertySpec.builder("foreground", KotlinTypeNames.Android.Drawable.copy(nullable = true), KModifier.PRIVATE) .initializer("null") .mutable() .build()) if (isLayout) { type.addProperty(PropertySpec.builder("selfBounds", KotlinTypeNames.Android.Rect, KModifier.PRIVATE, KModifier.FINAL) .initializer("%T()", KotlinTypeNames.Android.Rect) .build()) type.addProperty(PropertySpec.builder("overlayBounds", KotlinTypeNames.Android.Rect, KModifier.PRIVATE, KModifier.FINAL) .initializer("%T()", KotlinTypeNames.Android.Rect) .build()) type.addProperty(PropertySpec.builder("foregroundInPadding", BOOLEAN, KModifier.PRIVATE) .initializer("true") .mutable() .build()) type.addProperty(PropertySpec.builder("foregroundBoundsChanged", BOOLEAN, KModifier.PRIVATE) .initializer("false") .mutable() .build()) type.addProperty(PropertySpec.builder("foregroundGravity", INT, KModifier.PRIVATE) .initializer("%T.FILL", KotlinTypeNames.Android.Gravity) .mutable() .build()) } // Pull out the value initMethod.apply { addStatement("val foregroundTA = context.obtainStyledAttributes(attrs, %T.styleable.ForegroundView)", rClass) beginControlFlow("foregroundTA.getDrawable(%T.styleable.ForegroundView_android_foreground)?.let", rClass) addCode("//noinspection AndroidLintNewApi\n") addStatement("setForeground(it)") endControlFlow() if (isLayout) { addStatement( "foregroundGravity = foregroundTA.getInt(%T.styleable.ForegroundView_android_foregroundGravity, foregroundGravity)", rClass) addStatement( "foregroundInPadding = foregroundTA.getBoolean(%T.styleable.ForegroundView_foregroundInsidePadding, true)", rClass) } addStatement("foregroundTA.recycle()") } val onSizeChangedMethod = FunSpec.builder("onSizeChanged") .addModifiers(KModifier.PROTECTED, KModifier.OPEN, KModifier.OVERRIDE) .addParameter("w", INT) .addParameter("h", INT) .addParameter("oldw", INT) .addParameter("oldh", INT) .addStatement("super.onSizeChanged(w, h, oldw, oldh)") if (isLayout) { onSizeChangedMethod.addStatement("foregroundBoundsChanged = true") } else { onSizeChangedMethod.addStatement("foreground?.setBounds(0, 0, w, h)") } type.addFunction(onSizeChangedMethod.build()) if (sourceType.endsWith("ImageView")) { type.addFunction(FunSpec.builder("hasOverlappingRendering") .addModifiers(KModifier.OPEN, KModifier.OVERRIDE) .returns(BOOLEAN) .addStatement("return false") .build()) } if (isLayout) { type.addFunction(FunSpec.builder("getForegroundGravity") .addKdoc("""Describes how the foreground is positioned. @return foreground gravity. @see #setForegroundGravity(int) """) .addAnnotation(AnnotationSpec.builder(SuppressWarnings::class.java).addMember("%S", "MissingOverride").build()) .addModifiers(KModifier.OPEN, KModifier.OVERRIDE) .returns(INT) .addStatement("return foregroundGravity") .build()) type.addFunction(FunSpec.builder("setForegroundGravity") .addKdoc("""Describes how the foreground is positioned. Defaults to START and TOP. @param foregroundGravity See {@link android.view.Gravity} @see #getForegroundGravity() """) .addAnnotation(AnnotationSpec.builder(SuppressWarnings::class.java).addMember("%S", "MissingOverride").build()) .addModifiers(KModifier.OPEN, KModifier.OVERRIDE) .addParameter("foregroundGravity", INT) .beginControlFlow("if (this.foregroundGravity != foregroundGravity)") .beginControlFlow("if ((foregroundGravity and %T.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0)", KotlinTypeNames.Android.Gravity) .addStatement("this.foregroundGravity = foregroundGravity.or(%T.START)", KotlinTypeNames.Android.GravityCompat) .endControlFlow() .beginControlFlow("if ((foregroundGravity and %T.VERTICAL_GRAVITY_MASK) == 0)", KotlinTypeNames.Android.Gravity) .addStatement("this.foregroundGravity = foregroundGravity.or(%T.TOP)", KotlinTypeNames.Android.Gravity) .endControlFlow() .beginControlFlow("if (this.foregroundGravity == %T.FILL && foreground != null)", KotlinTypeNames.Android.Gravity) .addStatement("val padding = %T()", KotlinTypeNames.Android.Rect) .addStatement("foreground?.getPadding(padding)") .endControlFlow() .addStatement("requestLayout()") .endControlFlow() .build()) } type.addFunction(FunSpec.builder("verifyDrawable") .addModifiers(KModifier.PROTECTED, KModifier.OPEN, KModifier.OVERRIDE) .returns(BOOLEAN) .addParameter("who", KotlinTypeNames.Android.Drawable) .addStatement("return super.verifyDrawable(who) || (who == foreground)") .build()) type.addFunction(FunSpec.builder("jumpDrawablesToCurrentState") .addModifiers(KModifier.OPEN, KModifier.OVERRIDE) .addStatement("super.jumpDrawablesToCurrentState()") .addStatement("foreground?.jumpToCurrentState()") .build()) type.addFunction(FunSpec.builder("drawableStateChanged") .addModifiers(KModifier.PROTECTED, KModifier.OPEN, KModifier.OVERRIDE) .addStatement("super.drawableStateChanged()") .beginControlFlow("if (foreground?.isStateful() ?: false)") .addStatement("foreground?.setState(getDrawableState())") .endControlFlow() .build()) type.addFunction(FunSpec.builder("getForeground") .addKdoc("""Returns the drawable used as the foreground of this view. The foreground drawable, if non-null, is always drawn on top of the children. @return A Drawable or null if no foreground was set. """) .addAnnotation(AnnotationSpec.builder(SuppressWarnings::class.java).addMember("%S", "MissingOverride").build()) .addModifiers(KModifier.OPEN, KModifier.OVERRIDE) .returns(KotlinTypeNames.Android.Drawable.copy(nullable = true)) .addStatement("return foreground") .build()) val setForegroundMethod = FunSpec.builder("setForeground") .addKdoc("""Supply a Drawable that is to be rendered on top of all of the child views in this layout. Any padding in the Drawable will be taken into account by ensuring that the children are inset to be placed inside of the padding area. @param drawable The Drawable to be drawn on top of the children. """) .addAnnotation(AnnotationSpec.builder(SuppressWarnings::class.java).addMember("%S", "MissingOverride").build()) .addAnnotation(AnnotationSpec.builder(ClassName("android.annotation", "SuppressLint")) .addMember("%S", "NewApi") .build()) .addModifiers(KModifier.OPEN, KModifier.OVERRIDE) .addParameter("drawable", KotlinTypeNames.Android.Drawable.copy(nullable = true)) .beginControlFlow("if (foreground != drawable)") .beginControlFlow("if (foreground != null)") .addStatement("foreground?.setCallback(null)") .addStatement("unscheduleDrawable(foreground)") .endControlFlow() .addStatement("foreground = drawable") .beginControlFlow("if (drawable != null)") if (!isLayout) { setForegroundMethod.addStatement("foreground?.setBounds(0, 0, getWidth(), getHeight())") } setForegroundMethod.addStatement("setWillNotDraw(false)") .addStatement("drawable.setCallback(this)") .beginControlFlow("if (drawable.isStateful())") .addStatement("drawable.setState(getDrawableState())") .endControlFlow() if (isLayout) { setForegroundMethod.beginControlFlow("if (foregroundGravity == %T.FILL)", KotlinTypeNames.Android.Gravity) setForegroundMethod.addStatement("val padding = %T()", KotlinTypeNames.Android.Rect) setForegroundMethod.addStatement("drawable.getPadding(padding)") setForegroundMethod.endControlFlow() } setForegroundMethod.nextControlFlow("else") .addStatement("setWillNotDraw(true)") .endControlFlow() if (isLayout) { setForegroundMethod.addStatement("requestLayout()") } setForegroundMethod.addStatement("invalidate()") .endControlFlow() type.addFunction(setForegroundMethod.build()) if (isLayout) { type.addFunction(FunSpec.builder("onLayout") .addModifiers(KModifier.PROTECTED, KModifier.OPEN, KModifier.OVERRIDE) .addParameter("changed", BOOLEAN) .addParameter("left", INT) .addParameter("top", INT) .addParameter("right", INT) .addParameter("bottom", INT) .addStatement("super.onLayout(changed, left, top, right, bottom)") .beginControlFlow("if (changed)") .addStatement("foregroundBoundsChanged = true") .endControlFlow() .build()) } val drawMethod = FunSpec.builder("draw") .addModifiers(KModifier.OPEN, KModifier.OVERRIDE) .addParameter("canvas", KotlinTypeNames.Android.Canvas) .addStatement("super.draw(canvas)") if (isLayout) { drawMethod .beginControlFlow("foreground?.let") .addStatement("val localForeground = it") .beginControlFlow("if (foregroundBoundsChanged)") .addStatement("foregroundBoundsChanged = false") .addStatement("val localSelfBounds: %T = selfBounds", KotlinTypeNames.Android.Rect) .addStatement("val localOverlayBounds: %T = overlayBounds", KotlinTypeNames.Android.Rect) .addStatement("val w: Int = getRight() - getLeft()") .addStatement("val h: Int = getBottom() - getTop()") .beginControlFlow("if (foregroundInPadding)") .addStatement("localSelfBounds.set(0, 0, w, h)") .nextControlFlow("else") .addStatement("localSelfBounds.set(getPaddingLeft(), getPaddingTop(), w - getPaddingRight(), h - " + "getPaddingBottom())") .endControlFlow() .addStatement("%T.apply(foregroundGravity, localForeground.getIntrinsicWidth(), localForeground" + ".getIntrinsicHeight(), localSelfBounds, localOverlayBounds)", KotlinTypeNames.Android.Gravity) .addStatement("localForeground.setBounds(localOverlayBounds)") .endControlFlow() .addStatement("localForeground.draw(canvas)") .endControlFlow() } else { drawMethod.addStatement("foreground?.draw(canvas)") } type.addFunction(drawMethod.build()) type.addFunction(FunSpec.builder("drawableHotspotChanged") .addAnnotation(AnnotationSpec.builder(KotlinTypeNames.Annotations.TargetApi) .addMember("%T.VERSION_CODES.LOLLIPOP", ClassName("android.os", "Build")) .build()) .addModifiers(KModifier.OPEN, KModifier.OVERRIDE) .addParameter("x", FLOAT) .addParameter("y", FLOAT) .addStatement("super.drawableHotspotChanged(x, y)") .addStatement("foreground?.setHotspot(x, y)") .build()) } } ================================================ FILE: artist-traits/src/main/kotlin/com/uber/artist/traits/KotlinSuppressNullabilityInitializerTrait.kt ================================================ /* * Copyright (C) 2018. Uber Technologies * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.uber.artist.traits import com.google.auto.service.AutoService import com.squareup.kotlinpoet.AnnotationSpec import com.squareup.kotlinpoet.ClassName import com.squareup.kotlinpoet.FunSpec import com.squareup.kotlinpoet.TypeSpec import com.uber.artist.api.KotlinTrait @AutoService(KotlinTrait::class) class KotlinSuppressNullabilityInitializerTrait : KotlinTrait { override fun generateFor( type: TypeSpec.Builder, initMethod: FunSpec.Builder, rClass: ClassName, sourceType: String) { initMethod.addAnnotation(AnnotationSpec.builder(SuppressWarnings::class.java) .addMember("%S", "CheckNullabilityTypes") .build()) } } ================================================ FILE: artist-traits/src/main/kotlin/com/uber/artist/traits/KotlinVisibilityTrait.kt ================================================ /* * Copyright (C) 2018. Uber Technologies * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.uber.artist.traits import com.google.auto.service.AutoService import com.squareup.kotlinpoet.BOOLEAN import com.squareup.kotlinpoet.ClassName import com.squareup.kotlinpoet.FunSpec import com.squareup.kotlinpoet.KModifier import com.squareup.kotlinpoet.TypeSpec import com.uber.artist.api.KotlinTrait import com.uber.artist.api.KotlinTypeNames @AutoService(KotlinTrait::class) class KotlinVisibilityTrait : KotlinTrait { override fun generateFor( type: TypeSpec.Builder, initMethod: FunSpec.Builder, rClass: ClassName, sourceType: String) { // Visibility convenience methods arrayOf("visible", "invisible", "gone") .forEach { type.addFunction(createVisibilityConvenienceMethod(it)) } } private fun createVisibilityConvenienceMethod(type: String): FunSpec { return FunSpec.builder("is${type.capitalize()}") .addModifiers(KModifier.OPEN) .returns(BOOLEAN) .addStatement("return getVisibility() == %T.${type.toUpperCase()}", KotlinTypeNames.Android.View) .build() } } ================================================ FILE: artist-traits-rx/build.gradle ================================================ apply plugin: "java-library" apply plugin: "org.jetbrains.kotlin.jvm" apply plugin: "org.jetbrains.kotlin.kapt" sourceCompatibility = JavaVersion.VERSION_1_7 targetCompatibility = JavaVersion.VERSION_1_7 dependencies { kapt deps.apt.autoService compileOnly deps.apt.autoService // Also requires RxBinding be on the classpath. Since it is an AAR, we can't include it here. api deps.apt.javapoet api deps.external.rxjava2 api deps.external.rxrelay2 api project(":artist-api") implementation deps.kotlin.stdLibJdk7 } if (rootProject.projectDir.name != "buildSrc") { apply from: rootProject.file('gradle/gradle-mvn-push.gradle') } ================================================ FILE: artist-traits-rx/gradle.properties ================================================ # # Copyright (C) 2017. Uber Technologies # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # POM_NAME=artist-traits-rx POM_ARTIFACT_ID=artist-traits-rx POM_PACKAGING=jar ================================================ FILE: artist-traits-rx/src/main/kotlin/com/uber/artist/traits/rx/JavaApiHelper.kt ================================================ /* * Copyright (C) 2017. Uber Technologies * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.uber.artist.traits.rx import com.squareup.javapoet.ClassName import com.squareup.javapoet.CodeBlock import com.squareup.javapoet.FieldSpec import com.squareup.javapoet.MethodSpec import com.squareup.javapoet.ParameterSpec import com.squareup.javapoet.ParameterizedTypeName import com.squareup.javapoet.TypeName import com.squareup.javapoet.TypeSpec import com.uber.artist.api.TypeNames import com.uber.artist.traits.rx.config.JavaArtistRxConfigService import javax.lang.model.element.Modifier data class JavaRxBindingInfo( val className: ClassName, val methodName: String, val methodDoc: String ) data class JavaSettableApi( val rxBindingInfo: JavaRxBindingInfo, val listenerType: TypeName, val listenerMethod: String, val observableType: TypeName, val listenerImpl: MethodSpec.Builder, val isStateful: Boolean = false, val relayInitializer: CodeBlock? = null, val setterCaveats: String? = null, val isUViewOverride: Boolean = false, val setListenerMethodAnnotations: List = emptyList()) data class JavaAdditiveApi( val rxBindingInfo: JavaRxBindingInfo, val observableType: TypeName, val isUViewOverride: Boolean = false ) private fun TypeName.irrelevantIfObject(): TypeName { val artistRxConfig = JavaArtistRxConfigService.newInstance().getArtistRxConfig() return if (this == TypeName.OBJECT.box()) artistRxConfig.rxBindingSignalEventTypeName() else this } fun addRxBindingApiForAdditive(type: TypeSpec.Builder, api: JavaAdditiveApi) { val artistRxConfig = JavaArtistRxConfigService.newInstance().getArtistRxConfig() type.addMethod(MethodSpec.methodBuilder(api.rxBindingInfo.methodName) .addJavadoc("${api.rxBindingInfo.methodDoc}\n") .apply { if (api.isUViewOverride) { addAnnotation(Override::class.java) } } .addModifiers(Modifier.PUBLIC) .returns(ParameterizedTypeName.get(JavaRxTypeNames.Rx.Observable, api.observableType.irrelevantIfObject())) .addCode(CodeBlock.builder() .add("return \$T.${api.rxBindingInfo.methodName}(this)", api.rxBindingInfo.className) .apply { if (api.observableType == TypeName.OBJECT.box()) { artistRxConfig.processRxBindingSignalEvent(this) } if (api.rxBindingInfo.methodName != "attachEvents") { // Safe to call, otherwise it'd be a recursive stack overflow artistRxConfig.processRxBindingStream(this, api.observableType.irrelevantIfObject()) } add(";") } .build()) .build()) } fun addRxBindingApiForSettable(type: TypeSpec.Builder, api: JavaSettableApi, isDebug: Boolean = true) { val artistRxConfig = JavaArtistRxConfigService.newInstance().getArtistRxConfig() val rxBindingClassName = api.rxBindingInfo.className val rxBindingMethod = api.rxBindingInfo.methodName val rxBindingMethodDoc = api.rxBindingInfo.methodDoc val isInitting = "${api.rxBindingInfo.methodName}IsInitting" val disposable = "${api.rxBindingInfo.methodName}Disposable" // clicksInitting type.addField(TypeName.BOOLEAN, isInitting, Modifier.PRIVATE) // internal relay type.addField( FieldSpec.builder(ParameterizedTypeName.get(if (api.isStateful) JavaRxTypeNames.Rx.BehaviorRelay else JavaRxTypeNames.Rx.PublishRelay, api.observableType.irrelevantIfObject()), rxBindingMethod, Modifier.PRIVATE) .addAnnotation(TypeNames.Annotations.Nullable).build()) type.addField(FieldSpec.builder(JavaRxTypeNames.Rx.Disposable, disposable, Modifier.PRIVATE).addAnnotation(TypeNames.Annotations.Nullable).build()) val consumer = TypeSpec.anonymousClassBuilder("") .addSuperinterface(ParameterizedTypeName.get(JavaRxTypeNames.Rx.Consumer, api.observableType.irrelevantIfObject())) .addMethod(api.listenerImpl.addAnnotation(Override::class.java).build()) .build() // Overridden and deprecated setOnClickListener method type.addMethod(MethodSpec.methodBuilder(api.listenerMethod) .addJavadoc(StringBuilder().apply { if (api.setterCaveats != null) { append(api.setterCaveats) append("\n\n") } }.append("@deprecated Use {@link #$rxBindingMethod()}\n").toString()) .addModifiers(Modifier.PUBLIC, Modifier.FINAL) .addAnnotation(Override::class.java) .addAnnotation(java.lang.Deprecated::class.java) .addParameter( ParameterSpec.builder(api.listenerType, "l", Modifier.FINAL).apply { api.setListenerMethodAnnotations.forEach { addAnnotation(it) } }.build() ) .beginControlFlow("if ($isInitting)") .addStatement("$isInitting = false") .addStatement("super.${api.listenerMethod}(l)") .nextControlFlow("else") .beginControlFlow("if ($disposable != null)") .addStatement("$disposable.dispose()") .addStatement("$disposable = null") .endControlFlow() .beginControlFlow("if (l != null)") .addCode(CodeBlock.builder() .add("$disposable = $rxBindingMethod()") .add(".subscribe(\$L);", consumer) .build()) .endControlFlow() .endControlFlow() .build()) type.addMethod(MethodSpec.methodBuilder(rxBindingMethod) .addJavadoc(rxBindingMethodDoc) .apply { if (api.isUViewOverride) { addAnnotation(Override::class.java) } } .addModifiers(Modifier.PUBLIC) .returns(ParameterizedTypeName.get(JavaRxTypeNames.Rx.Observable, api.observableType.irrelevantIfObject())) .beginControlFlow("if ($rxBindingMethod == null)") .addStatement("$isInitting = true") .apply { if (api.relayInitializer != null) { addCode("$rxBindingMethod = ", JavaRxTypeNames.Rx.BehaviorRelay) addCode(api.relayInitializer) addCode(";\n") } else { addStatement("$rxBindingMethod = \$T.create()", if (api.isStateful) JavaRxTypeNames.Rx.BehaviorRelay else JavaRxTypeNames.Rx.PublishRelay) } } .addCode(CodeBlock.builder() .add("\$T.$rxBindingMethod(this)", rxBindingClassName) .apply { if (api.observableType == TypeName.OBJECT.box()) { artistRxConfig.processRxBindingSignalEvent(this) } if (rxBindingMethod.contains("click", true)) { artistRxConfig.processTap(this) } } .addStatement("\n\t.subscribe($rxBindingMethod)") .build()) .endControlFlow() .addCode(CodeBlock.builder() .add("return $rxBindingMethod.hide()") .apply { artistRxConfig.processRxBindingStream(this, api.observableType.irrelevantIfObject()) } .add(";") .build()) .build()) } ================================================ FILE: artist-traits-rx/src/main/kotlin/com/uber/artist/traits/rx/JavaCheckableTrait.kt ================================================ /* * Copyright (C) 2017. Uber Technologies * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.uber.artist.traits.rx import com.google.auto.service.AutoService import com.squareup.javapoet.ClassName import com.squareup.javapoet.CodeBlock import com.squareup.javapoet.MethodSpec import com.squareup.javapoet.ParameterizedTypeName import com.squareup.javapoet.TypeName import com.squareup.javapoet.TypeSpec import com.uber.artist.api.JavaTrait import com.uber.artist.api.TypeNames import javax.lang.model.element.Modifier @AutoService(JavaTrait::class) class JavaCheckableTrait : JavaTrait { override fun generateFor( type: TypeSpec.Builder, initMethod: MethodSpec.Builder, rClass: ClassName, baseType: String) { val isTextView = baseType.endsWith("TextView") if (isTextView) { type.addField( ParameterizedTypeName.get(JavaRxTypeNames.Rx.BehaviorRelay, TypeName.BOOLEAN.box()), "checkedChanges", Modifier.PRIVATE) type.addMethod(MethodSpec.methodBuilder("ensureCheckedChanges") .addModifiers(Modifier.PRIVATE) .beginControlFlow("if (checkedChanges == null)") .addStatement("checkedChanges = \$T.create()", JavaRxTypeNames.Rx.BehaviorRelay) .endControlFlow() .build()) type.addMethod(MethodSpec.methodBuilder("checkedChanges") .addJavadoc("""@return an observable of booleans representing the checked state of this view. """) .addModifiers(Modifier.PUBLIC) .returns(ParameterizedTypeName.get(JavaRxTypeNames.Rx.Observable, TypeName.BOOLEAN.box())) .addStatement("ensureCheckedChanges()") .addStatement("return checkedChanges.hide()") .build()) type.addMethod(MethodSpec.methodBuilder("setChecked") .addModifiers(Modifier.PUBLIC) .addAnnotation(Override::class.java) .addParameter(TypeName.BOOLEAN, "val") .addStatement("super.setChecked(val)") .addStatement("ensureCheckedChanges()") .addStatement("checkedChanges.accept(val)") .build()) } else { addRxBindingApiForSettable(type, JavaSettableApi( JavaRxBindingInfo(JavaRxTypeNames.Rx.RxCompoundButton, "checkedChanges", """@return an observable of booleans representing the checked state of this view. """), ClassName.bestGuess("OnCheckedChangeListener"), "setOnCheckedChangeListener", TypeName.BOOLEAN.box(), MethodSpec.methodBuilder("accept") .addModifiers(Modifier.PUBLIC) .addParameter(TypeName.BOOLEAN.box(), "isChecked") .addStatement("l.onCheckedChanged($baseType.this, isChecked)"), true, CodeBlock.of("\$T.createDefault(isChecked())", JavaRxTypeNames.Rx.BehaviorRelay), setListenerMethodAnnotations = listOf(TypeNames.Annotations.Nullable) )) } } } ================================================ FILE: artist-traits-rx/src/main/kotlin/com/uber/artist/traits/rx/JavaRxTypeNames.kt ================================================ /* * Copyright (C) 2017. Uber Technologies * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.uber.artist.traits.rx import com.squareup.javapoet.ClassName class JavaRxTypeNames { class Rx { companion object { // Rx val Consumer = ClassName.get(io.reactivex.functions.Consumer::class.java) val Disposable = ClassName.get(io.reactivex.disposables.Disposable::class.java) val Function = ClassName.get(io.reactivex.functions.Function::class.java) val Observable = ClassName.get(io.reactivex.Observable::class.java) // RxRelay val BehaviorRelay = ClassName.get(com.jakewharton.rxrelay2.BehaviorRelay::class.java) val PublishRelay = ClassName.get(com.jakewharton.rxrelay2.PublishRelay::class.java) // RxBinding val RecyclerViewScrollEvent = ClassName.get("com.jakewharton.rxbinding3.recyclerview", "RecyclerViewScrollEvent") val RxView = ClassName.get("com.jakewharton.rxbinding3.view", "RxView") val RxCompoundButton = ClassName.get("com.jakewharton.rxbinding3.widget", "RxCompoundButton") val RxNestedScrollView = ClassName.get("com.jakewharton.rxbinding3.core", "RxNestedScrollView") val RxRecyclerView = ClassName.get("com.jakewharton.rxbinding3.recyclerview", "RxRecyclerView") val RxSearchView = ClassName.get("com.jakewharton.rxbinding3.appcompat", "RxSearchView") val RxSeekBar = ClassName.get("com.jakewharton.rxbinding3.widget", "RxSeekBar") val SeekBarChangeEvent = ClassName.get("com.jakewharton.rxbinding3.widget", "SeekBarChangeEvent") val SeekBarProgressChangeEvent = ClassName.get("com.jakewharton.rxbinding3.widget", "SeekBarProgressChangeEvent") val SeekBarStartChangeEvent = ClassName.get("com.jakewharton.rxbinding3.widget", "SeekBarStartChangeEvent") val RxSwipeRefreshLayout = ClassName.get("com.jakewharton.rxbinding3.swiperefreshlayout", "RxSwipeRefreshLayout") val RxTabLayout = ClassName.get("com.jakewharton.rxbinding3.material", "RxTabLayout") val RxTextView = ClassName.get("com.jakewharton.rxbinding3.widget", "RxTextView") val RxToolbar = ClassName.get("com.jakewharton.rxbinding3.widget", "RxToolbar") val RxViewPager = ClassName.get("com.jakewharton.rxbinding3.viewpager", "RxViewPager") val RxViewAttachEvent = ClassName.get("com.jakewharton.rxbinding3.view", "ViewAttachEvent") val RxViewAttachAttachedEvent = ClassName.get("com.jakewharton.rxbinding3.view", "ViewAttachAttachedEvent") val RxViewAttachDetachedEvent = ClassName.get("com.jakewharton.rxbinding3.view", "ViewAttachDetachedEvent") val SearchViewQueryTextEvent = ClassName.get("com.jakewharton.rxbinding3.appcompat", "SearchViewQueryTextEvent") val ViewScrollChangeEvent = ClassName.get("com.jakewharton.rxbinding3.view", "ViewScrollChangeEvent") } } } ================================================ FILE: artist-traits-rx/src/main/kotlin/com/uber/artist/traits/rx/JavaScrollableTrait.kt ================================================ /* * Copyright (C) 2017. Uber Technologies * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.uber.artist.traits.rx import com.google.auto.service.AutoService import com.squareup.javapoet.ClassName import com.squareup.javapoet.MethodSpec import com.squareup.javapoet.TypeSpec import com.uber.artist.api.JavaTrait import javax.lang.model.element.Modifier @AutoService(JavaTrait::class) class JavaScrollableTrait : JavaTrait { override fun generateFor( type: TypeSpec.Builder, initMethod: MethodSpec.Builder, rClass: ClassName, sourceType: String) { // ScrollView overrides if (sourceType.contains("ScrollView")) { addRxBindingApiForSettable(type, JavaSettableApi( JavaRxBindingInfo(JavaRxTypeNames.Rx.RxNestedScrollView, "scrollChangeEvents", """@return an observable of scroll-change events for this NestedScrollView. """), ClassName.bestGuess("OnScrollChangeListener"), "setOnScrollChangeListener", JavaRxTypeNames.Rx.ViewScrollChangeEvent, MethodSpec.methodBuilder("accept") .addModifiers(Modifier.PUBLIC) .addParameter(JavaRxTypeNames.Rx.ViewScrollChangeEvent, "event") .addStatement("l.onScrollChange($sourceType.this, event.getScrollX(), event" + ".getScrollY" + "(), event.getOldScrollX(), event.getOldScrollY())"))) } // RecyclerView overrides if (sourceType.contains("RecyclerView")) { addRxBindingApiForAdditive(type, JavaAdditiveApi( JavaRxBindingInfo(JavaRxTypeNames.Rx.RxRecyclerView, "scrollEvents", "@return an observable of scroll events on this RecyclerView"), JavaRxTypeNames.Rx.RecyclerViewScrollEvent)) } } } ================================================ FILE: artist-traits-rx/src/main/kotlin/com/uber/artist/traits/rx/JavaTextInputTrait.kt ================================================ /* * Copyright (C) 2017. Uber Technologies * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.uber.artist.traits.rx import com.google.auto.service.AutoService import com.squareup.javapoet.ClassName import com.squareup.javapoet.MethodSpec import com.squareup.javapoet.TypeSpec import com.uber.artist.api.JavaTrait @AutoService(JavaTrait::class) class JavaTextInputTrait : JavaTrait { override fun generateFor( type: TypeSpec.Builder, initMethod: MethodSpec.Builder, rClass: ClassName, sourceType: String) { // TextChanges addRxBindingApiForAdditive(type, JavaAdditiveApi( JavaRxBindingInfo(JavaRxTypeNames.Rx.RxTextView, "textChanges", """@return an observable of character sequences for text changes on this TextView."""), ClassName.get(CharSequence::class.java))) } } ================================================ FILE: artist-traits-rx/src/main/kotlin/com/uber/artist/traits/rx/JavaViewTrait.kt ================================================ /* * Copyright (C) 2017. Uber Technologies * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.uber.artist.traits.rx import com.google.auto.service.AutoService import com.squareup.javapoet.ClassName import com.squareup.javapoet.MethodSpec import com.squareup.javapoet.TypeName import com.squareup.javapoet.TypeSpec import com.uber.artist.api.JavaTrait import com.uber.artist.api.TypeNames import com.uber.artist.traits.rx.config.JavaArtistRxConfigService import javax.lang.model.element.Modifier @AutoService(JavaTrait::class) open class JavaViewTrait : JavaTrait { private val artistRxConfig by lazy { JavaArtistRxConfigService.newInstance().getArtistRxConfig() } override fun generateFor( type: TypeSpec.Builder, initMethod: MethodSpec.Builder, rClass: ClassName, sourceType: String) { clicks(type, sourceType) longClicks(type, sourceType) layoutChanges(type) } open fun clicks(type: TypeSpec.Builder, sourceType: String) { addRxBindingApiForSettable(type, JavaSettableApi( JavaRxBindingInfo(JavaRxTypeNames.Rx.RxView, "clicks", """@return an Observable of click events. The emitted value is unspecified and should only be used as notification. """), ClassName.bestGuess("OnClickListener"), "setOnClickListener", TypeName.OBJECT.box(), MethodSpec.methodBuilder("accept") .addModifiers(Modifier.PUBLIC) .addParameter(artistRxConfig.rxBindingSignalEventTypeName(), "ignored") .addStatement("l.onClick($sourceType.this)"), setListenerMethodAnnotations = listOf(TypeNames.Annotations.Nullable) )) } open fun longClicks(type: TypeSpec.Builder, sourceType: String) { addRxBindingApiForSettable(type, JavaSettableApi( JavaRxBindingInfo(JavaRxTypeNames.Rx.RxView, "longClicks", """@return an Observable of longclick events. The emitted value is unspecified and should only be used as notification. """), ClassName.bestGuess("OnLongClickListener"), "setOnLongClickListener", TypeName.OBJECT.box(), MethodSpec.methodBuilder("accept") .addModifiers(Modifier.PUBLIC) .addParameter(artistRxConfig.rxBindingSignalEventTypeName(), "ignored") .addStatement("l.onLongClick($sourceType.this)"), setListenerMethodAnnotations = listOf(TypeNames.Annotations.Nullable) )) } open fun layoutChanges(type: TypeSpec.Builder) { // Attach state changes observable addRxBindingApiForAdditive(type, JavaAdditiveApi( JavaRxBindingInfo( JavaRxTypeNames.Rx.RxView, "layoutChanges", "@return an observable which emits on layout changes. The emitted value is " + "unspecified and should only be used as notification."), TypeName.OBJECT.box())) } } ================================================ FILE: artist-traits-rx/src/main/kotlin/com/uber/artist/traits/rx/KotlinApiHelper.kt ================================================ /* * Copyright (C) 2018. Uber Technologies * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.uber.artist.traits.rx import AliasTypeNames.Rx.Companion.rxExtensionFunctionToAlias import com.squareup.kotlinpoet.AnnotationSpec import com.squareup.kotlinpoet.BOOLEAN import com.squareup.kotlinpoet.ClassName import com.squareup.kotlinpoet.CodeBlock import com.squareup.kotlinpoet.FunSpec import com.squareup.kotlinpoet.KModifier import com.squareup.kotlinpoet.ParameterSpec import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy import com.squareup.kotlinpoet.PropertySpec import com.squareup.kotlinpoet.TypeName import com.squareup.kotlinpoet.TypeSpec import com.squareup.kotlinpoet.asClassName import com.uber.artist.api.KotlinTypeNames import com.uber.artist.traits.rx.config.KotlinArtistRxConfigService data class KotlinRxBindingInfo( val className: ClassName, val methodName: String, val methodDoc: String ) fun KotlinRxBindingInfo.getRxAlias(): String? { val rxBindingClassName = className val rxBindingMethod = methodName val alias_keys = rxExtensionFunctionToAlias.filter { it.key.methodName == rxBindingMethod && it.key .className == rxBindingClassName }.keys.toList() val rx_alias = if (alias_keys.size > 0) { rxExtensionFunctionToAlias[alias_keys[0]] } else { null } return rx_alias } data class KotlinSettableApi( val rxBindingInfo: KotlinRxBindingInfo, val listenerType: TypeName, val listenerMethod: String, val observableType: TypeName, val listenerImpl: FunSpec.Builder, val isStateful: Boolean = false, val relayInitializer: CodeBlock? = null, val setterCaveats: String? = null, val isUViewOverride: Boolean = false, val setListenerMethodAnnotations: List = emptyList()) data class KotlinAdditiveApi( val rxBindingInfo: KotlinRxBindingInfo, val observableType: TypeName, val isUViewOverride: Boolean = false ) private fun TypeName.irrelevantIfObject(): TypeName { val artistRxConfig = KotlinArtistRxConfigService.newInstance().getArtistRxConfig() return if (this == KotlinTypeNames.Java.Object) artistRxConfig.rxBindingSignalEventTypeName() else this } fun addRxBindingApiForAdditive(type: TypeSpec.Builder, api: KotlinAdditiveApi) { val artistRxConfig = KotlinArtistRxConfigService.newInstance().getArtistRxConfig() val rx_alias = api.rxBindingInfo.getRxAlias() type.addFunction(FunSpec.builder(api.rxBindingInfo.methodName) .addKdoc("${api.rxBindingInfo.methodDoc}\n") .apply { if (api.isUViewOverride) { addModifiers(KModifier.OVERRIDE) } } .addModifiers(KModifier.OPEN) .returns(KotlinRxTypeNames.Rx.Observable.parameterizedBy(api.observableType.irrelevantIfObject())) .addCode(CodeBlock.builder() .apply { if (rx_alias != null) { add("return ${rx_alias}()") } else { add("return ${api.rxBindingInfo.methodName}()") } } .apply { if (api.observableType == KotlinTypeNames.Java.Object) { artistRxConfig.processRxBindingSignalEvent(this) } if (api.rxBindingInfo.methodName != "attachEvents") { // Safe to call, otherwise it'd be a recursive stack overflow artistRxConfig.processRxBindingStream(this, api.observableType.irrelevantIfObject()) } } .add("\n") .build()) .build()) } fun addRxBindingApiForSettable(type: TypeSpec.Builder, api: KotlinSettableApi, isDebug: Boolean = true) { val artistRxConfig = KotlinArtistRxConfigService.newInstance().getArtistRxConfig() val rxBindingClassName = api.rxBindingInfo.className val rxBindingMethod = api.rxBindingInfo.methodName val rxBindingMethodDoc = api.rxBindingInfo.methodDoc val isInitting = "${api.rxBindingInfo.methodName}IsInitting" val disposable = "${api.rxBindingInfo.methodName}Disposable" val rx_alias = api.rxBindingInfo.getRxAlias() // clicksInitting type.addProperty(PropertySpec.builder(isInitting, BOOLEAN, KModifier.PRIVATE) .mutable() .initializer("false") .build()) // internal relay val internalRelayTypeName = if (api.isStateful) KotlinRxTypeNames.Rx.BehaviorRelay else KotlinRxTypeNames.Rx.PublishRelay type.addProperty( PropertySpec.builder(rxBindingMethod, internalRelayTypeName.parameterizedBy(api.observableType.irrelevantIfObject()).copy(nullable = true), KModifier.PRIVATE) .mutable() .initializer("null") .build()) type.addProperty(PropertySpec.builder(disposable, KotlinRxTypeNames.Rx.Disposable.copy(nullable = true), KModifier.PRIVATE) .mutable() .initializer("null") .build()) val consumer = TypeSpec.anonymousClassBuilder() .addSuperinterface(KotlinRxTypeNames.Rx.Consumer.parameterizedBy(api.observableType.irrelevantIfObject())) .addFunction(api.listenerImpl.addModifiers(KModifier.OVERRIDE).build()) .build() // Overridden and deprecated setOnClickListener method type.addFunction(FunSpec.builder(api.listenerMethod) .addKdoc(StringBuilder().apply { if (api.setterCaveats != null) { append(api.setterCaveats) append("\n\n") } }.append("@deprecated Use [$rxBindingMethod]\n").toString()) .addModifiers(KModifier.FINAL, KModifier.OVERRIDE) .addAnnotation(AnnotationSpec.builder(Deprecated::class.java) .addMember("message = %S", "Use $rxBindingMethod()") .addMember("replaceWith = %T(%S)", ReplaceWith::class.asClassName(), "$rxBindingMethod()") .addMember("level = %T.ERROR", DeprecationLevel::class.asClassName()) .build()) .addParameter( ParameterSpec.builder("l", api.listenerType.copy(nullable = true)).apply { api.setListenerMethodAnnotations.forEach { addAnnotation(it) } }.build() ) .beginControlFlow("if ($isInitting)") .addStatement("$isInitting = false") .addStatement("super.${api.listenerMethod}(l)") .nextControlFlow("else") .addStatement("$disposable?.dispose()") .addStatement("$disposable = null") .beginControlFlow("if (l != null)") .addCode(CodeBlock.builder() .add("$disposable = $rxBindingMethod()") .add(".subscribe($consumer)\n") .build()) .endControlFlow() .endControlFlow() .build()) type.addFunction(FunSpec.builder(rxBindingMethod) .addKdoc(rxBindingMethodDoc) .apply { if (api.isUViewOverride) { addModifiers(KModifier.OVERRIDE) } } .addModifiers(KModifier.OPEN) .returns(KotlinRxTypeNames.Rx.Observable.parameterizedBy(api.observableType.irrelevantIfObject())) .beginControlFlow("if ($rxBindingMethod == null)") .addStatement("$isInitting = true") .apply { if (api.relayInitializer != null) { addCode("$rxBindingMethod = ", KotlinRxTypeNames.Rx.BehaviorRelay) addCode(api.relayInitializer) } else { addStatement("$rxBindingMethod = %T.create()", if (api.isStateful) KotlinRxTypeNames.Rx.BehaviorRelay else KotlinRxTypeNames.Rx.PublishRelay) } } .addCode(CodeBlock.builder() .apply { add("$rxBindingMethod?.let {\n") } .apply { if (rx_alias != null) { add("$rx_alias()") } else { add("%T.$rxBindingMethod(this)", rxBindingClassName) } } .apply { if (api.observableType == KotlinTypeNames.Java.Object) { artistRxConfig.processRxBindingSignalEvent(this) } if (rxBindingMethod.contains("click", true)) { artistRxConfig.processTap(this) } } .addStatement(".subscribe(it)") .addStatement(" }\n") .build()) .endControlFlow() .addCode(CodeBlock.builder() .add("return $rxBindingMethod?.hide()?") .apply { artistRxConfig.processRxBindingStream(this, api.observableType.irrelevantIfObject()) } .add(" ?: Observable.empty()") .build()) .build()) } ================================================ FILE: artist-traits-rx/src/main/kotlin/com/uber/artist/traits/rx/KotlinCheckableTrait.kt ================================================ /* * Copyright (C) 2018. Uber Technologies * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.uber.artist.traits.rx import com.google.auto.service.AutoService import com.squareup.kotlinpoet.BOOLEAN import com.squareup.kotlinpoet.ClassName import com.squareup.kotlinpoet.CodeBlock import com.squareup.kotlinpoet.FunSpec import com.squareup.kotlinpoet.KModifier import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy import com.squareup.kotlinpoet.PropertySpec import com.squareup.kotlinpoet.TypeSpec import com.uber.artist.api.KotlinTrait @AutoService(KotlinTrait::class) class KotlinCheckableTrait : KotlinTrait { override fun generateFor( type: TypeSpec.Builder, initMethod: FunSpec.Builder, rClass: ClassName, baseType: String) { val isTextView = baseType.endsWith("TextView") if (isTextView) { type.addProperty(PropertySpec.builder( "checkedChanges", KotlinRxTypeNames.Rx.BehaviorRelay.parameterizedBy(BOOLEAN).copy(nullable = true), KModifier.PRIVATE) .mutable() .initializer("null") .build()) type.addFunction(FunSpec.builder("ensureCheckedChanges") .addModifiers(KModifier.PRIVATE) .beginControlFlow("if (checkedChanges == null)") .addStatement("checkedChanges = %T.create()", KotlinRxTypeNames.Rx.BehaviorRelay) .endControlFlow() .build()) type.addFunction(FunSpec.builder("checkedChanges") .addKdoc("""@return an observable of booleans representing the checked state of this view. """) .addModifiers(KModifier.OPEN) .returns(KotlinRxTypeNames.Rx.Observable.parameterizedBy(BOOLEAN)) .addStatement("ensureCheckedChanges()") .addStatement("return checkedChanges!!.hide()") .build()) type.addFunction(FunSpec.builder("setChecked") .addModifiers(KModifier.OPEN, KModifier.OVERRIDE) .addParameter("value", BOOLEAN) .addStatement("super.setChecked(value)") .addStatement("ensureCheckedChanges()") .addStatement("checkedChanges!!.accept(value)") .build()) } else { addRxBindingApiForSettable(type, KotlinSettableApi( KotlinRxBindingInfo(KotlinRxTypeNames.Rx.RxCompoundButton, "checkedChanges", """@return an observable of booleans representing the checked state of this view. """), ClassName.bestGuess("android.widget.CompoundButton.OnCheckedChangeListener"), "setOnCheckedChangeListener", BOOLEAN, FunSpec.builder("accept") .addParameter("isChecked", BOOLEAN) .addStatement("l.onCheckedChanged(this@$baseType, isChecked)"), true, CodeBlock.of("%T.createDefault(isChecked())\n", KotlinRxTypeNames.Rx.BehaviorRelay) )) } } } ================================================ FILE: artist-traits-rx/src/main/kotlin/com/uber/artist/traits/rx/KotlinRxTypeNames.kt ================================================ /* * Copyright (C) 2018. Uber Technologies * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.uber.artist.traits.rx import com.jakewharton.rxrelay2.BehaviorRelay import com.jakewharton.rxrelay2.PublishRelay import com.squareup.kotlinpoet.ClassName import com.squareup.kotlinpoet.asClassName import io.reactivex.Observable import io.reactivex.disposables.Disposable import io.reactivex.functions.Consumer import io.reactivex.functions.Function class KotlinRxTypeNames { class Rx { companion object { // Rx val Consumer = Consumer::class.asClassName() val Disposable = Disposable::class.asClassName() val Function = Function::class.asClassName() val Observable = Observable::class.asClassName() // RxRelay val BehaviorRelay = BehaviorRelay::class.asClassName() val PublishRelay = PublishRelay::class.asClassName() // As a consequence of RxBinding2 migrating to RxBinding3 and replacing its static calls // with equivalent extension functions, its important that we do not interrupt any existing // code that was created with RxBinding2 in mind, namely the function names. This can mean // that function names share the same name as the extension function which is being used in // lieu of the original static function. //Ideally, we would come up with a better way of listing these classes so as to keep this DRY. // RxBinding val RecyclerViewScrollEvent = AliasTypeNames.Rx.RecyclerViewScrollEvent val RxView = AliasTypeNames.Rx.RxView val RxCompoundButton = AliasTypeNames.Rx.RxCompoundButton val RxNestedScrollView = AliasTypeNames.Rx.RxNestedScrollView val RxRecyclerView = AliasTypeNames.Rx.RxRecyclerView val RxSearchView = AliasTypeNames.Rx.RxSearchView val RxSeekBar = AliasTypeNames.Rx.RxSeekBar val SeekBarChangeEvent = AliasTypeNames.Rx.SeekBarChangeEvent val SeekBarProgressChangeEvent = AliasTypeNames.Rx.SeekBarProgressChangeEvent val SeekBarStartChangeEvent = AliasTypeNames.Rx.SeekBarStartChangeEvent val RxSwipeRefreshLayout = AliasTypeNames.Rx.RxSwipeRefreshLayout val RxTabLayout = AliasTypeNames.Rx.RxTabLayout val RxTextView = AliasTypeNames.Rx.RxTextView val RxToolbar = AliasTypeNames.Rx.RxToolbar val RxViewPager = AliasTypeNames.Rx.RxViewPager val RxViewAttachEvent = AliasTypeNames.Rx.RxViewAttachEvent val RxViewAttachAttachedEvent = AliasTypeNames.Rx.RxViewAttachAttachedEvent val RxViewAttachDetachedEvent = AliasTypeNames.Rx.RxViewAttachDetachedEvent val SearchViewQueryTextEvent = AliasTypeNames.Rx.SearchViewQueryTextEvent val ViewScrollChangeEvent = AliasTypeNames.Rx.ViewScrollChangeEvent } } } ================================================ FILE: artist-traits-rx/src/main/kotlin/com/uber/artist/traits/rx/KotlinScrollableTrait.kt ================================================ /* * Copyright (C) 2018. Uber Technologies * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.uber.artist.traits.rx import com.google.auto.service.AutoService import com.squareup.kotlinpoet.ClassName import com.squareup.kotlinpoet.FunSpec import com.squareup.kotlinpoet.TypeSpec import com.uber.artist.api.KotlinTrait @AutoService(KotlinTrait::class) class KotlinScrollableTrait : KotlinTrait { override fun generateFor( type: TypeSpec.Builder, initMethod: FunSpec.Builder, rClass: ClassName, sourceType: String) { // ScrollView overrides if (sourceType.contains("ScrollView")) { addRxBindingApiForSettable(type, KotlinSettableApi( KotlinRxBindingInfo(KotlinRxTypeNames.Rx.RxNestedScrollView, "scrollChangeEvents", """@return an observable of scroll-change events for this NestedScrollView. """), ClassName.bestGuess("androidx.core.widget.NestedScrollView.OnScrollChangeListener"), "setOnScrollChangeListener", KotlinRxTypeNames.Rx.ViewScrollChangeEvent, FunSpec.builder("accept") .addParameter("event", KotlinRxTypeNames.Rx.ViewScrollChangeEvent) .addStatement("l.onScrollChange(this@$sourceType, event.scrollX, event.scrollY, event.oldScrollX, event.oldScrollY)"))) } // RecyclerView overrides if (sourceType.contains("RecyclerView")) { addRxBindingApiForAdditive(type, KotlinAdditiveApi( KotlinRxBindingInfo(KotlinRxTypeNames.Rx.RxRecyclerView, "scrollEvents", "@return an observable of scroll events on this RecyclerView"), KotlinRxTypeNames.Rx.RecyclerViewScrollEvent)) } } } ================================================ FILE: artist-traits-rx/src/main/kotlin/com/uber/artist/traits/rx/KotlinTextInputTrait.kt ================================================ /* * Copyright (C) 2018. Uber Technologies * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.uber.artist.traits.rx import com.google.auto.service.AutoService import com.squareup.kotlinpoet.ClassName import com.squareup.kotlinpoet.FunSpec import com.squareup.kotlinpoet.TypeSpec import com.squareup.kotlinpoet.asClassName import com.uber.artist.api.KotlinTrait @AutoService(KotlinTrait::class) class KotlinTextInputTrait : KotlinTrait { override fun generateFor( type: TypeSpec.Builder, initMethod: FunSpec.Builder, rClass: ClassName, sourceType: String) { // TextChanges addRxBindingApiForAdditive(type, KotlinAdditiveApi( KotlinRxBindingInfo(KotlinRxTypeNames.Rx.RxTextView, "textChanges", """@return an observable of character sequences for text changes on this TextView."""), CharSequence::class.asClassName())) } } ================================================ FILE: artist-traits-rx/src/main/kotlin/com/uber/artist/traits/rx/KotlinViewTrait.kt ================================================ /* * Copyright (C) 2018. Uber Technologies * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.uber.artist.traits.rx import com.google.auto.service.AutoService import com.squareup.kotlinpoet.ClassName import com.squareup.kotlinpoet.FunSpec import com.squareup.kotlinpoet.TypeSpec import com.uber.artist.api.KotlinTrait import com.uber.artist.api.KotlinTypeNames import com.uber.artist.traits.rx.config.KotlinArtistRxConfigService @AutoService(KotlinTrait::class) open class KotlinViewTrait : KotlinTrait { private val artistRxConfig by lazy { KotlinArtistRxConfigService.newInstance().getArtistRxConfig() } override fun generateFor( type: TypeSpec.Builder, initMethod: FunSpec.Builder, rClass: ClassName, sourceType: String) { clicks(type, sourceType) longClicks(type, sourceType) layoutChanges(type) } open fun clicks(type: TypeSpec.Builder, sourceType: String) { addRxBindingApiForSettable(type, KotlinSettableApi( KotlinRxBindingInfo(KotlinRxTypeNames.Rx.RxView, "clicks", """@return an Observable of click events. The emitted value is unspecified and should only be used as notification. """), ClassName.bestGuess("android.view.View.OnClickListener"), "setOnClickListener", KotlinTypeNames.Java.Object, FunSpec.builder("accept") .addParameter("ignored", artistRxConfig.rxBindingSignalEventTypeName()) .addStatement("l.onClick(this@$sourceType)") )) } open fun longClicks(type: TypeSpec.Builder, sourceType: String) { addRxBindingApiForSettable(type, KotlinSettableApi( KotlinRxBindingInfo(KotlinRxTypeNames.Rx.RxView, "longClicks", """@return an Observable of longclick events. The emitted value is unspecified and should only be used as notification. """), ClassName.bestGuess("android.view.View.OnLongClickListener"), "setOnLongClickListener", KotlinTypeNames.Java.Object, FunSpec.builder("accept") .addParameter("ignored", artistRxConfig.rxBindingSignalEventTypeName()) .addStatement("l.onLongClick(this@$sourceType)") )) } open fun layoutChanges(type: TypeSpec.Builder) { // Attach state changes observable addRxBindingApiForAdditive(type, KotlinAdditiveApi( KotlinRxBindingInfo( KotlinRxTypeNames.Rx.RxView, "layoutChanges", "@return an observable which emits on layout changes. The emitted value is " + "unspecified and should only be used as notification."), KotlinTypeNames.Java.Object)) } } ================================================ FILE: artist-traits-rx/src/main/kotlin/com/uber/artist/traits/rx/config/ArtistRxConfig.kt ================================================ /* * Copyright (C) 2017. Uber Technologies * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.uber.artist.traits.rx.config /** * This configuration object describes various plugin points for rx-based traits. */ interface ArtistRxConfig { /** * Plugin point for generating additional code to invoke when a view has been tapped. */ fun processTap(codeBlockBuilder: CodeBlockType) {} /** * Plugin point for generating additional code to invoke when a view has attached to the window. */ fun processImpression(codeBlockBuilder: CodeBlockType) {} /** * Plugin point for generating additional code to invoke when a view has changed visibility. */ fun processVisibilityChanges(codeBlockBuilder: CodeBlockType) {} /** * Plugin point for generating additional code to modify an RxBinding stream. */ fun processRxBindingStream(codeBlockBuilder: CodeBlockType, streamTypeName: TypeNameType) {} /** * Plugin point for generating additional code to modify an RxBinding stream which notifies that something occurred. * This can be used along with rxBindingSignalEventTypeName() to map signal events to a different type. */ fun processRxBindingSignalEvent(codeBlockBuilder: CodeBlockType) {} /** * This defines the type to be used for RxBinding event signals. It can be changed if processRxBindingSignalEvent() * has been overridden to map the signal events to a new type. The default type is Object. */ fun rxBindingSignalEventTypeName(): TypeNameType } ================================================ FILE: artist-traits-rx/src/main/kotlin/com/uber/artist/traits/rx/config/ArtistRxConfigService.kt ================================================ /* * Copyright (C) 2017. Uber Technologies * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.uber.artist.traits.rx.config interface ArtistRxConfigService { /** * Gets the optionally overridden [ArtistRxConfig] implementation or the default. * * @return The located [ArtistRxConfig] or a default config if not provided. */ fun getArtistRxConfig(): ArtistRxConfig } ================================================ FILE: artist-traits-rx/src/main/kotlin/com/uber/artist/traits/rx/config/JavaArtistRxConfig.kt ================================================ /* * Copyright (C) 2018. Uber Technologies * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.uber.artist.traits.rx.config import com.squareup.javapoet.CodeBlock import com.squareup.javapoet.TypeName /** * This configuration object describes various plugin points for rx-based traits. */ abstract class JavaArtistRxConfig : ArtistRxConfig { /** * Plugin point for generating additional code to invoke when a view has been tapped. */ override fun processTap(codeBlockBuilder: CodeBlock.Builder) {} /** * Plugin point for generating additional code to invoke when a view has attached to the window. */ override fun processImpression(codeBlockBuilder: CodeBlock.Builder) {} /** * Plugin point for generating additional code to invoke when a view has changed visibility. */ override fun processVisibilityChanges(codeBlockBuilder: CodeBlock.Builder) {} /** * Plugin point for generating additional code to modify an RxBinding stream. */ override fun processRxBindingStream(codeBlockBuilder: CodeBlock.Builder, streamTypeName: TypeName) {} /** * Plugin point for generating additional code to modify an RxBinding stream which notifies that something occurred. * This can be used along with rxBindingSignalEventTypeName() to map signal events to a different type. */ override fun processRxBindingSignalEvent(codeBlockBuilder: CodeBlock.Builder) {} /** * This defines the type to be used for RxBinding event signals. It can be changed if processRxBindingSignalEvent() * has been overridden to map the signal events to a new type. The default type is Object. */ override fun rxBindingSignalEventTypeName(): TypeName = TypeName.OBJECT } ================================================ FILE: artist-traits-rx/src/main/kotlin/com/uber/artist/traits/rx/config/JavaArtistRxConfigService.kt ================================================ /* * Copyright (C) 2018. Uber Technologies * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.uber.artist.traits.rx.config import java.util.ServiceLoader class JavaArtistRxConfigService private constructor() : ArtistRxConfigService { private val serviceLoader = ServiceLoader.load(JavaArtistRxConfig::class.java) /** * Gets the optionally overridden [ArtistRxConfig] implementation or the default. * * @return The located [ArtistRxConfig] or a default config if not provided. */ override fun getArtistRxConfig(): JavaArtistRxConfig = serviceLoader.asIterable().firstOrNull() ?: DEFAULT_CONFIG companion object { private val DEFAULT_CONFIG: JavaArtistRxConfig = JavaDefaultArtistRxConfig() fun newInstance(): JavaArtistRxConfigService = JavaArtistRxConfigService() } } ================================================ FILE: artist-traits-rx/src/main/kotlin/com/uber/artist/traits/rx/config/JavaDefaultArtistRxConfig.kt ================================================ /* * Copyright (C) 2018. Uber Technologies * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.uber.artist.traits.rx.config class JavaDefaultArtistRxConfig : JavaArtistRxConfig() ================================================ FILE: artist-traits-rx/src/main/kotlin/com/uber/artist/traits/rx/config/KotlinArtistRxConfig.kt ================================================ /* * Copyright (C) 2018. Uber Technologies * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.uber.artist.traits.rx.config import com.squareup.kotlinpoet.CodeBlock import com.squareup.kotlinpoet.TypeName import com.squareup.kotlinpoet.UNIT /** * This configuration object describes various plugin points for rx-based traits. */ abstract class KotlinArtistRxConfig : ArtistRxConfig { /** * Plugin point for generating additional code to invoke when a view has been tapped. */ override fun processTap(codeBlockBuilder: CodeBlock.Builder) {} /** * Plugin point for generating additional code to invoke when a view has attached to the window. */ override fun processImpression(codeBlockBuilder: CodeBlock.Builder) {} /** * Plugin point for generating additional code to invoke when a view has changed visibility. */ override fun processVisibilityChanges(codeBlockBuilder: CodeBlock.Builder) {} /** * Plugin point for generating additional code to modify an RxBinding stream. */ override fun processRxBindingStream(codeBlockBuilder: CodeBlock.Builder, streamTypeName: TypeName) {} /** * Plugin point for generating additional code to modify an RxBinding stream which notifies that something occurred. * This can be used along with rxBindingSignalEventTypeName() to map signal events to a different type. */ override fun processRxBindingSignalEvent(codeBlockBuilder: CodeBlock.Builder) {} /** * This defines the type to be used for RxBinding event signals. It can be changed if processRxBindingSignalEvent() * has been overridden to map the signal events to a new type. The default type is Object. */ override fun rxBindingSignalEventTypeName(): TypeName = UNIT } ================================================ FILE: artist-traits-rx/src/main/kotlin/com/uber/artist/traits/rx/config/KotlinArtistRxConfigService.kt ================================================ /* * Copyright (C) 2018. Uber Technologies * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.uber.artist.traits.rx.config import java.util.ServiceLoader class KotlinArtistRxConfigService private constructor() : ArtistRxConfigService { private val serviceLoader = ServiceLoader.load(KotlinArtistRxConfig::class.java) /** * Gets the optionally overridden [ArtistRxConfig] implementation or the default. * * @return The located [ArtistRxConfig] or a default config if not provided. */ override fun getArtistRxConfig(): KotlinArtistRxConfig = serviceLoader.asIterable().firstOrNull() ?: DEFAULT_CONFIG companion object { private val DEFAULT_CONFIG: KotlinArtistRxConfig = KotlinDefaultArtistRxConfig() fun newInstance(): KotlinArtistRxConfigService = KotlinArtistRxConfigService() } } ================================================ FILE: artist-traits-rx/src/main/kotlin/com/uber/artist/traits/rx/config/KotlinDefaultArtistRxConfig.kt ================================================ /* * Copyright (C) 2018. Uber Technologies * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.uber.artist.traits.rx.config class KotlinDefaultArtistRxConfig : KotlinArtistRxConfig() ================================================ FILE: build.gradle ================================================ /* * Copyright (C) 2017. Uber Technologies * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ buildscript { apply from: project.file('gradle/dependencies.gradle') repositories { google() jcenter() maven { url deps.build.gradlePluginsUrl } } dependencies { classpath deps.build.androidPlugin classpath deps.kotlin.gradlePlugin } } apply from: project.file('gradle/dependencies.gradle') subprojects { buildscript { repositories { google() jcenter() } } repositories { google() jcenter() maven { url deps.build.gradlePluginsUrl } } apply plugin: 'checkstyle' checkstyle { -> rootProject configFile rootProject.file('config/checkstyle/checkstyle.xml') } } task wrapper(type: Wrapper) { gradleVersion = '4.7' distributionUrl = "https://services.gradle.org/distributions/gradle-$gradleVersion-all.zip" } task clean(type: Delete) { delete rootProject.buildDir } apply from: 'gradle/dependencies.gradle' ================================================ FILE: buildSrc/build.gradle ================================================ buildscript { apply from: project.rootProject.file("../gradle/dependencies.gradle") repositories { google() jcenter() maven { url deps.build.gradlePluginsUrl } } dependencies { classpath deps.build.androidPlugin classpath deps.kotlin.gradlePlugin } } apply from: project.rootProject.file("../gradle/dependencies.gradle") repositories { google() jcenter() } subprojects { subproject -> if (subproject.buildFile.exists()) { apply from: project.rootProject.file("../gradle/dependencies.gradle") repositories { google() jcenter() } rootProject.dependencies { runtime project(path) } } subproject.afterEvaluate { // Disable useless tasks in buildSrc if (subproject.plugins.hasPlugin("kotlin")) { subproject.tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { kotlinOptions.suppressWarnings = true } } subproject.tasks.findAll { it.name.toLowerCase().contains("test") || it.name.toLowerCase().contains("lint") || it.name.toLowerCase().contains("checkstyle") }.each { it.enabled = false } } } ================================================ FILE: buildSrc/settings.gradle ================================================ // These are not needed if consuming the Artist plugin from Maven Central instead of the local copy from this repo include ':artist' include ':artist-api' include ':artist-core' include ':artist-traits' include ':artist-traits-rx' include ':sample:providers' include ':sample:providers-kotlin' ================================================ FILE: config/checkstyle/checkstyle-suppressions.xml ================================================ ================================================ FILE: config/checkstyle/checkstyle-test.xml ================================================ ================================================ FILE: config/checkstyle/checkstyle.xml ================================================ ================================================ FILE: config/lint/lint.xml ================================================ ================================================ FILE: gradle/dependencies.gradle ================================================ /* * Copyright (C) 2017. Uber Technologies * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ def versions = [ kotlin : "1.3.0", rxBinding: "3.1.0" ] def androidx = [ annotations: "androidx.annotation:annotation:1.0.0", appcompat : "androidx.appcompat:appcompat:1.0.2" ] def apt = [ autoService: "com.google.auto.service:auto-service:1.0-rc4", javapoet : "com.squareup:javapoet:1.9.0", kotlinPoet : "com.squareup:kotlinpoet:1.0.1" ] def build = [ androidPlugin : "com.android.tools.build:gradle:3.2.0", buildToolsVersion : "28.0.3", compileSdkVersion : 28, ci : 'true' == System.getenv('CI'), googleJavaFormatter: "com.google.googlejavaformat:google-java-format:1.4", gradleAptPlugin : "net.ltgt.gradle:gradle-apt-plugin:0.15", gradlePluginsUrl : "https://plugins.gradle.org/m2/", minSdkVersion : 16, targetSdkVersion : 28 ] def external = [ rxbinding : "com.jakewharton.rxbinding3:rxbinding-core:${versions.rxBinding}", rxbindingAppCompat : "com.jakewharton.rxbinding3:rxbinding-appcompat:${versions.rxBinding}", rxbindingRecyclerView: "com.jakewharton.rxbinding3:rxbinding-recyclerview:${versions.rxBinding}", rxjava2 : "io.reactivex.rxjava2:rxjava:2.2.3", rxrelay2 : "com.jakewharton.rxrelay2:rxrelay:2.1.0", ] def kotlin = [ gradlePlugin: "org.jetbrains.kotlin:kotlin-gradle-plugin:${versions.kotlin}", stdLibJdk7 : "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${versions.kotlin}" ] def test = [ compileTesting: "com.google.testing.compile:compile-testing:0.15", junit : 'junit:junit:4.12', robolectric : "org.robolectric:android-all:8.1.0-robolectric-4402310", truth : "com.google.truth:truth:0.42" ] ext.deps = [ "androidx": androidx, "apt" : apt, "build" : build, "external": external, "kotlin" : kotlin, "test" : test, "versions": versions ] ================================================ FILE: gradle/gradle-mvn-push.gradle ================================================ /* * Copyright (C) Chris Banes * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ apply plugin: 'maven' apply plugin: 'signing' version = VERSION_NAME group = GROUP def isReleaseBuild() { return VERSION_NAME.contains("SNAPSHOT") == false } def getReleaseRepositoryUrl() { return hasProperty('RELEASE_REPOSITORY_URL') ? RELEASE_REPOSITORY_URL : "https://oss.sonatype.org/service/local/staging/deploy/maven2/" } def getSnapshotRepositoryUrl() { return hasProperty('SNAPSHOT_REPOSITORY_URL') ? SNAPSHOT_REPOSITORY_URL : "https://oss.sonatype.org/content/repositories/snapshots/" } def getRepositoryUsername() { return hasProperty('SONATYPE_NEXUS_USERNAME') ? SONATYPE_NEXUS_USERNAME : "" } def getRepositoryPassword() { return hasProperty('SONATYPE_NEXUS_PASSWORD') ? SONATYPE_NEXUS_PASSWORD : "" } afterEvaluate { project -> uploadArchives { repositories { mavenDeployer { beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) } pom.groupId = GROUP pom.artifactId = POM_ARTIFACT_ID pom.version = VERSION_NAME repository(url: getReleaseRepositoryUrl()) { authentication(userName: getRepositoryUsername(), password: getRepositoryPassword()) } snapshotRepository(url: getSnapshotRepositoryUrl()) { authentication(userName: getRepositoryUsername(), password: getRepositoryPassword()) } pom.project { name POM_NAME packaging POM_PACKAGING description POM_DESCRIPTION url POM_URL scm { url POM_SCM_URL connection POM_SCM_CONNECTION developerConnection POM_SCM_DEV_CONNECTION } licenses { license { name POM_LICENCE_NAME url POM_LICENCE_URL distribution POM_LICENCE_DIST } } developers { developer { id POM_DEVELOPER_ID name POM_DEVELOPER_NAME } } } } } } signing { required { isReleaseBuild() && gradle.taskGraph.hasTask("uploadArchives") } sign configurations.archives } if (project.getPlugins().hasPlugin('com.android.application') || project.getPlugins().hasPlugin('com.android.library')) { task install(type: Upload, dependsOn: assemble) { repositories.mavenInstaller { configuration = configurations.archives pom.groupId = GROUP pom.artifactId = POM_ARTIFACT_ID pom.version = VERSION_NAME pom.project { name POM_NAME packaging POM_PACKAGING description POM_DESCRIPTION url POM_URL scm { url POM_SCM_URL connection POM_SCM_CONNECTION developerConnection POM_SCM_DEV_CONNECTION } licenses { license { name POM_LICENCE_NAME url POM_LICENCE_URL distribution POM_LICENCE_DIST } } developers { developer { id POM_DEVELOPER_ID name POM_DEVELOPER_NAME } } } } } task androidJavadocs(type: Javadoc) { if (!project.plugins.hasPlugin('kotlin-android')) { source = android.sourceSets.main.java.srcDirs } classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) exclude '**/internal/*' if (JavaVersion.current().isJava8Compatible()) { options.addStringOption('Xdoclint:none', '-quiet') } } task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) { classifier = 'javadoc' from androidJavadocs.destinationDir } task androidSourcesJar(type: Jar) { classifier = 'sources' from android.sourceSets.main.java.sourceFiles } } else { install { repositories.mavenInstaller { pom.groupId = GROUP pom.artifactId = POM_ARTIFACT_ID pom.version = VERSION_NAME pom.project { name POM_NAME packaging POM_PACKAGING description POM_DESCRIPTION url POM_URL scm { url POM_SCM_URL connection POM_SCM_CONNECTION developerConnection POM_SCM_DEV_CONNECTION } licenses { license { name POM_LICENCE_NAME url POM_LICENCE_URL distribution POM_LICENCE_DIST } } developers { developer { id POM_DEVELOPER_ID name POM_DEVELOPER_NAME } } } } } task sourcesJar(type: Jar, dependsOn: classes) { classifier = 'sources' from sourceSets.main.allSource } task javadocJar(type: Jar, dependsOn: javadoc) { classifier = 'javadoc' from javadoc.destinationDir } } if (JavaVersion.current().isJava8Compatible()) { allprojects { tasks.withType(Javadoc) { options.addStringOption('Xdoclint:none', '-quiet') } } } artifacts { if (project.getPlugins().hasPlugin('com.android.application') || project.getPlugins().hasPlugin('com.android.library')) { archives androidSourcesJar archives androidJavadocsJar } else { archives sourcesJar archives javadocJar } } } ================================================ FILE: gradle/wrapper/gradle-wrapper.properties ================================================ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-4.7-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists ================================================ FILE: gradle.properties ================================================ # Project-wide Gradle settings. # IDE (e.g. Android Studio) users: # Gradle settings configured through the IDE *will override* # any settings specified in this file. # For more details on how to configure your build environment visit # http://www.gradle.org/docs/current/userguide/build_environment.html # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true GROUP=com.uber.artist VERSION_NAME=0.4.10-SNAPSHOT POM_DESCRIPTION=A Gradle plugin that generates a base set of Views POM_URL=https://github.com/uber/artist/ POM_SCM_URL=https://github.com/uber/artist/ POM_SCM_CONNECTION=scm:git:git://github.com/uber/artist.git POM_SCM_DEV_CONNECTION=scm:git:ssh://git@github.com/uber/artist.git POM_LICENCE_NAME=The Apache Software License, Version 2.0 POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt POM_LICENCE_DIST=repo POM_DEVELOPER_ID=uber POM_DEVELOPER_NAME=Uber Technologies android.useAndroidX=true android.enableJetifier=true ================================================ FILE: gradlew ================================================ #!/usr/bin/env sh ############################################################################## ## ## Gradle start up script for UN*X ## ############################################################################## # Attempt to set APP_HOME # Resolve links: $0 may be a link PRG="$0" # Need this for relative symlinks. while [ -h "$PRG" ] ; do ls=`ls -ld "$PRG"` link=`expr "$ls" : '.*-> \(.*\)$'` if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG=`dirname "$PRG"`"/$link" fi done SAVED="`pwd`" cd "`dirname \"$PRG\"`/" >/dev/null APP_HOME="`pwd -P`" cd "$SAVED" >/dev/null APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS="" # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" warn () { echo "$*" } die () { echo echo "$*" echo exit 1 } # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false case "`uname`" in CYGWIN* ) cygwin=true ;; Darwin* ) darwin=true ;; MINGW* ) msys=true ;; NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD="$JAVA_HOME/jre/sh/java" else JAVACMD="$JAVA_HOME/bin/java" fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else JAVACMD="java" which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi # Increase the maximum file descriptors if we can. if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then MAX_FD="$MAX_FD_LIMIT" fi ulimit -n $MAX_FD if [ $? -ne 0 ] ; then warn "Could not set maximum file descriptor limit: $MAX_FD" fi else warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" fi fi # For Darwin, add options to specify how the application appears in the dock if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi # For Cygwin, switch paths to Windows format before running java if $cygwin ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` SEP="" for dir in $ROOTDIRSRAW ; do ROOTDIRS="$ROOTDIRS$SEP$dir" SEP="|" done OURCYGPATTERN="(^($ROOTDIRS))" # Add a user-defined pattern to the cygpath arguments if [ "$GRADLE_CYGPATTERN" != "" ] ; then OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" fi # Now convert the arguments - kludge to limit ourselves to /bin/sh i=0 for arg in "$@" ; do CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` else eval `echo args$i`="\"$arg\"" fi i=$((i+1)) done case $i in (0) set -- ;; (1) set -- "$args0" ;; (2) set -- "$args0" "$args1" ;; (3) set -- "$args0" "$args1" "$args2" ;; (4) set -- "$args0" "$args1" "$args2" "$args3" ;; (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi # Escape application args save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } APP_ARGS=$(save "$@") # Collect all arguments for the java command, following the shell quoting and substitution rules eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then cd "$(dirname "$0")" fi exec "$JAVACMD" "$@" ================================================ FILE: sample/app/build.gradle ================================================ /* * Copyright (C) 2017. Uber Technologies * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ apply plugin: 'com.android.application' apply plugin: "org.jetbrains.kotlin.android" android { compileSdkVersion deps.build.compileSdkVersion buildToolsVersion deps.build.buildToolsVersion defaultConfig { applicationId "com.uber.artist.myapplication" minSdkVersion deps.build.minSdkVersion targetSdkVersion deps.build.targetSdkVersion versionCode 1 versionName "1.0" } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } // Setup a simple lint config for an android app/library. lintOptions { abortOnError true lintConfig rootProject.file('config/lint/lint.xml') } } // This is required to run checkstyle on an android app/library. task checkstyle(type: Checkstyle) { source 'src' include '**/*.java' exclude '**/gen/**' classpath = files() } check.dependsOn 'checkstyle' dependencies { api project(":sample:library") api deps.androidx.annotations } ================================================ FILE: sample/app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: sample/app/src/main/java/com/uber/artist/myapplication/MainActivity.kt ================================================ /* * Copyright (C) 2017. Uber Technologies * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.uber.artist.myapplication import android.annotation.SuppressLint import android.os.Bundle import android.widget.Toast import com.uber.artist.mylibrary.MyButton import com.uber.artist.mylibrary.MyEditText import com.uber.artist.mylibrary.MyImageView import com.uber.artist.mylibrary.MyNestedScrollView import com.uber.artist.mylibrary.MySwitch import com.uber.artist.mylibrary.MyTextView import java.util.concurrent.TimeUnit import androidx.appcompat.app.AppCompatActivity import io.reactivex.android.schedulers.AndroidSchedulers import android.widget.Toast.LENGTH_SHORT /** * Sample activity. */ class MainActivity : AppCompatActivity() { private val button by lazy { findViewById(R.id.button) } private val editText by lazy { findViewById(R.id.edittext) } private val imageView by lazy { findViewById(R.id.image) } private val scrollView by lazy { findViewById(R.id.scrollView) } private val textView by lazy { findViewById(R.id.text) } private val toggle by lazy { findViewById(R.id.toggle) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) demoArtistViewUsage() } @SuppressLint("CheckResult") private fun demoArtistViewUsage() { textView.sampleMethodFromCustomTrait() button.clicks() .observeOn(AndroidSchedulers.mainThread()) .subscribe { toast("Click from MyButton's clicks() stream!") } editText.textChanges() .skip(1) .debounce(200, TimeUnit.MILLISECONDS) .observeOn(AndroidSchedulers.mainThread()) .subscribe { charSequence: CharSequence -> toast("MyEditText's textChanges() stream sent: $charSequence") } scrollView.scrollChangeEvents() .debounce(200, TimeUnit.MILLISECONDS) .observeOn(AndroidSchedulers.mainThread()) .subscribe { toast("Scroll from MyScrollView's debounced scrollEvents() stream") } toggle.checkedChanges() .observeOn(AndroidSchedulers.mainThread()) .subscribe { isChecked: Boolean -> toast("MySwitch's checkedChanges() stream sent: $isChecked") } } private fun toast(msg: CharSequence) = Toast.makeText(this, msg, LENGTH_SHORT).show() } ================================================ FILE: sample/app/src/main/res/drawable/divider.xml ================================================ ================================================ FILE: sample/app/src/main/res/layout/activity_main.xml ================================================ ================================================ FILE: sample/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml ================================================ ================================================ FILE: sample/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml ================================================ ================================================ FILE: sample/app/src/main/res/values/colors.xml ================================================ #3F51B5 #303F9F #FF4081 ================================================ FILE: sample/app/src/main/res/values/dimens.xml ================================================ 16dp 16dp 16dp ================================================ FILE: sample/app/src/main/res/values/ic_launcher_background.xml ================================================ #333333 ================================================ FILE: sample/app/src/main/res/values/strings.xml ================================================ Artist Sample ================================================ FILE: sample/app/src/main/res/values/styles.xml ================================================ ================================================ FILE: sample/demo/java/MyButton.java ================================================ package com.uber.artist.mylibrary; import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.view.View; import androidx.annotation.AttrRes; import androidx.annotation.CallSuper; import androidx.annotation.Nullable; import androidx.annotation.StyleRes; import androidx.appcompat.widget.AppCompatButton; import com.jakewharton.rxbinding3.view.RxView; import com.jakewharton.rxrelay2.PublishRelay; import io.reactivex.Observable; import io.reactivex.disposables.Disposable; import io.reactivex.functions.Consumer; import java.lang.Deprecated; import java.lang.Override; import java.lang.SuppressWarnings; public class MyButton extends AppCompatButton implements MyView { private Drawable foreground; private boolean clicksIsInitting; @Nullable private PublishRelay clicks; @Nullable private Disposable clicksDisposable; private boolean longClicksIsInitting; @Nullable private PublishRelay longClicks; @Nullable private Disposable longClicksDisposable; public MyButton(Context context) { this(context, null); } public MyButton(Context context, @Nullable AttributeSet attrs) { this(context, attrs, R.attr.buttonStyle); } public MyButton(Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) { super(context, attrs, defStyleAttr); init(context, attrs, defStyleAttr, 0); } public View sampleMethodFromCustomTrait() { return this; } public boolean isVisible() { return getVisibility() == View.VISIBLE; } public boolean isInvisible() { return getVisibility() == View.INVISIBLE; } public boolean isGone() { return getVisibility() == View.GONE; } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); if (foreground != null) { foreground.setBounds(0, 0, w, h); } } @Override protected boolean verifyDrawable(Drawable who) { return super.verifyDrawable(who) || (who == foreground); } @Override public void jumpDrawablesToCurrentState() { super.jumpDrawablesToCurrentState(); if (foreground != null) { foreground.jumpToCurrentState(); } } @Override protected void drawableStateChanged() { super.drawableStateChanged(); if (foreground != null && foreground.isStateful()) { foreground.setState(getDrawableState()); } } /** * Returns the drawable used as the foreground of this view. The foreground drawable, if non-null, * is always drawn on top of the children. * * @return A Drawable or null if no foreground was set. */ @SuppressWarnings("MissingOverride") public Drawable getForeground() { return foreground; } /** * Supply a Drawable that is to be rendered on top of all of the child views in this layout. Any * padding in the Drawable will be taken into account by ensuring that the children are inset to * be placed inside of the padding area. * * @param drawable The Drawable to be drawn on top of the children. */ @SuppressWarnings("MissingOverride") @SuppressLint("NewApi") public void setForeground(Drawable drawable) { if (foreground != drawable) { if (foreground != null) { foreground.setCallback(null); unscheduleDrawable(foreground); } foreground = drawable; if (drawable != null) { foreground.setBounds(0, 0, getWidth(), getHeight()); setWillNotDraw(false); drawable.setCallback(this); if (drawable.isStateful()) { drawable.setState(getDrawableState()); } } else { setWillNotDraw(true); } invalidate(); } } @Override public void draw(Canvas canvas) { super.draw(canvas); if (foreground != null) { foreground.draw(canvas); } } @TargetApi(android.os.Build.VERSION_CODES.LOLLIPOP) @Override public void drawableHotspotChanged(float x, float y) { super.drawableHotspotChanged(x, y); if (foreground != null) { foreground.setHotspot(x, y); } } /** @deprecated Use {@link #clicks()} */ @Override @Deprecated public final void setOnClickListener(@Nullable final OnClickListener l) { if (clicksIsInitting) { clicksIsInitting = false; super.setOnClickListener(l); } else { if (clicksDisposable != null) { clicksDisposable.dispose(); clicksDisposable = null; } if (l != null) { clicksDisposable = clicks() .subscribe( new Consumer() { @Override public void accept(Signal ignored) { l.onClick(MyButton.this); } }); } } } /** * @return an Observable of click events. The emitted value is unspecified and should only be used * as notification. */ public Observable clicks() { if (clicks == null) { clicksIsInitting = true; clicks = PublishRelay.create(); RxView.clicks(this) .map(MyUtils.createRxBindingSignalMapper()) .doOnNext(MyUtils.createTapProcessor()) .subscribe(clicks); } return clicks.hide(); } /** @deprecated Use {@link #longClicks()} */ @Override @Deprecated public final void setOnLongClickListener(@Nullable final OnLongClickListener l) { if (longClicksIsInitting) { longClicksIsInitting = false; super.setOnLongClickListener(l); } else { if (longClicksDisposable != null) { longClicksDisposable.dispose(); longClicksDisposable = null; } if (l != null) { longClicksDisposable = longClicks() .subscribe( new Consumer() { @Override public void accept(Signal ignored) { l.onLongClick(MyButton.this); } }); } } } /** * @return an Observable of longclick events. The emitted value is unspecified and should only be * used as notification. */ public Observable longClicks() { if (longClicks == null) { longClicksIsInitting = true; longClicks = PublishRelay.create(); RxView.longClicks(this) .map(MyUtils.createRxBindingSignalMapper()) .doOnNext(MyUtils.createTapProcessor()) .subscribe(longClicks); } return longClicks.hide(); } /** * @return an observable which emits on layout changes. The emitted value is unspecified and * should only be used as notification. */ public Observable layoutChanges() { return RxView.layoutChanges(this).map(MyUtils.createRxBindingSignalMapper()); } @CallSuper protected void init( Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr, @StyleRes int defStyleRes) { TypedArray foregroundTA = context.obtainStyledAttributes(attrs, R.styleable.ForegroundView); final Drawable localForeground = foregroundTA.getDrawable(R.styleable.ForegroundView_android_foreground); if (localForeground != null) { // noinspection AndroidLintNewApi setForeground(localForeground); } foregroundTA.recycle(); } } ================================================ FILE: sample/demo/java/MyEditText.java ================================================ package com.uber.artist.mylibrary; import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.view.View; import androidx.annotation.AttrRes; import androidx.annotation.CallSuper; import androidx.annotation.Nullable; import androidx.annotation.StyleRes; import androidx.appcompat.widget.AppCompatEditText; import com.jakewharton.rxbinding3.view.RxView; import com.jakewharton.rxbinding3.widget.RxTextView; import com.jakewharton.rxrelay2.PublishRelay; import io.reactivex.Observable; import io.reactivex.disposables.Disposable; import io.reactivex.functions.Consumer; import java.lang.CharSequence; import java.lang.Deprecated; import java.lang.Override; import java.lang.SuppressWarnings; public class MyEditText extends AppCompatEditText implements MyView { private Drawable foreground; private boolean clicksIsInitting; @Nullable private PublishRelay clicks; @Nullable private Disposable clicksDisposable; private boolean longClicksIsInitting; @Nullable private PublishRelay longClicks; @Nullable private Disposable longClicksDisposable; public MyEditText(Context context) { this(context, null); } public MyEditText(Context context, @Nullable AttributeSet attrs) { this(context, attrs, android.R.attr.editTextStyle); } public MyEditText(Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) { super(context, attrs, defStyleAttr); init(context, attrs, defStyleAttr, 0); } public View sampleMethodFromCustomTrait() { return this; } public boolean isVisible() { return getVisibility() == View.VISIBLE; } public boolean isInvisible() { return getVisibility() == View.INVISIBLE; } public boolean isGone() { return getVisibility() == View.GONE; } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); if (foreground != null) { foreground.setBounds(0, 0, w, h); } } @Override protected boolean verifyDrawable(Drawable who) { return super.verifyDrawable(who) || (who == foreground); } @Override public void jumpDrawablesToCurrentState() { super.jumpDrawablesToCurrentState(); if (foreground != null) { foreground.jumpToCurrentState(); } } @Override protected void drawableStateChanged() { super.drawableStateChanged(); if (foreground != null && foreground.isStateful()) { foreground.setState(getDrawableState()); } } /** * Returns the drawable used as the foreground of this view. The foreground drawable, if non-null, * is always drawn on top of the children. * * @return A Drawable or null if no foreground was set. */ @SuppressWarnings("MissingOverride") public Drawable getForeground() { return foreground; } /** * Supply a Drawable that is to be rendered on top of all of the child views in this layout. Any * padding in the Drawable will be taken into account by ensuring that the children are inset to * be placed inside of the padding area. * * @param drawable The Drawable to be drawn on top of the children. */ @SuppressWarnings("MissingOverride") @SuppressLint("NewApi") public void setForeground(Drawable drawable) { if (foreground != drawable) { if (foreground != null) { foreground.setCallback(null); unscheduleDrawable(foreground); } foreground = drawable; if (drawable != null) { foreground.setBounds(0, 0, getWidth(), getHeight()); setWillNotDraw(false); drawable.setCallback(this); if (drawable.isStateful()) { drawable.setState(getDrawableState()); } } else { setWillNotDraw(true); } invalidate(); } } @Override public void draw(Canvas canvas) { super.draw(canvas); if (foreground != null) { foreground.draw(canvas); } } @TargetApi(android.os.Build.VERSION_CODES.LOLLIPOP) @Override public void drawableHotspotChanged(float x, float y) { super.drawableHotspotChanged(x, y); if (foreground != null) { foreground.setHotspot(x, y); } } /** @deprecated Use {@link #clicks()} */ @Override @Deprecated public final void setOnClickListener(@Nullable final OnClickListener l) { if (clicksIsInitting) { clicksIsInitting = false; super.setOnClickListener(l); } else { if (clicksDisposable != null) { clicksDisposable.dispose(); clicksDisposable = null; } if (l != null) { clicksDisposable = clicks() .subscribe( new Consumer() { @Override public void accept(Signal ignored) { l.onClick(MyEditText.this); } }); } } } /** * @return an Observable of click events. The emitted value is unspecified and should only be used * as notification. */ public Observable clicks() { if (clicks == null) { clicksIsInitting = true; clicks = PublishRelay.create(); RxView.clicks(this) .map(MyUtils.createRxBindingSignalMapper()) .doOnNext(MyUtils.createTapProcessor()) .subscribe(clicks); } return clicks.hide(); } /** @deprecated Use {@link #longClicks()} */ @Override @Deprecated public final void setOnLongClickListener(@Nullable final OnLongClickListener l) { if (longClicksIsInitting) { longClicksIsInitting = false; super.setOnLongClickListener(l); } else { if (longClicksDisposable != null) { longClicksDisposable.dispose(); longClicksDisposable = null; } if (l != null) { longClicksDisposable = longClicks() .subscribe( new Consumer() { @Override public void accept(Signal ignored) { l.onLongClick(MyEditText.this); } }); } } } /** * @return an Observable of longclick events. The emitted value is unspecified and should only be * used as notification. */ public Observable longClicks() { if (longClicks == null) { longClicksIsInitting = true; longClicks = PublishRelay.create(); RxView.longClicks(this) .map(MyUtils.createRxBindingSignalMapper()) .doOnNext(MyUtils.createTapProcessor()) .subscribe(longClicks); } return longClicks.hide(); } /** * @return an observable which emits on layout changes. The emitted value is unspecified and * should only be used as notification. */ public Observable layoutChanges() { return RxView.layoutChanges(this).map(MyUtils.createRxBindingSignalMapper()); } /** @return an observable of character sequences for text changes on this TextView. */ public Observable textChanges() { return RxTextView.textChanges(this); } @CallSuper protected void init( Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr, @StyleRes int defStyleRes) { TypedArray foregroundTA = context.obtainStyledAttributes(attrs, R.styleable.ForegroundView); final Drawable localForeground = foregroundTA.getDrawable(R.styleable.ForegroundView_android_foreground); if (localForeground != null) { // noinspection AndroidLintNewApi setForeground(localForeground); } foregroundTA.recycle(); } } ================================================ FILE: sample/demo/java/MyImageView.java ================================================ package com.uber.artist.mylibrary; import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.view.View; import androidx.annotation.AttrRes; import androidx.annotation.CallSuper; import androidx.annotation.Nullable; import androidx.annotation.StyleRes; import androidx.appcompat.widget.AppCompatImageView; import com.jakewharton.rxbinding3.view.RxView; import com.jakewharton.rxrelay2.PublishRelay; import io.reactivex.Observable; import io.reactivex.disposables.Disposable; import io.reactivex.functions.Consumer; import java.lang.Deprecated; import java.lang.Override; import java.lang.SuppressWarnings; public class MyImageView extends AppCompatImageView implements MyView { private Drawable foreground; private boolean clicksIsInitting; @Nullable private PublishRelay clicks; @Nullable private Disposable clicksDisposable; private boolean longClicksIsInitting; @Nullable private PublishRelay longClicks; @Nullable private Disposable longClicksDisposable; public MyImageView(Context context) { this(context, null); } public MyImageView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public MyImageView(Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) { super(context, attrs, defStyleAttr); init(context, attrs, defStyleAttr, 0); } public View sampleMethodFromCustomTrait() { return this; } public boolean isVisible() { return getVisibility() == View.VISIBLE; } public boolean isInvisible() { return getVisibility() == View.INVISIBLE; } public boolean isGone() { return getVisibility() == View.GONE; } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); if (foreground != null) { foreground.setBounds(0, 0, w, h); } } @Override public boolean hasOverlappingRendering() { return false; } @Override protected boolean verifyDrawable(Drawable who) { return super.verifyDrawable(who) || (who == foreground); } @Override public void jumpDrawablesToCurrentState() { super.jumpDrawablesToCurrentState(); if (foreground != null) { foreground.jumpToCurrentState(); } } @Override protected void drawableStateChanged() { super.drawableStateChanged(); if (foreground != null && foreground.isStateful()) { foreground.setState(getDrawableState()); } } /** * Returns the drawable used as the foreground of this view. The foreground drawable, if non-null, * is always drawn on top of the children. * * @return A Drawable or null if no foreground was set. */ @SuppressWarnings("MissingOverride") public Drawable getForeground() { return foreground; } /** * Supply a Drawable that is to be rendered on top of all of the child views in this layout. Any * padding in the Drawable will be taken into account by ensuring that the children are inset to * be placed inside of the padding area. * * @param drawable The Drawable to be drawn on top of the children. */ @SuppressWarnings("MissingOverride") @SuppressLint("NewApi") public void setForeground(Drawable drawable) { if (foreground != drawable) { if (foreground != null) { foreground.setCallback(null); unscheduleDrawable(foreground); } foreground = drawable; if (drawable != null) { foreground.setBounds(0, 0, getWidth(), getHeight()); setWillNotDraw(false); drawable.setCallback(this); if (drawable.isStateful()) { drawable.setState(getDrawableState()); } } else { setWillNotDraw(true); } invalidate(); } } @Override public void draw(Canvas canvas) { super.draw(canvas); if (foreground != null) { foreground.draw(canvas); } } @TargetApi(android.os.Build.VERSION_CODES.LOLLIPOP) @Override public void drawableHotspotChanged(float x, float y) { super.drawableHotspotChanged(x, y); if (foreground != null) { foreground.setHotspot(x, y); } } /** @deprecated Use {@link #clicks()} */ @Override @Deprecated public final void setOnClickListener(@Nullable final OnClickListener l) { if (clicksIsInitting) { clicksIsInitting = false; super.setOnClickListener(l); } else { if (clicksDisposable != null) { clicksDisposable.dispose(); clicksDisposable = null; } if (l != null) { clicksDisposable = clicks() .subscribe( new Consumer() { @Override public void accept(Signal ignored) { l.onClick(MyImageView.this); } }); } } } /** * @return an Observable of click events. The emitted value is unspecified and should only be used * as notification. */ public Observable clicks() { if (clicks == null) { clicksIsInitting = true; clicks = PublishRelay.create(); RxView.clicks(this) .map(MyUtils.createRxBindingSignalMapper()) .doOnNext(MyUtils.createTapProcessor()) .subscribe(clicks); } return clicks.hide(); } /** @deprecated Use {@link #longClicks()} */ @Override @Deprecated public final void setOnLongClickListener(@Nullable final OnLongClickListener l) { if (longClicksIsInitting) { longClicksIsInitting = false; super.setOnLongClickListener(l); } else { if (longClicksDisposable != null) { longClicksDisposable.dispose(); longClicksDisposable = null; } if (l != null) { longClicksDisposable = longClicks() .subscribe( new Consumer() { @Override public void accept(Signal ignored) { l.onLongClick(MyImageView.this); } }); } } } /** * @return an Observable of longclick events. The emitted value is unspecified and should only be * used as notification. */ public Observable longClicks() { if (longClicks == null) { longClicksIsInitting = true; longClicks = PublishRelay.create(); RxView.longClicks(this) .map(MyUtils.createRxBindingSignalMapper()) .doOnNext(MyUtils.createTapProcessor()) .subscribe(longClicks); } return longClicks.hide(); } /** * @return an observable which emits on layout changes. The emitted value is unspecified and * should only be used as notification. */ public Observable layoutChanges() { return RxView.layoutChanges(this).map(MyUtils.createRxBindingSignalMapper()); } @CallSuper protected void init( Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr, @StyleRes int defStyleRes) { TypedArray foregroundTA = context.obtainStyledAttributes(attrs, R.styleable.ForegroundView); final Drawable localForeground = foregroundTA.getDrawable(R.styleable.ForegroundView_android_foreground); if (localForeground != null) { // noinspection AndroidLintNewApi setForeground(localForeground); } foregroundTA.recycle(); } } ================================================ FILE: sample/demo/java/MyLinearLayout.java ================================================ package com.uber.artist.mylibrary; import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.view.Gravity; import android.view.View; import android.widget.LinearLayout; import androidx.annotation.AttrRes; import androidx.annotation.CallSuper; import androidx.annotation.Nullable; import androidx.annotation.StyleRes; import androidx.core.view.GravityCompat; import com.jakewharton.rxbinding3.view.RxView; import com.jakewharton.rxrelay2.PublishRelay; import io.reactivex.Observable; import io.reactivex.disposables.Disposable; import io.reactivex.functions.Consumer; import java.lang.Deprecated; import java.lang.Override; import java.lang.SuppressWarnings; public class MyLinearLayout extends LinearLayout implements MyView { private Drawable foreground; private final Rect selfBounds = new Rect(); private final Rect overlayBounds = new Rect(); private boolean foregroundInPadding = true; private boolean foregroundBoundsChanged = false; private int foregroundGravity = Gravity.FILL; private boolean clicksIsInitting; @Nullable private PublishRelay clicks; @Nullable private Disposable clicksDisposable; private boolean longClicksIsInitting; @Nullable private PublishRelay longClicks; @Nullable private Disposable longClicksDisposable; public MyLinearLayout(Context context) { this(context, null); } public MyLinearLayout(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public MyLinearLayout(Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) { super(context, attrs, defStyleAttr); init(context, attrs, defStyleAttr, 0); } public View sampleMethodFromCustomTrait() { return this; } public boolean isVisible() { return getVisibility() == View.VISIBLE; } public boolean isInvisible() { return getVisibility() == View.INVISIBLE; } public boolean isGone() { return getVisibility() == View.GONE; } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); foregroundBoundsChanged = true; } /** * Describes how the foreground is positioned. * * @return foreground gravity. * @see #setForegroundGravity(int) */ @SuppressWarnings("MissingOverride") public int getForegroundGravity() { return foregroundGravity; } /** * Describes how the foreground is positioned. Defaults to START and TOP. * * @param foregroundGravity See {@link android.view.Gravity} * @see #getForegroundGravity() */ @SuppressWarnings("MissingOverride") public void setForegroundGravity(int foregroundGravity) { if (this.foregroundGravity != foregroundGravity) { if ((foregroundGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) { foregroundGravity |= GravityCompat.START; } if ((foregroundGravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) { foregroundGravity |= Gravity.TOP; } this.foregroundGravity = foregroundGravity; if (this.foregroundGravity == Gravity.FILL && foreground != null) { Rect padding = new Rect(); foreground.getPadding(padding); } requestLayout(); } } @Override protected boolean verifyDrawable(Drawable who) { return super.verifyDrawable(who) || (who == foreground); } @Override public void jumpDrawablesToCurrentState() { super.jumpDrawablesToCurrentState(); if (foreground != null) { foreground.jumpToCurrentState(); } } @Override protected void drawableStateChanged() { super.drawableStateChanged(); if (foreground != null && foreground.isStateful()) { foreground.setState(getDrawableState()); } } /** * Returns the drawable used as the foreground of this view. The foreground drawable, if non-null, * is always drawn on top of the children. * * @return A Drawable or null if no foreground was set. */ @SuppressWarnings("MissingOverride") public Drawable getForeground() { return foreground; } /** * Supply a Drawable that is to be rendered on top of all of the child views in this layout. Any * padding in the Drawable will be taken into account by ensuring that the children are inset to * be placed inside of the padding area. * * @param drawable The Drawable to be drawn on top of the children. */ @SuppressWarnings("MissingOverride") @SuppressLint("NewApi") public void setForeground(Drawable drawable) { if (foreground != drawable) { if (foreground != null) { foreground.setCallback(null); unscheduleDrawable(foreground); } foreground = drawable; if (drawable != null) { setWillNotDraw(false); drawable.setCallback(this); if (drawable.isStateful()) { drawable.setState(getDrawableState()); } if (foregroundGravity == Gravity.FILL) { Rect padding = new Rect(); drawable.getPadding(padding); } } else { setWillNotDraw(true); } requestLayout(); invalidate(); } } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); if (changed) { foregroundBoundsChanged = true; } } @Override public void draw(Canvas canvas) { super.draw(canvas); if (foreground != null) { final Drawable localForeground = foreground; if (foregroundBoundsChanged) { foregroundBoundsChanged = false; final Rect localSelfBounds = selfBounds; final Rect localOverlayBounds = overlayBounds; final int w = getRight() - getLeft(); final int h = getBottom() - getTop(); if (foregroundInPadding) { localSelfBounds.set(0, 0, w, h); } else { localSelfBounds.set( getPaddingLeft(), getPaddingTop(), w - getPaddingRight(), h - getPaddingBottom()); } Gravity.apply( foregroundGravity, localForeground.getIntrinsicWidth(), localForeground.getIntrinsicHeight(), localSelfBounds, localOverlayBounds); localForeground.setBounds(localOverlayBounds); } localForeground.draw(canvas); } } @TargetApi(android.os.Build.VERSION_CODES.LOLLIPOP) @Override public void drawableHotspotChanged(float x, float y) { super.drawableHotspotChanged(x, y); if (foreground != null) { foreground.setHotspot(x, y); } } /** @deprecated Use {@link #clicks()} */ @Override @Deprecated public final void setOnClickListener(@Nullable final OnClickListener l) { if (clicksIsInitting) { clicksIsInitting = false; super.setOnClickListener(l); } else { if (clicksDisposable != null) { clicksDisposable.dispose(); clicksDisposable = null; } if (l != null) { clicksDisposable = clicks() .subscribe( new Consumer() { @Override public void accept(Signal ignored) { l.onClick(MyLinearLayout.this); } }); } } } /** * @return an Observable of click events. The emitted value is unspecified and should only be used * as notification. */ public Observable clicks() { if (clicks == null) { clicksIsInitting = true; clicks = PublishRelay.create(); RxView.clicks(this) .map(MyUtils.createRxBindingSignalMapper()) .doOnNext(MyUtils.createTapProcessor()) .subscribe(clicks); } return clicks.hide(); } /** @deprecated Use {@link #longClicks()} */ @Override @Deprecated public final void setOnLongClickListener(@Nullable final OnLongClickListener l) { if (longClicksIsInitting) { longClicksIsInitting = false; super.setOnLongClickListener(l); } else { if (longClicksDisposable != null) { longClicksDisposable.dispose(); longClicksDisposable = null; } if (l != null) { longClicksDisposable = longClicks() .subscribe( new Consumer() { @Override public void accept(Signal ignored) { l.onLongClick(MyLinearLayout.this); } }); } } } /** * @return an Observable of longclick events. The emitted value is unspecified and should only be * used as notification. */ public Observable longClicks() { if (longClicks == null) { longClicksIsInitting = true; longClicks = PublishRelay.create(); RxView.longClicks(this) .map(MyUtils.createRxBindingSignalMapper()) .doOnNext(MyUtils.createTapProcessor()) .subscribe(longClicks); } return longClicks.hide(); } /** * @return an observable which emits on layout changes. The emitted value is unspecified and * should only be used as notification. */ public Observable layoutChanges() { return RxView.layoutChanges(this).map(MyUtils.createRxBindingSignalMapper()); } @CallSuper protected void init( Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr, @StyleRes int defStyleRes) { TypedArray foregroundTA = context.obtainStyledAttributes(attrs, R.styleable.ForegroundView); final Drawable localForeground = foregroundTA.getDrawable(R.styleable.ForegroundView_android_foreground); if (localForeground != null) { // noinspection AndroidLintNewApi setForeground(localForeground); } foregroundGravity = foregroundTA.getInt( R.styleable.ForegroundView_android_foregroundGravity, foregroundGravity); foregroundInPadding = foregroundTA.getBoolean(R.styleable.ForegroundView_foregroundInsidePadding, true); foregroundTA.recycle(); } } ================================================ FILE: sample/demo/java/MyNestedScrollView.java ================================================ package com.uber.artist.mylibrary; import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.view.View; import androidx.annotation.AttrRes; import androidx.annotation.CallSuper; import androidx.annotation.Nullable; import androidx.annotation.StyleRes; import androidx.core.widget.NestedScrollView; import com.jakewharton.rxbinding3.core.RxNestedScrollView; import com.jakewharton.rxbinding3.view.RxView; import com.jakewharton.rxbinding3.view.ViewScrollChangeEvent; import com.jakewharton.rxrelay2.PublishRelay; import io.reactivex.Observable; import io.reactivex.disposables.Disposable; import io.reactivex.functions.Consumer; import java.lang.Deprecated; import java.lang.Override; import java.lang.SuppressWarnings; public class MyNestedScrollView extends NestedScrollView implements MyView { private Drawable foreground; private boolean clicksIsInitting; @Nullable private PublishRelay clicks; @Nullable private Disposable clicksDisposable; private boolean longClicksIsInitting; @Nullable private PublishRelay longClicks; @Nullable private Disposable longClicksDisposable; private boolean scrollChangeEventsIsInitting; @Nullable private PublishRelay scrollChangeEvents; @Nullable private Disposable scrollChangeEventsDisposable; public MyNestedScrollView(Context context) { this(context, null); } public MyNestedScrollView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public MyNestedScrollView( Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) { super(context, attrs, defStyleAttr); init(context, attrs, defStyleAttr, 0); } public View sampleMethodFromCustomTrait() { return this; } public boolean isVisible() { return getVisibility() == View.VISIBLE; } public boolean isInvisible() { return getVisibility() == View.INVISIBLE; } public boolean isGone() { return getVisibility() == View.GONE; } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); if (foreground != null) { foreground.setBounds(0, 0, w, h); } } @Override protected boolean verifyDrawable(Drawable who) { return super.verifyDrawable(who) || (who == foreground); } @Override public void jumpDrawablesToCurrentState() { super.jumpDrawablesToCurrentState(); if (foreground != null) { foreground.jumpToCurrentState(); } } @Override protected void drawableStateChanged() { super.drawableStateChanged(); if (foreground != null && foreground.isStateful()) { foreground.setState(getDrawableState()); } } /** * Returns the drawable used as the foreground of this view. The foreground drawable, if non-null, * is always drawn on top of the children. * * @return A Drawable or null if no foreground was set. */ @SuppressWarnings("MissingOverride") public Drawable getForeground() { return foreground; } /** * Supply a Drawable that is to be rendered on top of all of the child views in this layout. Any * padding in the Drawable will be taken into account by ensuring that the children are inset to * be placed inside of the padding area. * * @param drawable The Drawable to be drawn on top of the children. */ @SuppressWarnings("MissingOverride") @SuppressLint("NewApi") public void setForeground(Drawable drawable) { if (foreground != drawable) { if (foreground != null) { foreground.setCallback(null); unscheduleDrawable(foreground); } foreground = drawable; if (drawable != null) { foreground.setBounds(0, 0, getWidth(), getHeight()); setWillNotDraw(false); drawable.setCallback(this); if (drawable.isStateful()) { drawable.setState(getDrawableState()); } } else { setWillNotDraw(true); } invalidate(); } } @Override public void draw(Canvas canvas) { super.draw(canvas); if (foreground != null) { foreground.draw(canvas); } } @TargetApi(android.os.Build.VERSION_CODES.LOLLIPOP) @Override public void drawableHotspotChanged(float x, float y) { super.drawableHotspotChanged(x, y); if (foreground != null) { foreground.setHotspot(x, y); } } /** @deprecated Use {@link #clicks()} */ @Override @Deprecated public final void setOnClickListener(@Nullable final OnClickListener l) { if (clicksIsInitting) { clicksIsInitting = false; super.setOnClickListener(l); } else { if (clicksDisposable != null) { clicksDisposable.dispose(); clicksDisposable = null; } if (l != null) { clicksDisposable = clicks() .subscribe( new Consumer() { @Override public void accept(Signal ignored) { l.onClick(MyNestedScrollView.this); } }); } } } /** * @return an Observable of click events. The emitted value is unspecified and should only be used * as notification. */ public Observable clicks() { if (clicks == null) { clicksIsInitting = true; clicks = PublishRelay.create(); RxView.clicks(this) .map(MyUtils.createRxBindingSignalMapper()) .doOnNext(MyUtils.createTapProcessor()) .subscribe(clicks); } return clicks.hide(); } /** @deprecated Use {@link #longClicks()} */ @Override @Deprecated public final void setOnLongClickListener(@Nullable final OnLongClickListener l) { if (longClicksIsInitting) { longClicksIsInitting = false; super.setOnLongClickListener(l); } else { if (longClicksDisposable != null) { longClicksDisposable.dispose(); longClicksDisposable = null; } if (l != null) { longClicksDisposable = longClicks() .subscribe( new Consumer() { @Override public void accept(Signal ignored) { l.onLongClick(MyNestedScrollView.this); } }); } } } /** * @return an Observable of longclick events. The emitted value is unspecified and should only be * used as notification. */ public Observable longClicks() { if (longClicks == null) { longClicksIsInitting = true; longClicks = PublishRelay.create(); RxView.longClicks(this) .map(MyUtils.createRxBindingSignalMapper()) .doOnNext(MyUtils.createTapProcessor()) .subscribe(longClicks); } return longClicks.hide(); } /** * @return an observable which emits on layout changes. The emitted value is unspecified and * should only be used as notification. */ public Observable layoutChanges() { return RxView.layoutChanges(this).map(MyUtils.createRxBindingSignalMapper()); } /** @deprecated Use {@link #scrollChangeEvents()} */ @Override @Deprecated public final void setOnScrollChangeListener(final OnScrollChangeListener l) { if (scrollChangeEventsIsInitting) { scrollChangeEventsIsInitting = false; super.setOnScrollChangeListener(l); } else { if (scrollChangeEventsDisposable != null) { scrollChangeEventsDisposable.dispose(); scrollChangeEventsDisposable = null; } if (l != null) { scrollChangeEventsDisposable = scrollChangeEvents() .subscribe( new Consumer() { @Override public void accept(ViewScrollChangeEvent event) { l.onScrollChange( MyNestedScrollView.this, event.scrollX(), event.scrollY(), event.oldScrollX(), event.oldScrollY()); } }); } } } /** @return an observable of scroll-change events for this NestedScrollView. */ public Observable scrollChangeEvents() { if (scrollChangeEvents == null) { scrollChangeEventsIsInitting = true; scrollChangeEvents = PublishRelay.create(); RxNestedScrollView.scrollChangeEvents(this).subscribe(scrollChangeEvents); } return scrollChangeEvents.hide(); } @CallSuper protected void init( Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr, @StyleRes int defStyleRes) { TypedArray foregroundTA = context.obtainStyledAttributes(attrs, R.styleable.ForegroundView); final Drawable localForeground = foregroundTA.getDrawable(R.styleable.ForegroundView_android_foreground); if (localForeground != null) { // noinspection AndroidLintNewApi setForeground(localForeground); } foregroundTA.recycle(); } } ================================================ FILE: sample/demo/java/MySwitch.java ================================================ package com.uber.artist.mylibrary; import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.view.View; import androidx.annotation.AttrRes; import androidx.annotation.CallSuper; import androidx.annotation.Nullable; import androidx.annotation.StyleRes; import androidx.appcompat.widget.SwitchCompat; import com.jakewharton.rxbinding3.view.RxView; import com.jakewharton.rxbinding3.widget.RxCompoundButton; import com.jakewharton.rxrelay2.BehaviorRelay; import com.jakewharton.rxrelay2.PublishRelay; import io.reactivex.Observable; import io.reactivex.disposables.Disposable; import io.reactivex.functions.Consumer; import java.lang.Boolean; import java.lang.Deprecated; import java.lang.Override; import java.lang.SuppressWarnings; public class MySwitch extends SwitchCompat implements MyView { private Drawable foreground; private boolean clicksIsInitting; @Nullable private PublishRelay clicks; @Nullable private Disposable clicksDisposable; private boolean longClicksIsInitting; @Nullable private PublishRelay longClicks; @Nullable private Disposable longClicksDisposable; private boolean checkedChangesIsInitting; @Nullable private BehaviorRelay checkedChanges; @Nullable private Disposable checkedChangesDisposable; public MySwitch(Context context) { this(context, null); } public MySwitch(Context context, @Nullable AttributeSet attrs) { this(context, attrs, R.attr.switchStyle); } public MySwitch(Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) { super(context, attrs, defStyleAttr); init(context, attrs, defStyleAttr, 0); } public View sampleMethodFromCustomTrait() { return this; } public boolean isVisible() { return getVisibility() == View.VISIBLE; } public boolean isInvisible() { return getVisibility() == View.INVISIBLE; } public boolean isGone() { return getVisibility() == View.GONE; } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); if (foreground != null) { foreground.setBounds(0, 0, w, h); } } @Override protected boolean verifyDrawable(Drawable who) { return super.verifyDrawable(who) || (who == foreground); } @Override public void jumpDrawablesToCurrentState() { super.jumpDrawablesToCurrentState(); if (foreground != null) { foreground.jumpToCurrentState(); } } @Override protected void drawableStateChanged() { super.drawableStateChanged(); if (foreground != null && foreground.isStateful()) { foreground.setState(getDrawableState()); } } /** * Returns the drawable used as the foreground of this view. The foreground drawable, if non-null, * is always drawn on top of the children. * * @return A Drawable or null if no foreground was set. */ @SuppressWarnings("MissingOverride") public Drawable getForeground() { return foreground; } /** * Supply a Drawable that is to be rendered on top of all of the child views in this layout. Any * padding in the Drawable will be taken into account by ensuring that the children are inset to * be placed inside of the padding area. * * @param drawable The Drawable to be drawn on top of the children. */ @SuppressWarnings("MissingOverride") @SuppressLint("NewApi") public void setForeground(Drawable drawable) { if (foreground != drawable) { if (foreground != null) { foreground.setCallback(null); unscheduleDrawable(foreground); } foreground = drawable; if (drawable != null) { foreground.setBounds(0, 0, getWidth(), getHeight()); setWillNotDraw(false); drawable.setCallback(this); if (drawable.isStateful()) { drawable.setState(getDrawableState()); } } else { setWillNotDraw(true); } invalidate(); } } @Override public void draw(Canvas canvas) { super.draw(canvas); if (foreground != null) { foreground.draw(canvas); } } @TargetApi(android.os.Build.VERSION_CODES.LOLLIPOP) @Override public void drawableHotspotChanged(float x, float y) { super.drawableHotspotChanged(x, y); if (foreground != null) { foreground.setHotspot(x, y); } } /** @deprecated Use {@link #clicks()} */ @Override @Deprecated public final void setOnClickListener(@Nullable final OnClickListener l) { if (clicksIsInitting) { clicksIsInitting = false; super.setOnClickListener(l); } else { if (clicksDisposable != null) { clicksDisposable.dispose(); clicksDisposable = null; } if (l != null) { clicksDisposable = clicks() .subscribe( new Consumer() { @Override public void accept(Signal ignored) { l.onClick(MySwitch.this); } }); } } } /** * @return an Observable of click events. The emitted value is unspecified and should only be used * as notification. */ public Observable clicks() { if (clicks == null) { clicksIsInitting = true; clicks = PublishRelay.create(); RxView.clicks(this) .map(MyUtils.createRxBindingSignalMapper()) .doOnNext(MyUtils.createTapProcessor()) .subscribe(clicks); } return clicks.hide(); } /** @deprecated Use {@link #longClicks()} */ @Override @Deprecated public final void setOnLongClickListener(@Nullable final OnLongClickListener l) { if (longClicksIsInitting) { longClicksIsInitting = false; super.setOnLongClickListener(l); } else { if (longClicksDisposable != null) { longClicksDisposable.dispose(); longClicksDisposable = null; } if (l != null) { longClicksDisposable = longClicks() .subscribe( new Consumer() { @Override public void accept(Signal ignored) { l.onLongClick(MySwitch.this); } }); } } } /** * @return an Observable of longclick events. The emitted value is unspecified and should only be * used as notification. */ public Observable longClicks() { if (longClicks == null) { longClicksIsInitting = true; longClicks = PublishRelay.create(); RxView.longClicks(this) .map(MyUtils.createRxBindingSignalMapper()) .doOnNext(MyUtils.createTapProcessor()) .subscribe(longClicks); } return longClicks.hide(); } /** * @return an observable which emits on layout changes. The emitted value is unspecified and * should only be used as notification. */ public Observable layoutChanges() { return RxView.layoutChanges(this).map(MyUtils.createRxBindingSignalMapper()); } /** @deprecated Use {@link #checkedChanges()} */ @Override @Deprecated public final void setOnCheckedChangeListener(@Nullable final OnCheckedChangeListener l) { if (checkedChangesIsInitting) { checkedChangesIsInitting = false; super.setOnCheckedChangeListener(l); } else { if (checkedChangesDisposable != null) { checkedChangesDisposable.dispose(); checkedChangesDisposable = null; } if (l != null) { checkedChangesDisposable = checkedChanges() .subscribe( new Consumer() { @Override public void accept(Boolean isChecked) { l.onCheckedChanged(MySwitch.this, isChecked); } }); } } } /** @return an observable of booleans representing the checked state of this view. */ public Observable checkedChanges() { if (checkedChanges == null) { checkedChangesIsInitting = true; checkedChanges = BehaviorRelay.createDefault(isChecked()); RxCompoundButton.checkedChanges(this).subscribe(checkedChanges); } return checkedChanges.hide(); } @CallSuper protected void init( Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr, @StyleRes int defStyleRes) { TypedArray foregroundTA = context.obtainStyledAttributes(attrs, R.styleable.ForegroundView); final Drawable localForeground = foregroundTA.getDrawable(R.styleable.ForegroundView_android_foreground); if (localForeground != null) { // noinspection AndroidLintNewApi setForeground(localForeground); } foregroundTA.recycle(); } } ================================================ FILE: sample/demo/java/MyTextView.java ================================================ package com.uber.artist.mylibrary; import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.view.View; import android.widget.TextView; import androidx.annotation.AttrRes; import androidx.annotation.CallSuper; import androidx.annotation.Nullable; import androidx.annotation.StyleRes; import com.jakewharton.rxbinding3.view.RxView; import com.jakewharton.rxrelay2.PublishRelay; import io.reactivex.Observable; import io.reactivex.disposables.Disposable; import io.reactivex.functions.Consumer; import java.lang.Deprecated; import java.lang.Override; import java.lang.SuppressWarnings; public class MyTextView extends TextView implements MyView { private Drawable foreground; private boolean clicksIsInitting; @Nullable private PublishRelay clicks; @Nullable private Disposable clicksDisposable; private boolean longClicksIsInitting; @Nullable private PublishRelay longClicks; @Nullable private Disposable longClicksDisposable; public MyTextView(Context context) { this(context, null); } public MyTextView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, android.R.attr.textViewStyle); } public MyTextView(Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) { super(context, attrs, defStyleAttr); init(context, attrs, defStyleAttr, 0); } public View sampleMethodFromCustomTrait() { return this; } public boolean isVisible() { return getVisibility() == View.VISIBLE; } public boolean isInvisible() { return getVisibility() == View.INVISIBLE; } public boolean isGone() { return getVisibility() == View.GONE; } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); if (foreground != null) { foreground.setBounds(0, 0, w, h); } } @Override protected boolean verifyDrawable(Drawable who) { return super.verifyDrawable(who) || (who == foreground); } @Override public void jumpDrawablesToCurrentState() { super.jumpDrawablesToCurrentState(); if (foreground != null) { foreground.jumpToCurrentState(); } } @Override protected void drawableStateChanged() { super.drawableStateChanged(); if (foreground != null && foreground.isStateful()) { foreground.setState(getDrawableState()); } } /** * Returns the drawable used as the foreground of this view. The foreground drawable, if non-null, * is always drawn on top of the children. * * @return A Drawable or null if no foreground was set. */ @SuppressWarnings("MissingOverride") public Drawable getForeground() { return foreground; } /** * Supply a Drawable that is to be rendered on top of all of the child views in this layout. Any * padding in the Drawable will be taken into account by ensuring that the children are inset to * be placed inside of the padding area. * * @param drawable The Drawable to be drawn on top of the children. */ @SuppressWarnings("MissingOverride") @SuppressLint("NewApi") public void setForeground(Drawable drawable) { if (foreground != drawable) { if (foreground != null) { foreground.setCallback(null); unscheduleDrawable(foreground); } foreground = drawable; if (drawable != null) { foreground.setBounds(0, 0, getWidth(), getHeight()); setWillNotDraw(false); drawable.setCallback(this); if (drawable.isStateful()) { drawable.setState(getDrawableState()); } } else { setWillNotDraw(true); } invalidate(); } } @Override public void draw(Canvas canvas) { super.draw(canvas); if (foreground != null) { foreground.draw(canvas); } } @TargetApi(android.os.Build.VERSION_CODES.LOLLIPOP) @Override public void drawableHotspotChanged(float x, float y) { super.drawableHotspotChanged(x, y); if (foreground != null) { foreground.setHotspot(x, y); } } /** @deprecated Use {@link #clicks()} */ @Override @Deprecated public final void setOnClickListener(@Nullable final OnClickListener l) { if (clicksIsInitting) { clicksIsInitting = false; super.setOnClickListener(l); } else { if (clicksDisposable != null) { clicksDisposable.dispose(); clicksDisposable = null; } if (l != null) { clicksDisposable = clicks() .subscribe( new Consumer() { @Override public void accept(Signal ignored) { l.onClick(MyTextView.this); } }); } } } /** * @return an Observable of click events. The emitted value is unspecified and should only be used * as notification. */ public Observable clicks() { if (clicks == null) { clicksIsInitting = true; clicks = PublishRelay.create(); RxView.clicks(this) .map(MyUtils.createRxBindingSignalMapper()) .doOnNext(MyUtils.createTapProcessor()) .subscribe(clicks); } return clicks.hide(); } /** @deprecated Use {@link #longClicks()} */ @Override @Deprecated public final void setOnLongClickListener(@Nullable final OnLongClickListener l) { if (longClicksIsInitting) { longClicksIsInitting = false; super.setOnLongClickListener(l); } else { if (longClicksDisposable != null) { longClicksDisposable.dispose(); longClicksDisposable = null; } if (l != null) { longClicksDisposable = longClicks() .subscribe( new Consumer() { @Override public void accept(Signal ignored) { l.onLongClick(MyTextView.this); } }); } } } /** * @return an Observable of longclick events. The emitted value is unspecified and should only be * used as notification. */ public Observable longClicks() { if (longClicks == null) { longClicksIsInitting = true; longClicks = PublishRelay.create(); RxView.longClicks(this) .map(MyUtils.createRxBindingSignalMapper()) .doOnNext(MyUtils.createTapProcessor()) .subscribe(longClicks); } return longClicks.hide(); } /** * @return an observable which emits on layout changes. The emitted value is unspecified and * should only be used as notification. */ public Observable layoutChanges() { return RxView.layoutChanges(this).map(MyUtils.createRxBindingSignalMapper()); } @CallSuper protected void init( Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr, @StyleRes int defStyleRes) { TypedArray foregroundTA = context.obtainStyledAttributes(attrs, R.styleable.ForegroundView); final Drawable localForeground = foregroundTA.getDrawable(R.styleable.ForegroundView_android_foreground); if (localForeground != null) { // noinspection AndroidLintNewApi setForeground(localForeground); } foregroundTA.recycle(); } } ================================================ FILE: sample/library/build.gradle ================================================ /* * Copyright (C) 2017. Uber Technologies * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ apply plugin: 'com.android.library' apply plugin: "org.jetbrains.kotlin.android" if (project.rootProject.name != 'buildSrc') { apply plugin: 'com.uber.artist' } android { compileSdkVersion deps.build.compileSdkVersion buildToolsVersion deps.build.buildToolsVersion defaultConfig { minSdkVersion deps.build.minSdkVersion targetSdkVersion deps.build.targetSdkVersion } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } // Setup a simple lint config for an android app/library. lintOptions { abortOnError false lintConfig rootProject.file('config/lint/lint.xml') } } // This is required to run checkstyle on an android app/library. task checkstyle(type: Checkstyle) { source 'src' include '**/*.java' exclude '**/gen/**' classpath = files() } check.dependsOn 'checkstyle' dependencies { api deps.androidx.appcompat api deps.kotlin.stdLibJdk7 api deps.external.rxbinding api deps.external.rxbindingAppCompat api deps.external.rxbindingRecyclerView api deps.external.rxjava2 api deps.external.rxrelay2 } if (project.rootProject.name != 'buildSrc') { artist { interfaceClassName = "com.uber.artist.mylibrary.MyView" viewNamePrefix = "My" generateKotlin = false } } ================================================ FILE: sample/library/src/main/AndroidManifest.xml ================================================ ================================================ FILE: sample/library/src/main/java/com/uber/artist/mylibrary/MyUtils.java ================================================ /* * Copyright (C) 2017. Uber Technologies * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.uber.artist.mylibrary; import android.util.Log; import io.reactivex.functions.Consumer; import io.reactivex.functions.Function; /** * Various utilities used during code generation. */ public final class MyUtils { private MyUtils() { } /** * A static factory method that creates the Consumer that processes the tap. In this case we * write a message in LogCat. * * @return The processor consumer. */ public static Consumer createTapProcessor() { return new Consumer() { @Override public void accept(Object o) { Log.d("Artist", "Tapped a MyView"); } }; } /** * A static factory method that creates the Function that maps RxBinding signals to another type. * In this case we map them to our instance of Signal. * * @return The mapper function. */ public static Function createRxBindingSignalMapper() { return new Function() { @Override public Signal apply(Object o) throws Exception { return Signal.INSTANCE; } }; } } ================================================ FILE: sample/library/src/main/java/com/uber/artist/mylibrary/MyView.java ================================================ /* * Copyright (C) 2017. Uber Technologies * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.uber.artist.mylibrary; /** * A common interface amongst all of the Artist-generated View types. */ public interface MyView { } ================================================ FILE: sample/library/src/main/java/com/uber/artist/mylibrary/Signal.java ================================================ /* * Copyright (C) 2017. Uber Technologies * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.uber.artist.mylibrary; /** * Enum useful for Rx streams that are just notifications with no actual values. */ public enum Signal { INSTANCE; @Override public String toString() { return "██████"; } } ================================================ FILE: sample/library/src/main/res/values/attrs_foreground_view.xml ================================================ ================================================ FILE: sample/library/src/main/res/values/strings.xml ================================================ My Application ================================================ FILE: sample/providers/build.gradle ================================================ /* * Copyright (C) 2017. Uber Technologies * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ buildscript { repositories { maven { url deps.build.gradlePluginsUrl } } dependencies { classpath deps.build.gradleAptPlugin } } apply plugin: 'java-library' apply plugin: 'net.ltgt.apt' sourceCompatibility = JavaVersion.VERSION_1_7 targetCompatibility = JavaVersion.VERSION_1_7 dependencies { annotationProcessor deps.apt.autoService compileOnly deps.apt.autoService implementation deps.androidx.annotations implementation project(":artist-api") implementation project(":artist-traits") implementation project(":artist-traits-rx") } ================================================ FILE: sample/providers/src/main/java/com/uber/artist/myproviders/JavaSampleRxConfig.java ================================================ /* * Copyright (C) 2017. Uber Technologies * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.uber.artist.myproviders; import com.google.auto.service.AutoService; import com.squareup.javapoet.CodeBlock; import com.squareup.javapoet.TypeName; import com.uber.artist.traits.rx.config.JavaArtistRxConfig; import androidx.annotation.NonNull; /** * Sample Artist RxTrait Config. */ @AutoService(JavaArtistRxConfig.class) public class JavaSampleRxConfig extends JavaArtistRxConfig { @Override public void processTap(CodeBlock.Builder codeBlockBuilder) { super.processTap(codeBlockBuilder); codeBlockBuilder.add(".doOnNext($T.createTapProcessor())", JavaSampleTypeNames.MY_UTILS); } @Override public void processRxBindingSignalEvent(@NonNull CodeBlock.Builder codeBlockBuilder) { super.processRxBindingSignalEvent(codeBlockBuilder); codeBlockBuilder.add(".map($T.createRxBindingSignalMapper())", JavaSampleTypeNames.MY_UTILS); } @Override public TypeName rxBindingSignalEventTypeName() { return JavaSampleTypeNames.SIGNAL; } } ================================================ FILE: sample/providers/src/main/java/com/uber/artist/myproviders/JavaSampleTypeNames.java ================================================ /* * Copyright (C) 2017. Uber Technologies * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.uber.artist.myproviders; import com.squareup.javapoet.ClassName; /** * Sample TypeNames. */ public final class JavaSampleTypeNames { public static final ClassName MY_UTILS = ClassName.get("com.uber.artist.mylibrary", "MyUtils"); public static final ClassName SIGNAL = ClassName.get("com.uber.artist.mylibrary", "Signal"); public static final ClassName VIEW = ClassName.get("android.view", "View"); private JavaSampleTypeNames() { } } ================================================ FILE: sample/providers/src/main/java/com/uber/artist/myproviders/JavaSampleViewStencilProvider.java ================================================ /* * Copyright (C) 2017. Uber Technologies * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.uber.artist.myproviders; import com.google.auto.service.AutoService; import com.uber.artist.api.JavaTrait; import com.uber.artist.api.JavaViewStencil; import com.uber.artist.api.JavaViewStencilProvider; import com.uber.artist.myproviders.trait.JavaSampleTrait; import com.uber.artist.traits.JavaForegroundTrait; import com.uber.artist.traits.JavaVisibilityTrait; import com.uber.artist.traits.rx.JavaCheckableTrait; import com.uber.artist.traits.rx.JavaScrollableTrait; import com.uber.artist.traits.rx.JavaTextInputTrait; import com.uber.artist.traits.rx.JavaViewTrait; import java.util.Arrays; import java.util.LinkedHashSet; import java.util.Set; /** * Sample ViewStencil provider. */ @AutoService(JavaViewStencilProvider.class) public class JavaSampleViewStencilProvider implements JavaViewStencilProvider { @Override public Set stencils() { return new LinkedHashSet<>(Arrays.asList( new JavaViewStencil("androidx.appcompat.widget.AppCompatButton", 3, "buttonStyle"), new JavaViewStencil("androidx.appcompat.widget.AppCompatEditText", 3, "android.R.attr.editTextStyle", JavaTextInputTrait.class), new JavaViewStencil("android.widget.LinearLayout", 3, null), new JavaViewStencil("androidx.appcompat.widget.AppCompatImageView", 3, null), new JavaViewStencil("androidx.core.widget.NestedScrollView", 3, null, JavaScrollableTrait.class), new JavaViewStencil("android.widget.TextView", 3, "android.R.attr.textViewStyle"), new SwitchStencil() )); } @Override public Set> globalTraits() { return new LinkedHashSet<>(Arrays.asList( JavaSampleTrait.class, JavaVisibilityTrait.class, JavaForegroundTrait.class, JavaViewTrait.class )); } private static class SwitchStencil extends JavaViewStencil { public SwitchStencil() { super("androidx.appcompat.widget.SwitchCompat", 3, "switchStyle", JavaCheckableTrait.class); } @Override public String name() { return "MySwitch"; } } } ================================================ FILE: sample/providers/src/main/java/com/uber/artist/myproviders/trait/JavaSampleTrait.java ================================================ /* * Copyright (C) 2017. Uber Technologies * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package com.uber.artist.myproviders.trait; import com.google.auto.service.AutoService; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.TypeSpec; import com.uber.artist.api.JavaTrait; import com.uber.artist.myproviders.JavaSampleTypeNames; import javax.lang.model.element.Modifier; /** * A somewhat arbitrary example of a custom Trait. */ @AutoService(JavaTrait.class) public class JavaSampleTrait implements JavaTrait { @Override public void generateFor(TypeSpec.Builder type, MethodSpec.Builder initMethod, ClassName rClass, String sourceType) { type.addMethod(MethodSpec.methodBuilder("sampleMethodFromCustomTrait") .addModifiers(Modifier.PUBLIC) .returns(JavaSampleTypeNames.VIEW) .addStatement("return this") .build() ); } } ================================================ FILE: sample/providers-kotlin/build.gradle ================================================ /* * Copyright (C) 2018. Uber Technologies * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ apply plugin: "org.jetbrains.kotlin.jvm" apply plugin: "org.jetbrains.kotlin.kapt" sourceCompatibility = JavaVersion.VERSION_1_7 targetCompatibility = JavaVersion.VERSION_1_7 dependencies { kapt deps.apt.autoService compileOnly deps.apt.autoService implementation deps.androidx.annotations implementation project(":artist-api") implementation project(":artist-traits") implementation project(":artist-traits-rx") } ================================================ FILE: sample/providers-kotlin/src/main/kotlin/com/uber/artist/myproviders/KotlinSampleRxConfig.kt ================================================ /* * Copyright (C) 2018. Uber Technologies * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.uber.artist.myproviders import com.google.auto.service.AutoService import com.squareup.kotlinpoet.CodeBlock import com.squareup.kotlinpoet.TypeName import com.uber.artist.traits.rx.config.KotlinArtistRxConfig /** * Sample Artist RxTrait Config. */ @AutoService(KotlinArtistRxConfig::class) class KotlinSampleRxConfig : KotlinArtistRxConfig() { override fun processTap(codeBlockBuilder: CodeBlock.Builder) { super.processTap(codeBlockBuilder) codeBlockBuilder.add(".doOnNext(%T.createTapProcessor())", KotlinSampleTypeNames.MY_UTILS) } override fun processRxBindingSignalEvent(codeBlockBuilder: CodeBlock.Builder) { super.processRxBindingSignalEvent(codeBlockBuilder) codeBlockBuilder.add(".map(%T.createRxBindingSignalMapper())", KotlinSampleTypeNames.MY_UTILS) } override fun rxBindingSignalEventTypeName(): TypeName { return KotlinSampleTypeNames.SIGNAL } } ================================================ FILE: sample/providers-kotlin/src/main/kotlin/com/uber/artist/myproviders/KotlinSampleTypeNames.kt ================================================ /* * Copyright (C) 2018. Uber Technologies * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.uber.artist.myproviders import com.squareup.kotlinpoet.ClassName /** * Sample TypeNames. */ object KotlinSampleTypeNames { val MY_UTILS = ClassName("com.uber.artist.mylibrary", "MyUtils") val SIGNAL = ClassName("com.uber.artist.mylibrary", "Signal") val VIEW = ClassName("android.view", "View") } ================================================ FILE: sample/providers-kotlin/src/main/kotlin/com/uber/artist/myproviders/KotlinSampleViewStencilProvider.kt ================================================ /* * Copyright (C) 2018. Uber Technologies * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.uber.artist.myproviders import com.google.auto.service.AutoService import com.uber.artist.api.KotlinTrait import com.uber.artist.api.KotlinViewStencil import com.uber.artist.api.KotlinViewStencilProvider import com.uber.artist.myproviders.trait.KotlinSampleTrait import com.uber.artist.traits.KotlinForegroundTrait import com.uber.artist.traits.KotlinSuppressNullabilityInitializerTrait import com.uber.artist.traits.KotlinVisibilityTrait import com.uber.artist.traits.rx.KotlinCheckableTrait import com.uber.artist.traits.rx.KotlinScrollableTrait import com.uber.artist.traits.rx.KotlinTextInputTrait import com.uber.artist.traits.rx.KotlinViewTrait /** * Sample ViewStencil provider. */ @AutoService(KotlinViewStencilProvider::class) class KotlinSampleViewStencilProvider : KotlinViewStencilProvider { override fun stencils(): Set { return linkedSetOf( KotlinViewStencil("androidx.appcompat.widget.AppCompatButton", 3, "buttonStyle"), KotlinViewStencil("androidx.appcompat.widget.AppCompatEditText", 3, "android.R.attr.editTextStyle", KotlinTextInputTrait::class.java), KotlinViewStencil("android.widget.LinearLayout", 4, null), KotlinViewStencil("androidx.appcompat.widget.AppCompatImageView", 3, null), KotlinViewStencil("androidx.core.widget.NestedScrollView", 3, null, KotlinScrollableTrait::class.java), KotlinViewStencil("android.widget.TextView", 3, "android.R.attr.textViewStyle"), SwitchStencil() ) } override fun globalTraits(): Set> = setOf( KotlinSampleTrait::class.java, KotlinVisibilityTrait::class.java, KotlinForegroundTrait::class.java, KotlinSuppressNullabilityInitializerTrait::class.java, KotlinViewTrait::class.java ) private class SwitchStencil : KotlinViewStencil("androidx.appcompat.widget.SwitchCompat", 3, "switchStyle", KotlinCheckableTrait::class.java) { override fun name(): String { return "MySwitch" } } } ================================================ FILE: sample/providers-kotlin/src/main/kotlin/com/uber/artist/myproviders/trait/KotlinSampleTrait.kt ================================================ /* * Copyright (C) 2018. Uber Technologies * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package com.uber.artist.myproviders.trait import com.google.auto.service.AutoService import com.squareup.kotlinpoet.ClassName import com.squareup.kotlinpoet.FunSpec import com.squareup.kotlinpoet.KModifier import com.squareup.kotlinpoet.TypeSpec import com.uber.artist.api.KotlinTrait import com.uber.artist.myproviders.KotlinSampleTypeNames /** * A somewhat arbitrary example of a custom Trait. */ @AutoService(KotlinTrait::class) class KotlinSampleTrait : KotlinTrait { override fun generateFor(type: TypeSpec.Builder, initMethod: FunSpec.Builder, rClass: ClassName, sourceType: String) { type.addFunction(FunSpec.builder("sampleMethodFromCustomTrait") .addModifiers(KModifier.PUBLIC) .returns(KotlinSampleTypeNames.VIEW) .addStatement("return this") .build()) } } ================================================ FILE: settings.gradle ================================================ include ':artist' include ':artist-api' include ':artist-core' include ':artist-traits' include ':artist-traits-rx' include ':sample:app' include ':sample:library' include ':sample:providers' include ':sample:providers-kotlin'