From 9e76b35e35fbd94217bdab6a79fb619232d1b6d9 Mon Sep 17 00:00:00 2001 From: Zach Klippenstein Date: Sat, 9 May 2020 16:43:30 -0700 Subject: [PATCH 01/67] Initial import of Compose support from main Workflow repo. --- .buildscript/android-sample-app.gradle | 8 + .buildscript/android-ui-tests.gradle | 10 + .buildscript/binary-validation.gradle | 32 +++ .../configure-android-defaults.gradle | 26 +++ .buildscript/configure-compose.gradle | 36 ++++ .buildscript/configure-maven-publish.gradle | 20 ++ .editorconfig | 3 + .github/workflows/kotlin.yml | 135 ++++++++++++ .gitignore | 36 ++++ .idea/dictionaries/workflow.xml | 14 ++ .idea/misc.xml | 22 ++ CODE_OF_CONDUCT.md | 101 +++++++++ CONTRIBUTING.md | 16 ++ LICENSE | 201 ++++++++++++++++++ README.md | 68 ++++++ build.gradle.kts | 94 ++++++++ buildSrc/build.gradle.kts | 26 +++ buildSrc/src/main/java/Dependencies.kt | 176 +++++++++++++++ core-compose/api/core-compose.api | 13 ++ core-compose/build.gradle.kts | 47 ++++ core-compose/gradle.properties | 18 ++ core-compose/src/main/AndroidManifest.xml | 16 ++ .../workflow/ui/compose/ComposeViewFactory.kt | 103 +++++++++ gradle.properties | 37 ++++ gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 58910 bytes gradle/wrapper/gradle-wrapper.properties | 5 + gradlew | 185 ++++++++++++++++ gradlew.bat | 104 +++++++++ .../hello-compose-binding/build.gradle.kts | 47 ++++ .../hellocomposebinding/HelloBindingTest.kt | 43 ++++ .../src/main/AndroidManifest.xml | 38 ++++ .../hellocomposebinding/HelloBinding.kt | 57 +++++ .../HelloBindingActivity.kt | 37 ++++ .../hellocomposebinding/HelloWorkflow.kt | 66 ++++++ .../src/main/res/values/strings.xml | 18 ++ .../src/main/res/values/styles.xml | 23 ++ settings.gradle.kts | 21 ++ 37 files changed, 1902 insertions(+) create mode 100644 .buildscript/android-sample-app.gradle create mode 100644 .buildscript/android-ui-tests.gradle create mode 100644 .buildscript/binary-validation.gradle create mode 100644 .buildscript/configure-android-defaults.gradle create mode 100644 .buildscript/configure-compose.gradle create mode 100644 .buildscript/configure-maven-publish.gradle create mode 100644 .editorconfig create mode 100644 .github/workflows/kotlin.yml create mode 100644 .gitignore create mode 100644 .idea/dictionaries/workflow.xml create mode 100644 .idea/misc.xml create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE create mode 100644 README.md create mode 100644 build.gradle.kts create mode 100644 buildSrc/build.gradle.kts create mode 100644 buildSrc/src/main/java/Dependencies.kt create mode 100644 core-compose/api/core-compose.api create mode 100644 core-compose/build.gradle.kts create mode 100644 core-compose/gradle.properties create mode 100644 core-compose/src/main/AndroidManifest.xml create mode 100644 core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactory.kt create mode 100644 gradle.properties create mode 100755 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100644 samples/hello-compose-binding/build.gradle.kts create mode 100644 samples/hello-compose-binding/src/androidTest/java/com/squareup/sample/hellocomposebinding/HelloBindingTest.kt create mode 100644 samples/hello-compose-binding/src/main/AndroidManifest.xml create mode 100644 samples/hello-compose-binding/src/main/java/com/squareup/sample/hellocomposebinding/HelloBinding.kt create mode 100644 samples/hello-compose-binding/src/main/java/com/squareup/sample/hellocomposebinding/HelloBindingActivity.kt create mode 100644 samples/hello-compose-binding/src/main/java/com/squareup/sample/hellocomposebinding/HelloWorkflow.kt create mode 100644 samples/hello-compose-binding/src/main/res/values/strings.xml create mode 100644 samples/hello-compose-binding/src/main/res/values/styles.xml create mode 100644 settings.gradle.kts diff --git a/.buildscript/android-sample-app.gradle b/.buildscript/android-sample-app.gradle new file mode 100644 index 0000000000..4de6a954a8 --- /dev/null +++ b/.buildscript/android-sample-app.gradle @@ -0,0 +1,8 @@ +apply from: rootProject.file('.buildscript/configure-android-defaults.gradle') + +dependencies { + implementation(Deps.get("androidx.appcompat")) + implementation(Deps.get("timber")) + implementation(Deps.get("workflow.core")) + implementation(Deps.get("workflow.runtime")) +} diff --git a/.buildscript/android-ui-tests.gradle b/.buildscript/android-ui-tests.gradle new file mode 100644 index 0000000000..4c86c81920 --- /dev/null +++ b/.buildscript/android-ui-tests.gradle @@ -0,0 +1,10 @@ +android { + defaultConfig { + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } +} + +dependencies { + androidTestImplementation Deps.get("test.androidx.espresso.core") + androidTestImplementation Deps.get("test.androidx.junitExt") +} diff --git a/.buildscript/binary-validation.gradle b/.buildscript/binary-validation.gradle new file mode 100644 index 0000000000..898a381a8b --- /dev/null +++ b/.buildscript/binary-validation.gradle @@ -0,0 +1,32 @@ +/* + * Copyright 2020 Square Inc. + * + * 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. + */ + +/* + * We use JetBrain's Kotlin Binary Compatibility Validator to track changes to our public binary + * APIs. + * When making a change that results in a public ABI change, the apiCheck task will fail. When this + * happens, run ./gradlew apiDump to generate updated *.api files, and add those to your commit. + * See https://github.com/Kotlin/binary-compatibility-validator + */ + +apply plugin: 'binary-compatibility-validator' + +apiValidation { + // Ignore all sample projects, since they're not part of our API. + // Only leaf project name is valid configuration, and every project must be individually ignored. + // See https://github.com/Kotlin/binary-compatibility-validator/issues/3 + ignoredProjects += project('samples').subprojects.collect { it.name } +} diff --git a/.buildscript/configure-android-defaults.gradle b/.buildscript/configure-android-defaults.gradle new file mode 100644 index 0000000000..84ffc87f67 --- /dev/null +++ b/.buildscript/configure-android-defaults.gradle @@ -0,0 +1,26 @@ +android { + compileSdkVersion Versions.targetSdk + buildToolsVersion '29.0.2' + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + defaultConfig { + minSdkVersion 21 + targetSdkVersion Versions.targetSdk + versionCode 1 + versionName "1.0" + } + + buildFeatures.viewBinding = true + + // See https://github.com/Kotlin/kotlinx.coroutines/issues/1064#issuecomment-479412940 + packagingOptions { + exclude 'META-INF/atomicfu.kotlin_module' + exclude 'META-INF/common.kotlin_module' + exclude 'META-INF/android_debug.kotlin_module' + exclude 'META-INF/android_release.kotlin_module' + } +} diff --git a/.buildscript/configure-compose.gradle b/.buildscript/configure-compose.gradle new file mode 100644 index 0000000000..7534e01d68 --- /dev/null +++ b/.buildscript/configure-compose.gradle @@ -0,0 +1,36 @@ +/* + * Copyright 2020 Square Inc. + * + * 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. + */ + +/* +In addition applying this file, as of dev10 modules that use Compose also need to include this +code snippet to avoid warnings about using compiler version 1.4 (this is because the compiler plugin +is built against compiler source that is in a liminal state between 1.3 and 1.4, the warnings are +safe to ignore and this suppresses them): + +tasks.withType().configureEach { + kotlinOptions.apiVersion = "1.3" +} +*/ + +android { + buildFeatures { + compose true + } + composeOptions { + kotlinCompilerVersion "1.3.70-dev-withExperimentalGoogleExtensions-20200424" + kotlinCompilerExtensionVersion Versions.compose + } +} diff --git a/.buildscript/configure-maven-publish.gradle b/.buildscript/configure-maven-publish.gradle new file mode 100644 index 0000000000..ad7c855d8b --- /dev/null +++ b/.buildscript/configure-maven-publish.gradle @@ -0,0 +1,20 @@ +/* + * Copyright 2019 Square Inc. + * + * 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.vanniktech.maven.publish' + +group = GROUP +version = VERSION_NAME diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000000..17bc1fa451 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,3 @@ +[*.kt] +indent_size = 2 +continuation_indent_size=4 \ No newline at end of file diff --git a/.github/workflows/kotlin.yml b/.github/workflows/kotlin.yml new file mode 100644 index 0000000000..3687cc66b4 --- /dev/null +++ b/.github/workflows/kotlin.yml @@ -0,0 +1,135 @@ +name: Kotlin CI + +on: + push: + branches: [master] + pull_request: + +env: + # Must use $HOME here, NOT a tilde, because of the order in which bash does expansion: + # Tilde happens before variables, so will be used literally, whereas $HOME will be + # recursively expanded. + GRADLE_CACHE_PATH: $HOME/.gradle/caches + +jobs: + assemble: + name: Assemble + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + # These setup steps should be common across all jobs in this workflow. + - uses: actions/checkout@v2 + - uses: gradle/wrapper-validation-action@v1 + - name: set up JDK 1.8 + uses: actions/setup-java@v1 + with: + java-version: 1.8 + + ## Caching + - name: Cache gradle dependencies + uses: actions/cache@v1 + with: + path: ${{ env.GRADLE_CACHE_PATH }} + # Include the SHA in the hash so this step always adds a cache entry. If we didn't use the SHA, the artifacts + # would only get cached once for each build config hash. + # Don't use ${{ runner.os }} in the key so we don't re-assemble for UI tests. + key: gradle-${{ hashFiles('**/gradle/wrapper/gradle-wrapper.properties') }}-${{ hashFiles('**/*.gradle*') }}-${{ hashFiles('**/buildSrc/**') }}-${{ github.sha }} + # The first time a SHA is assembled, we still want to load dependencies from the cache. + # Note that none of jobs dependent on this one need restore keys, since they'll always have an exact hit. + restore-keys: | + gradle-${{ hashFiles('**/gradle/wrapper/gradle-wrapper.properties') }}-${{ hashFiles('**/*.gradle*') }}-${{ hashFiles('**/buildSrc/**') }}- + + # We want to keep the dependencies from the cache, but clear out the build cache which contains the actual + # compiled artifacts from this project. This ensures we don't run into any issues with stale cache entries, + # and that the resulting cache we upload for the other jobs won't waste any space on stale binaries. + # A simpler approach would be simply to delete the build-cache before uploading the cache archive, however + # if we did that in this job it would defeat the purpose of sharing that directory with dependent jobs, + # and there's no way to modify the cache after the job that created it finishes. + - name: Clean gradle build cache to assemble fresh + run: | + ls -lhrt $GRADLE_CACHE_PATH || true + rm -rf $GRADLE_CACHE_PATH/build-cache-1 + ls -lhrt $GRADLE_CACHE_PATH || true + + ## Actual task + - name: Assemble with gradle + run: ./gradlew assemble --build-cache --no-daemon --stacktrace + + # Runs all check tasks in parallel. + check: + name: Check + needs: assemble + runs-on: ubuntu-latest + timeout-minutes: 10 + strategy: + # Run all checks, even if some fail. + fail-fast: false + matrix: + gradle-task: + # Unit tests + - test + # Binary compatibility + - apiCheck + - lint + - ktlintCheck + - detekt + # Build the JMH benchmarks to verify, but don't run them. + - jmhJar + steps: + - uses: actions/checkout@v2 + - name: set up JDK 1.8 + uses: actions/setup-java@v1 + with: + java-version: 1.8 + + ## Caching + - name: Cache build artifacts + uses: actions/cache@v1 + with: + path: ${{ env.GRADLE_CACHE_PATH }} + # Don't set restore-keys so cache is always only valid for the current build config. + # Also don't use ${{ runner.os }} in the key so we don't re-assemble for UI tests. + key: gradle-${{ hashFiles('**/gradle/wrapper/gradle-wrapper.properties') }}-${{ hashFiles('**/*.gradle*') }}-${{ hashFiles('**/buildSrc/**') }}-${{ github.sha }} + + ## Actual task + - name: Check with Gradle + run: ./gradlew ${{ matrix.gradle-task }} --build-cache --no-daemon --stacktrace + + instrumentation-tests: + name: Instrumentation tests + needs: assemble + runs-on: macos-latest + timeout-minutes: 20 + strategy: + # Allow tests to continue on other devices if they fail on one device. + fail-fast: false + matrix: + api-level: + # Tests are failing on APIs <24. + #- 21 + #- 23 + - 24 + - 29 + steps: + - uses: actions/checkout@v2 + - name: set up JDK 1.8 + uses: actions/setup-java@v1 + with: + java-version: 1.8 + + ## Caching + - name: Cache build artifacts + uses: actions/cache@v1 + with: + path: ${{ env.GRADLE_CACHE_PATH }} + # Don't set restore-keys so cache is always only valid for the current build config. + # Also don't use ${{ runner.os }} in the key so we don't re-assemble for UI tests. + key: gradle-${{ hashFiles('**/gradle/wrapper/gradle-wrapper.properties') }}-${{ hashFiles('**/*.gradle*') }}-${{ hashFiles('**/buildSrc/**') }}-${{ github.sha }} + + ## Actual task + - name: Instrumentation Tests + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: ${{ matrix.api-level }} + arch: x86_64 + script: ./gradlew connectedCheck --build-cache --no-daemon --stacktrace diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..08676baedf --- /dev/null +++ b/.gitignore @@ -0,0 +1,36 @@ +# macOS +.DS_Store + +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +# Gradle +out/ +.gradle/ +build/ +local.properties + +# Intellij +*.iml +.idea/ diff --git a/.idea/dictionaries/workflow.xml b/.idea/dictionaries/workflow.xml new file mode 100644 index 0000000000..f424539ec1 --- /dev/null +++ b/.idea/dictionaries/workflow.xml @@ -0,0 +1,14 @@ + + + + atomicfu + coroutine + coroutines + flowable + okio + passthrough + squareup + workflows + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000000..9053c7ab2b --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000000..48fae94d34 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,101 @@ +Open Source Code of Conduct +=========================== + +At Square, we are committed to contributing to the open source community and simplifying the process +of releasing and managing open source software. We’ve seen incredible support and enthusiasm from +thousands of people who have already contributed to our projects — and we want to ensure ourcommunity +continues to be truly open for everyone. + +This code of conduct outlines our expectations for participants, as well as steps to reporting +unacceptable behavior. We are committed to providing a welcoming and inspiring community for all and +expect our code of conduct to be honored. + +Square’s open source community strives to: + +* **Be open**: We invite anyone to participate in any aspect of our projects. Our community is + open, and any responsibility can be carried by a contributor who demonstrates the required + capacity and competence. + +* **Be considerate**: People use our work, and we depend on the work of others. Consider users and + colleagues before taking action. For example, changes to code, infrastructure, policy, and + documentation may negatively impact others. + +* **Be respectful**: We expect people to work together to resolve conflict, assume good intentions, + and act with empathy. Do not turn disagreements into personal attacks. + +* **Be collaborative**: Collaboration reduces redundancy and improves the quality of our work. We + strive for transparency within our open source community, and we work closely with upstream + developers and others in the free software community to coordinate our efforts. + +* **Be pragmatic**: Questions are encouraged and should be asked early in the process to avoid + problems later. Be thoughtful and considerate when seeking out the appropriate forum for your + questions. Those who are asked should be responsive and helpful. + +* **Step down considerately**: Members of every project come and go. When somebody leaves or + disengages from the project, they should make it known and take the proper steps to ensure that + others can pick up where they left off. + +This code is not exhaustive or complete. It serves to distill our common understanding of a +collaborative, shared environment, and goals. We expect it to be followed in spirit as much as in +the letter. + +Diversity Statement +------------------- + +We encourage everyone to participate and are committed to building a community for all. Although we +may not be able to satisfy everyone, we all agree that everyone is equal. + +Whenever a participant has made a mistake, we expect them to take responsibility for it. If someone +has been harmed or offended, it is our responsibility to listen carefully and respectfully, and do +our best to right the wrong. + +Although this list cannot be exhaustive, we explicitly honor diversity in age, culture, ethnicity, +gender identity or expression, language, national origin, political beliefs, profession, race, +religion, sexual orientation, socioeconomic status, and technical ability. We will not tolerate +discrimination based on any of the protected characteristics above, including participants with +disabilities. + +Reporting Issues +---------------- + +If you experience or witness unacceptable behavior — or have any other concerns — please report it by +emailing [codeofconduct@squareup.com][codeofconduct_at]. For more details, please see our Reporting +Guidelines below. + +Thanks +------ + +Some of the ideas and wording for the statements and guidelines above were based on work by the +[Twitter][twitter_coc], [Ubuntu][ubuntu_coc], [GDC][gdc_coc], and [Django][django_coc] communities. +We are thankful for their work. + +Reporting Guide +--------------- + +If you experience or witness unacceptable behavior — or have any other concerns — please report it by +emailing [codeofconduct@squareup.com][codeofconduct_at]. All reports will be handled with +discretion. + +In your report please include: + +* Your contact information. +* Names (real, nicknames, or pseudonyms) of any individuals involved. If there are additional + witnesses, please include them as well. +* Your account of what occurred, and if you believe the incident is ongoing. If there is a publicly + available record (e.g. a mailing list archive or a public IRC logger), please include a link. +* Any additional information that may be helpful. + +After filing a report, a representative from the Square Code of Conduct committee will contact you +personally. The committee will then review the incident, follow up with any additional questions, +and make a decision as to how to respond. + +Anyone asked to stop unacceptable behavior is expected to comply immediately. If an individual +engages in unacceptable behavior, the Square Code of Conduct committee may take any action they deem +appropriate, up to and including a permanent ban from all of Square spaces without warning. + +[codeofconduct_at]: mailto:codeofconduct@squareup.com +[twitter_coc]: https://github.com/twitter/code-of-conduct/blob/master/code-of-conduct.md +[ubuntu_coc]: https://ubuntu.com/community/code-of-conduct +[gdc_coc]: https://www.gdconf.com/code-of-conduct +[django_coc]: https://www.djangoproject.com/conduct/reporting/ + diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000000..7725594c98 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,16 @@ +Contributing +============ + +If you would like to contribute code to Workflow you can do so through GitHub by +forking the repository and sending a pull request. + +When submitting code, please make every effort to follow existing conventions +and style in order to keep the code as readable as possible. Please also make +sure your code compiles by running `./gradlew clean build`. If you're using IntelliJ IDEA, +we use [Square's code style definitions][2]. + +Before your code can be accepted into the project you must also sign the +[Individual Contributor License Agreement (CLA)][1]. + + [1]: https://spreadsheets.google.com/spreadsheet/viewform?formkey=dDViT2xzUHAwRkI3X3k5Z0lQM091OGc6MQ&ndplr=1 + [2]: https://github.com/square/java-code-styles diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000..261eeb9e9f --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + 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. diff --git a/README.md b/README.md new file mode 100644 index 0000000000..3d278fdf87 --- /dev/null +++ b/README.md @@ -0,0 +1,68 @@ +# workflow-kotlin-compose + +This module provides experimental support for [Jetpack Compose UI][1] with workflows. + +The only integration that is currently supported is the ability to define [ViewFactories][2] that +are implemented as `@Composable` functions. See the `hello-compose-binding` sample in `samples` for +an example of how to use. + +---- + +## Pre-Alpha + +**DO NOT USE this module in your production apps!** + +Jetpack Compose is in pre-alpha, developer preview stage. The API is incomplete and changes +_very frequently_. This integration module exists as a proof-of-concept, to show what's possible, +and to experiment with various ways to integrate Compose with Workflow. + +---- + +## Usage + +To get started, you must be using the latest Android Gradle Plugin 4.x version. Then, you need to +enable Compose support in your `build.gradle`: + +```groovy +android { + buildFeatures { + compose true + } + composeOptions { + kotlinCompilerVersion "1.3.70-dev-withExperimentalGoogleExtensions-20200424" + kotlinCompilerExtensionVersion "${compose_version}" + } +} +``` + +You may also need to set the Kotlin API version to 1.3: + +```groovy +tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach { + kotlinOptions.apiVersion = "1.3" +} +``` + +To create a `ViewFactory`, call `bindCompose`. The lambda passed to `bindCompose` is a `@Composable` +function. + +```kotlin +val HelloBinding = bindCompose { rendering -> + MaterialTheme { + Clickable(onClick = { rendering.onClick() }) { + Text(rendering.message) + } + } +} +``` + +The `bindCompose` function returns a regular [`ViewFactory`][2] which can be added to a +[`ViewRegistry`][3] like any other: + +```kotlin +val viewRegistry = ViewRegistry(HelloBinding) +``` + +[1]: https://developer.android.com/jetpack/compose +[2]: https://square.github.io/workflow/kotlin/api/workflow/com.squareup.workflow.ui/-view-factory/ +[3]: https://square.github.io/workflow/kotlin/api/workflow/com.squareup.workflow.ui/-view-registry/ diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000000..dddff11c42 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,94 @@ +/* + * Copyright 2017 Square Inc. + * + * 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. + */ +import org.jetbrains.dokka.gradle.DokkaTask +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile +import org.jlleitschuh.gradle.ktlint.KtlintExtension +import org.jlleitschuh.gradle.ktlint.reporter.ReporterType + +buildscript { + dependencies { + classpath(Dependencies.android_gradle_plugin) + classpath(Dependencies.detekt) + classpath(Dependencies.dokka) + classpath(Dependencies.Jmh.gradlePlugin) + classpath(Dependencies.Kotlin.binaryCompatibilityValidatorPlugin) + classpath(Dependencies.Kotlin.gradlePlugin) + classpath(Dependencies.Kotlin.Serialization.gradlePlugin) + classpath(Dependencies.ktlint) + classpath(Dependencies.mavenPublish) + } + + repositories { + mavenCentral() + gradlePluginPortal() + google() + // For binary compatibility validator. + maven { url = uri("https://kotlin.bintray.com/kotlinx") } + } +} + +subprojects { + repositories { + google() + mavenCentral() + jcenter() + } + + apply(plugin = "org.jlleitschuh.gradle.ktlint") + apply(plugin = "io.gitlab.arturbosch.detekt") + afterEvaluate { + tasks.findByName("check") + ?.dependsOn("detekt") + + configurations.configureEach { + // There could be transitive dependencies in tests with a lower version. This could cause + // problems with a newer Kotlin version that we use. + resolutionStrategy.force(Dependencies.Kotlin.reflect) + } + } + + tasks.withType() { + kotlinOptions { + kotlinOptions.allWarningsAsErrors = true + + jvmTarget = "1.8" + + // Don't panic, all this does is allow us to use the @OptIn meta-annotation. + // to define our own experiments. + freeCompilerArgs += "-Xopt-in=kotlin.RequiresOptIn" + } + } + + // Configuration documentation: https://github.com/JLLeitschuh/ktlint-gradle#configuration + configure { + // Prints the name of failed rules. + verbose.set(true) + reporters { + // Default "plain" reporter is actually harder to read. + reporter(ReporterType.JSON) + } + + disabledRules.set( + setOf( + // IntelliJ refuses to sort imports correctly. + // This is a known issue: https://github.com/pinterest/ktlint/issues/527 + "import-ordering" + ) + ) + } +} + +apply(from = rootProject.file(".buildscript/binary-validation.gradle")) diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts new file mode 100644 index 0000000000..9db828f6bc --- /dev/null +++ b/buildSrc/build.gradle.kts @@ -0,0 +1,26 @@ +/* + * Copyright 2020 Square Inc. + * + * 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. + */ +plugins { + `kotlin-dsl` +} + +repositories { + mavenCentral() +} + +kotlinDslPluginOptions { + experimentalWarning.set(false) +} diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt new file mode 100644 index 0000000000..6da85cac82 --- /dev/null +++ b/buildSrc/src/main/java/Dependencies.kt @@ -0,0 +1,176 @@ +/* + * Copyright 2020 Square Inc. + * + * 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:JvmName("Deps") + +import java.util.Locale.US +import kotlin.reflect.full.declaredMembers + +object Versions { + const val compose = "0.1.0-dev10" + const val coroutines = "1.3.4" + const val kotlin = "1.3.71" + const val targetSdk = 29 + const val workflow = "0.26.0" +} + +@Suppress("unused") +object Dependencies { + const val android_gradle_plugin = "com.android.tools.build:gradle:4.1.0-alpha08" + + object AndroidX { + const val activity = "androidx.activity:activity:1.1.0" + const val annotations = "androidx.annotation:annotation:1.1.0" + const val appcompat = "androidx.appcompat:appcompat:1.1.0" + const val constraint_layout = "androidx.constraintlayout:constraintlayout:1.1.3" + const val fragment = "androidx.fragment:fragment:1.2.3" + const val gridlayout = "androidx.gridlayout:gridlayout:1.0.0" + + object Lifecycle { + const val reactivestreams = "androidx.lifecycle:lifecycle-reactivestreams-ktx:2.2.0" + } + + // Note that we're not using the actual androidx material dep yet, it's still alpha. + const val material = "com.google.android.material:material:1.1.0" + const val recyclerview = "androidx.recyclerview:recyclerview:1.1.0" + + // Note that we are *not* using lifecycle-viewmodel-savedstate, which at this + // writing is still in beta and still fixing bad bugs. Probably we'll never bother to, + // it doesn't really add value for us. + const val savedstate = "androidx.savedstate:savedstate:1.0.0" + const val transition = "androidx.transition:transition:1.3.1" + const val viewbinding = "androidx.databinding:viewbinding:3.6.2" + } + + object Compose { + const val foundation = "androidx.ui:ui-foundation:${Versions.compose}" + const val layout = "androidx.ui:ui-layout:${Versions.compose}" + const val material = "androidx.ui:ui-material:${Versions.compose}" + const val test = "androidx.ui:ui-test:${Versions.compose}" + const val tooling = "androidx.ui:ui-tooling:${Versions.compose}" + } + + const val timber = "com.jakewharton.timber:timber:4.7.1" + + object Kotlin { + const val binaryCompatibilityValidatorPlugin = + "org.jetbrains.kotlinx:binary-compatibility-validator:0.2.3" + const val gradlePlugin = "org.jetbrains.kotlin:kotlin-gradle-plugin:${Versions.kotlin}" + + const val stdlib = "org.jetbrains.kotlin:kotlin-stdlib-jdk8" + + object Coroutines { + const val android = "org.jetbrains.kotlinx:kotlinx-coroutines-android:${Versions.coroutines}" + const val core = "org.jetbrains.kotlinx:kotlinx-coroutines-core:${Versions.coroutines}" + + const val test = "org.jetbrains.kotlinx:kotlinx-coroutines-test:${Versions.coroutines}" + } + + const val moshi = "com.squareup.moshi:moshi-kotlin:1.9.2" + const val reflect = "org.jetbrains.kotlin:kotlin-reflect:${Versions.kotlin}" + + object Serialization { + const val gradlePlugin = "org.jetbrains.kotlin:kotlin-serialization:${Versions.kotlin}" + const val runtime = "org.jetbrains.kotlinx:kotlinx-serialization-runtime:0.20.0" + const val kaml = "com.charleskorn.kaml:kaml:0.16.1" + } + + object Test { + const val common = "org.jetbrains.kotlin:kotlin-test-common" + const val annotations = "org.jetbrains.kotlin:kotlin-test-annotations-common" + const val jdk = "org.jetbrains.kotlin:kotlin-test-junit" + + const val mockito = "com.nhaarman:mockito-kotlin-kt1.1:1.6.0" + } + } + + const val dokka = "org.jetbrains.dokka:dokka-gradle-plugin:0.10.0" + + object Jmh { + const val gradlePlugin = "me.champeau.gradle:jmh-gradle-plugin:0.5.0" + const val core = "org.openjdk.jmh:jmh-core:1.23" + const val generator = "org.openjdk.jmh:jmh-generator-annprocess:1.23" + } + + const val mavenPublish = "com.vanniktech:gradle-maven-publish-plugin:0.11.1" + const val ktlint = "org.jlleitschuh.gradle:ktlint-gradle:9.2.0" + const val lanterna = "com.googlecode.lanterna:lanterna:3.0.2" + const val detekt = "io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.0.1" + const val okio = "com.squareup.okio:okio:2.5.0" + + object Annotations { + const val intellij = "org.jetbrains:annotations:19.0.0" + } + + object Test { + object AndroidX { + object Espresso { + const val contrib = "androidx.test.espresso:espresso-contrib:3.2.0" + const val core = "androidx.test.espresso:espresso-core:3.2.0" + const val idlingResource = "androidx.test.espresso:espresso-idling-resource:3.2.0" + const val intents = "androidx.test.espresso:espresso-intents:3.2.0" + } + + const val junitExt = "androidx.test.ext:junit:1.1.1" + const val runner = "androidx.test:runner:1.2.0" + const val truthExt = "androidx.test.ext:truth:1.2.0" + const val uiautomator = "androidx.test.uiautomator:uiautomator:2.2.0" + } + + const val hamcrestCore = "org.hamcrest:hamcrest-core:2.2" + const val junit = "junit:junit:4.13" + const val mockito = "org.mockito:mockito-core:3.3.3" + const val truth = "com.google.truth:truth:1.0.1" + } + + object Workflow { + const val core = "com.squareup.workflow:workflow-core-jvm:${Versions.workflow}" + const val runtime = "com.squareup.workflow:workflow-runtime-jvm:${Versions.workflow}" + + object UI { + const val coreAndroid = "com.squareup.workflow:workflow-ui-core-android:${Versions.workflow}" + } + } +} + +/** + * Workaround to make [Dependencies] accessible from Groovy scripts. [path] is case-insensitive. + * + * ``` + * dependencies { + * implementation Deps.get("kotlin.stdlib.common") + * } + * ``` + */ +@JvmName("get") +fun getDependencyFromGroovy(path: String): String = Dependencies.resolveObject( + path.toLowerCase(US) + .split(".") +) + +private tailrec fun Any.resolveObject(pathParts: List): String { + require(pathParts.isNotEmpty()) + val klass = this::class + + if (pathParts.size == 1) { + @Suppress("UNCHECKED_CAST") + val member = klass.declaredMembers.single { it.name.toLowerCase(US) == pathParts.single() } + return member.call() as String + } + + val nestedKlasses = klass.nestedClasses + val selectedKlass = nestedKlasses.single { it.simpleName!!.toLowerCase(US) == pathParts.first() } + return selectedKlass.objectInstance!!.resolveObject(pathParts.subList(1, pathParts.size)) +} diff --git a/core-compose/api/core-compose.api b/core-compose/api/core-compose.api new file mode 100644 index 0000000000..257b70fba0 --- /dev/null +++ b/core-compose/api/core-compose.api @@ -0,0 +1,13 @@ +public final class com/squareup/workflow/ui/compose/ComposeViewFactory : com/squareup/workflow/ui/ViewFactory { + public fun (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function3;)V + public fun buildView (Ljava/lang/Object;Lcom/squareup/workflow/ui/ViewEnvironment;Landroid/content/Context;Landroid/view/ViewGroup;)Landroid/view/View; + public fun getType ()Lkotlin/reflect/KClass; +} + +public final class com/squareup/workflow/ui/core/compose/BuildConfig { + public static final field BUILD_TYPE Ljava/lang/String; + public static final field DEBUG Z + public static final field LIBRARY_PACKAGE_NAME Ljava/lang/String; + public fun ()V +} + diff --git a/core-compose/build.gradle.kts b/core-compose/build.gradle.kts new file mode 100644 index 0000000000..230318f1c3 --- /dev/null +++ b/core-compose/build.gradle.kts @@ -0,0 +1,47 @@ +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +/* + * Copyright 2019 Square Inc. + * + * 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. + */ +plugins { + id("com.android.library") + kotlin("android") +} + +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} + +apply(from = rootProject.file(".buildscript/configure-maven-publish.gradle")) +apply(from = rootProject.file(".buildscript/configure-android-defaults.gradle")) + +apply(from = rootProject.file(".buildscript/configure-compose.gradle")) +tasks.withType { + kotlinOptions.apiVersion = "1.3" +} + +dependencies { + api(Dependencies.Kotlin.stdlib) + api(Dependencies.Workflow.UI.coreAndroid) + + implementation(Dependencies.Compose.foundation) + implementation(Dependencies.Compose.layout) + implementation(Dependencies.Compose.tooling) + implementation(Dependencies.Workflow.runtime) + + testImplementation(Dependencies.Test.junit) + testImplementation(Dependencies.Test.truth) +} diff --git a/core-compose/gradle.properties b/core-compose/gradle.properties new file mode 100644 index 0000000000..293cedcf2e --- /dev/null +++ b/core-compose/gradle.properties @@ -0,0 +1,18 @@ +# +# Copyright 2019 Square Inc. +# +# 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_ARTIFACT_ID=workflow-ui-core-compose +POM_NAME=Workflow UI Core Compose +POM_PACKAGING=aar diff --git a/core-compose/src/main/AndroidManifest.xml b/core-compose/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..4b26d2a703 --- /dev/null +++ b/core-compose/src/main/AndroidManifest.xml @@ -0,0 +1,16 @@ + + diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactory.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactory.kt new file mode 100644 index 0000000000..b095f296f7 --- /dev/null +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactory.kt @@ -0,0 +1,103 @@ +/* + * Copyright 2019 Square Inc. + * + * 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. + */ +// See https://youtrack.jetbrains.com/issue/KT-31734 +@file:Suppress("RemoveEmptyParenthesesFromAnnotationEntry") + +package com.squareup.workflow.ui.compose + +import android.content.Context +import android.view.View +import android.view.ViewGroup +import android.widget.FrameLayout +import androidx.compose.Composable +import androidx.compose.Recomposer +import androidx.compose.StructurallyEqual +import androidx.compose.mutableStateOf +import androidx.ui.core.setContent +import com.squareup.workflow.ui.ViewEnvironment +import com.squareup.workflow.ui.ViewFactory +import com.squareup.workflow.ui.bindShowRendering +import kotlin.reflect.KClass + +/** + * Creates a [ViewFactory] that uses a [Composable] function to display the rendering. + * + * Note that the function you pass in will not have any `MaterialTheme` applied, so views that rely + * on Material theme attributes must be explicitly wrapped with `MaterialTheme`. + * + * Simple usage: + * + * ``` + * // Function references to @Composable functions aren't supported yet. + * val FooBinding = bindCompose { showFoo(it) } + * + * @Composable + * private fun showFoo(foo: FooRendering) { + * MaterialTheme { + * Text(foo.message) + * } + * } + * + * … + * + * val viewRegistry = ViewRegistry(FooBinding, …) + * ``` + */ +inline fun bindCompose( + noinline showRendering: @Composable() (RenderingT, ViewEnvironment) -> Unit +): ViewFactory = ComposeViewFactory(RenderingT::class) { rendering, environment -> + showRendering(rendering, environment) +} + +@PublishedApi +internal class ComposeViewFactory( + override val type: KClass, + private val showRendering: @Composable() (RenderingT, ViewEnvironment) -> Unit +) : ViewFactory { + + override fun buildView( + initialRendering: RenderingT, + initialViewEnvironment: ViewEnvironment, + contextForNewView: Context, + container: ViewGroup? + ): View { + // There is currently no way to automatically generate an Android View directly from a + // Composable function, so we need to use ViewGroup.setContent. + val composeContainer = FrameLayout(contextForNewView) + + // This model will be used to feed state updates into the composition. + val renderState = mutableStateOf?>( + value = null, + areEquivalent = StructurallyEqual + ) + + // Entry point to the composition. + composeContainer.setContent(Recomposer.current()) { + // Don't compose anything until we have the first value (which should happen in the initial + // frame). + val (rendering, environment) = renderState.value ?: return@setContent + showRendering(rendering, environment) + } + + composeContainer.bindShowRendering( + initialRendering, + initialViewEnvironment + ) { rendering, environment -> + renderState.value = Pair(rendering, environment) + } + return composeContainer + } +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000000..cf6d83a2ad --- /dev/null +++ b/gradle.properties @@ -0,0 +1,37 @@ +# +# Copyright 2017 Square Inc. +# +# 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. +# +org.gradle.jvmargs='-Dfile.encoding=UTF-8' +org.gradle.parallel=true +android.useAndroidX=true +# Required for ViewBinding. +android.enableJetifier=true + +GROUP=com.squareup.workflow +VERSION_NAME=0.27.0-SNAPSHOT + +POM_DESCRIPTION=Reactive workflows + +POM_URL=https://github.com/square/workflow/ +POM_SCM_URL=https://github.com/square/workflow/ +POM_SCM_CONNECTION=scm:git:git://github.com/square/workflow.git +POM_SCM_DEV_CONNECTION=scm:git:ssh://git@github.com/square/workflow.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=square +POM_DEVELOPER_NAME=Square, Inc. diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100755 index 0000000000000000000000000000000000000000..62d4c053550b91381bbd28b1afc82d634bf73a8a GIT binary patch literal 58910 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 literal 0 HcmV?d00001 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000..fd0c5a38e9 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.4-all.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000000..fbd7c51583 --- /dev/null +++ b/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# 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 +# +# https://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. +# + +############################################################################## +## +## 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='"-Xmx64m" "-Xms64m"' + +# 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 or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; 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=`expr $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" + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000000..a9f778a7a9 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,104 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/samples/hello-compose-binding/build.gradle.kts b/samples/hello-compose-binding/build.gradle.kts new file mode 100644 index 0000000000..4484111d16 --- /dev/null +++ b/samples/hello-compose-binding/build.gradle.kts @@ -0,0 +1,47 @@ +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +/* + * Copyright 2019 Square Inc. + * + * 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. + */ +plugins { + id("com.android.application") + kotlin("android") +} + +apply(from = rootProject.file(".buildscript/android-sample-app.gradle")) +apply(from = rootProject.file(".buildscript/android-ui-tests.gradle")) + +android { + defaultConfig { + applicationId = "com.squareup.sample.hellocomposebinding" + } +} + +apply(from = rootProject.file(".buildscript/configure-compose.gradle")) +tasks.withType { + kotlinOptions.apiVersion = "1.3" +} + +dependencies { + implementation(project(":core-compose")) + + implementation(Dependencies.Compose.layout) + implementation(Dependencies.Compose.material) + implementation(Dependencies.Compose.tooling) + implementation(Dependencies.Compose.foundation) + + androidTestImplementation(Dependencies.Compose.test) + androidTestImplementation(Dependencies.Test.junit) +} diff --git a/samples/hello-compose-binding/src/androidTest/java/com/squareup/sample/hellocomposebinding/HelloBindingTest.kt b/samples/hello-compose-binding/src/androidTest/java/com/squareup/sample/hellocomposebinding/HelloBindingTest.kt new file mode 100644 index 0000000000..9723cbf5d4 --- /dev/null +++ b/samples/hello-compose-binding/src/androidTest/java/com/squareup/sample/hellocomposebinding/HelloBindingTest.kt @@ -0,0 +1,43 @@ +/* + * Copyright 2020 Square Inc. + * + * 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.squareup.sample.hellocomposebinding + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.ui.test.android.AndroidComposeTestRule +import androidx.ui.test.assertIsDisplayed +import androidx.ui.test.doClick +import androidx.ui.test.findByText +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class HelloBindingTest { + + // Launches the activity. + @Rule @JvmField val composeRule = AndroidComposeTestRule() + + @Test fun togglesBetweenStates() { + findByText("Hello") + .assertIsDisplayed() + .doClick() + findByText("Goodbye") + .assertIsDisplayed() + .doClick() + findByText("Hello") + .assertIsDisplayed() + } +} diff --git a/samples/hello-compose-binding/src/main/AndroidManifest.xml b/samples/hello-compose-binding/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..557bdb0d0e --- /dev/null +++ b/samples/hello-compose-binding/src/main/AndroidManifest.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + diff --git a/samples/hello-compose-binding/src/main/java/com/squareup/sample/hellocomposebinding/HelloBinding.kt b/samples/hello-compose-binding/src/main/java/com/squareup/sample/hellocomposebinding/HelloBinding.kt new file mode 100644 index 0000000000..1ab0b009c8 --- /dev/null +++ b/samples/hello-compose-binding/src/main/java/com/squareup/sample/hellocomposebinding/HelloBinding.kt @@ -0,0 +1,57 @@ +/* + * Copyright 2019 Square Inc. + * + * 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.squareup.sample.hellocomposebinding + +import androidx.compose.Composable +import androidx.ui.core.Alignment +import androidx.ui.core.Modifier +import androidx.ui.foundation.Clickable +import androidx.ui.foundation.Text +import androidx.ui.layout.fillMaxSize +import androidx.ui.layout.wrapContentSize +import androidx.ui.material.MaterialTheme +import androidx.ui.material.Surface +import androidx.ui.material.ripple.ripple +import androidx.ui.tooling.preview.Preview +import com.squareup.sample.hellocomposebinding.HelloWorkflow.Rendering +import com.squareup.workflow.ui.compose.bindCompose + +val HelloBinding = bindCompose { rendering, _ -> + MaterialTheme { + DrawHelloRendering(rendering) + } +} + +@Composable +private fun DrawHelloRendering(rendering: Rendering) { + Clickable( + modifier = Modifier.fillMaxSize() + .ripple(bounded = true), + onClick = { rendering.onClick() } + ) { + Text(rendering.message, modifier = Modifier.wrapContentSize(Alignment.Center)) + } +} + +@Preview(heightDp = 150) +@Composable +fun DrawHelloRenderingPreview() { + MaterialTheme { + Surface { + DrawHelloRendering(Rendering("Hello!", onClick = {})) + } + } +} diff --git a/samples/hello-compose-binding/src/main/java/com/squareup/sample/hellocomposebinding/HelloBindingActivity.kt b/samples/hello-compose-binding/src/main/java/com/squareup/sample/hellocomposebinding/HelloBindingActivity.kt new file mode 100644 index 0000000000..4f6302e61a --- /dev/null +++ b/samples/hello-compose-binding/src/main/java/com/squareup/sample/hellocomposebinding/HelloBindingActivity.kt @@ -0,0 +1,37 @@ +/* + * Copyright 2019 Square Inc. + * + * 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.squareup.sample.hellocomposebinding + +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import com.squareup.workflow.diagnostic.SimpleLoggingDiagnosticListener +import com.squareup.workflow.ui.ViewRegistry +import com.squareup.workflow.ui.WorkflowRunner +import com.squareup.workflow.ui.setContentWorkflow + +private val viewRegistry = ViewRegistry(HelloBinding) + +class HelloBindingActivity : AppCompatActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentWorkflow(viewRegistry) { + WorkflowRunner.Config( + HelloWorkflow, + diagnosticListener = SimpleLoggingDiagnosticListener() + ) + } + } +} diff --git a/samples/hello-compose-binding/src/main/java/com/squareup/sample/hellocomposebinding/HelloWorkflow.kt b/samples/hello-compose-binding/src/main/java/com/squareup/sample/hellocomposebinding/HelloWorkflow.kt new file mode 100644 index 0000000000..42a7e8bc0b --- /dev/null +++ b/samples/hello-compose-binding/src/main/java/com/squareup/sample/hellocomposebinding/HelloWorkflow.kt @@ -0,0 +1,66 @@ +/* + * Copyright 2019 Square Inc. + * + * 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.squareup.sample.hellocomposebinding + +import com.squareup.sample.hellocomposebinding.HelloWorkflow.Rendering +import com.squareup.sample.hellocomposebinding.HelloWorkflow.State +import com.squareup.sample.hellocomposebinding.HelloWorkflow.State.Goodbye +import com.squareup.sample.hellocomposebinding.HelloWorkflow.State.Hello +import com.squareup.workflow.RenderContext +import com.squareup.workflow.Snapshot +import com.squareup.workflow.StatefulWorkflow +import com.squareup.workflow.action +import com.squareup.workflow.parse + +object HelloWorkflow : StatefulWorkflow() { + enum class State { + Hello, + Goodbye; + + fun theOtherState(): State = when (this) { + Hello -> Goodbye + Goodbye -> Hello + } + } + + data class Rendering( + val message: String, + val onClick: () -> Unit + ) + + private val helloAction = action { + nextState = nextState.theOtherState() + } + + override fun initialState( + props: Unit, + snapshot: Snapshot? + ): State = snapshot?.bytes?.parse { source -> if (source.readInt() == 1) Hello else Goodbye } + ?: Hello + + override fun render( + props: Unit, + state: State, + context: RenderContext + ): Rendering { + return Rendering( + message = state.name, + onClick = { context.actionSink.send(helloAction) } + ) + } + + override fun snapshotState(state: State): Snapshot = Snapshot.of(if (state == Hello) 1 else 0) +} diff --git a/samples/hello-compose-binding/src/main/res/values/strings.xml b/samples/hello-compose-binding/src/main/res/values/strings.xml new file mode 100644 index 0000000000..a2cc91abdc --- /dev/null +++ b/samples/hello-compose-binding/src/main/res/values/strings.xml @@ -0,0 +1,18 @@ + + + Hello Compose Binding + diff --git a/samples/hello-compose-binding/src/main/res/values/styles.xml b/samples/hello-compose-binding/src/main/res/values/styles.xml new file mode 100644 index 0000000000..d5b0c9026f --- /dev/null +++ b/samples/hello-compose-binding/src/main/res/values/styles.xml @@ -0,0 +1,23 @@ + + + + + + + diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000000..220ad639ef --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,21 @@ +/* + * Copyright 2017 Square Inc. + * + * 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. + */ +rootProject.name = "workflow-compose" + +include( + ":core-compose", + ":samples:hello-compose-binding" +) From dea0cce3368ff80821a7819bfc19c8762271682f Mon Sep 17 00:00:00 2001 From: Zach Klippenstein Date: Tue, 12 May 2020 11:25:10 -0700 Subject: [PATCH 02/67] Add workaround for Nexus issue with sha256/512 checksums. See https://github.com/gradle/gradle/issues/11308. --- gradle.properties | 3 +++ 1 file changed, 3 insertions(+) diff --git a/gradle.properties b/gradle.properties index cf6d83a2ad..f3dd00919c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -19,6 +19,9 @@ android.useAndroidX=true # Required for ViewBinding. android.enableJetifier=true +# Required to publish to Nexus (see https://github.com/gradle/gradle/issues/11308) +systemProp.org.gradle.internal.publish.checksums.insecure=true + GROUP=com.squareup.workflow VERSION_NAME=0.27.0-SNAPSHOT From c793477119c425c0bed2f0e360ca230a93c7fa93 Mon Sep 17 00:00:00 2001 From: Zach Klippenstein Date: Tue, 12 May 2020 11:32:59 -0700 Subject: [PATCH 03/67] Remove unused JMH dependencies and configuration. --- .github/workflows/kotlin.yml | 2 -- build.gradle.kts | 1 - buildSrc/src/main/java/Dependencies.kt | 6 ------ 3 files changed, 9 deletions(-) diff --git a/.github/workflows/kotlin.yml b/.github/workflows/kotlin.yml index 3687cc66b4..227984829f 100644 --- a/.github/workflows/kotlin.yml +++ b/.github/workflows/kotlin.yml @@ -73,8 +73,6 @@ jobs: - lint - ktlintCheck - detekt - # Build the JMH benchmarks to verify, but don't run them. - - jmhJar steps: - uses: actions/checkout@v2 - name: set up JDK 1.8 diff --git a/build.gradle.kts b/build.gradle.kts index dddff11c42..ce98afbe3c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -23,7 +23,6 @@ buildscript { classpath(Dependencies.android_gradle_plugin) classpath(Dependencies.detekt) classpath(Dependencies.dokka) - classpath(Dependencies.Jmh.gradlePlugin) classpath(Dependencies.Kotlin.binaryCompatibilityValidatorPlugin) classpath(Dependencies.Kotlin.gradlePlugin) classpath(Dependencies.Kotlin.Serialization.gradlePlugin) diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt index 6da85cac82..5db04d1a95 100644 --- a/buildSrc/src/main/java/Dependencies.kt +++ b/buildSrc/src/main/java/Dependencies.kt @@ -98,12 +98,6 @@ object Dependencies { const val dokka = "org.jetbrains.dokka:dokka-gradle-plugin:0.10.0" - object Jmh { - const val gradlePlugin = "me.champeau.gradle:jmh-gradle-plugin:0.5.0" - const val core = "org.openjdk.jmh:jmh-core:1.23" - const val generator = "org.openjdk.jmh:jmh-generator-annprocess:1.23" - } - const val mavenPublish = "com.vanniktech:gradle-maven-publish-plugin:0.11.1" const val ktlint = "org.jlleitschuh.gradle:ktlint-gradle:9.2.0" const val lanterna = "com.googlecode.lanterna:lanterna:3.0.2" From 2e48b4d16a1c9502905b1801f7eb7638dd21980f Mon Sep 17 00:00:00 2001 From: Zach Klippenstein Date: Tue, 12 May 2020 11:51:49 -0700 Subject: [PATCH 04/67] Remove unused dependencies. --- build.gradle.kts | 1 - buildSrc/src/main/java/Dependencies.kt | 77 ++++---------------------- 2 files changed, 12 insertions(+), 66 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index ce98afbe3c..320746cceb 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -25,7 +25,6 @@ buildscript { classpath(Dependencies.dokka) classpath(Dependencies.Kotlin.binaryCompatibilityValidatorPlugin) classpath(Dependencies.Kotlin.gradlePlugin) - classpath(Dependencies.Kotlin.Serialization.gradlePlugin) classpath(Dependencies.ktlint) classpath(Dependencies.mavenPublish) } diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt index 5db04d1a95..d48075ee1a 100644 --- a/buildSrc/src/main/java/Dependencies.kt +++ b/buildSrc/src/main/java/Dependencies.kt @@ -20,7 +20,6 @@ import kotlin.reflect.full.declaredMembers object Versions { const val compose = "0.1.0-dev10" - const val coroutines = "1.3.4" const val kotlin = "1.3.71" const val targetSdk = 29 const val workflow = "0.26.0" @@ -31,27 +30,7 @@ object Dependencies { const val android_gradle_plugin = "com.android.tools.build:gradle:4.1.0-alpha08" object AndroidX { - const val activity = "androidx.activity:activity:1.1.0" - const val annotations = "androidx.annotation:annotation:1.1.0" const val appcompat = "androidx.appcompat:appcompat:1.1.0" - const val constraint_layout = "androidx.constraintlayout:constraintlayout:1.1.3" - const val fragment = "androidx.fragment:fragment:1.2.3" - const val gridlayout = "androidx.gridlayout:gridlayout:1.0.0" - - object Lifecycle { - const val reactivestreams = "androidx.lifecycle:lifecycle-reactivestreams-ktx:2.2.0" - } - - // Note that we're not using the actual androidx material dep yet, it's still alpha. - const val material = "com.google.android.material:material:1.1.0" - const val recyclerview = "androidx.recyclerview:recyclerview:1.1.0" - - // Note that we are *not* using lifecycle-viewmodel-savedstate, which at this - // writing is still in beta and still fixing bad bugs. Probably we'll never bother to, - // it doesn't really add value for us. - const val savedstate = "androidx.savedstate:savedstate:1.0.0" - const val transition = "androidx.transition:transition:1.3.1" - const val viewbinding = "androidx.databinding:viewbinding:3.6.2" } object Compose { @@ -68,64 +47,28 @@ object Dependencies { const val binaryCompatibilityValidatorPlugin = "org.jetbrains.kotlinx:binary-compatibility-validator:0.2.3" const val gradlePlugin = "org.jetbrains.kotlin:kotlin-gradle-plugin:${Versions.kotlin}" - const val stdlib = "org.jetbrains.kotlin:kotlin-stdlib-jdk8" - - object Coroutines { - const val android = "org.jetbrains.kotlinx:kotlinx-coroutines-android:${Versions.coroutines}" - const val core = "org.jetbrains.kotlinx:kotlinx-coroutines-core:${Versions.coroutines}" - - const val test = "org.jetbrains.kotlinx:kotlinx-coroutines-test:${Versions.coroutines}" - } - - const val moshi = "com.squareup.moshi:moshi-kotlin:1.9.2" const val reflect = "org.jetbrains.kotlin:kotlin-reflect:${Versions.kotlin}" - - object Serialization { - const val gradlePlugin = "org.jetbrains.kotlin:kotlin-serialization:${Versions.kotlin}" - const val runtime = "org.jetbrains.kotlinx:kotlinx-serialization-runtime:0.20.0" - const val kaml = "com.charleskorn.kaml:kaml:0.16.1" - } - - object Test { - const val common = "org.jetbrains.kotlin:kotlin-test-common" - const val annotations = "org.jetbrains.kotlin:kotlin-test-annotations-common" - const val jdk = "org.jetbrains.kotlin:kotlin-test-junit" - - const val mockito = "com.nhaarman:mockito-kotlin-kt1.1:1.6.0" - } } const val dokka = "org.jetbrains.dokka:dokka-gradle-plugin:0.10.0" - const val mavenPublish = "com.vanniktech:gradle-maven-publish-plugin:0.11.1" const val ktlint = "org.jlleitschuh.gradle:ktlint-gradle:9.2.0" - const val lanterna = "com.googlecode.lanterna:lanterna:3.0.2" const val detekt = "io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.0.1" - const val okio = "com.squareup.okio:okio:2.5.0" - - object Annotations { - const val intellij = "org.jetbrains:annotations:19.0.0" - } object Test { object AndroidX { - object Espresso { - const val contrib = "androidx.test.espresso:espresso-contrib:3.2.0" - const val core = "androidx.test.espresso:espresso-core:3.2.0" - const val idlingResource = "androidx.test.espresso:espresso-idling-resource:3.2.0" - const val intents = "androidx.test.espresso:espresso-intents:3.2.0" - } - const val junitExt = "androidx.test.ext:junit:1.1.1" const val runner = "androidx.test:runner:1.2.0" const val truthExt = "androidx.test.ext:truth:1.2.0" const val uiautomator = "androidx.test.uiautomator:uiautomator:2.2.0" + + object Espresso { + const val core = "androidx.test.espresso:espresso-core:3.2.0" + } } - const val hamcrestCore = "org.hamcrest:hamcrest-core:2.2" const val junit = "junit:junit:4.13" - const val mockito = "org.mockito:mockito-core:3.3.3" const val truth = "com.google.truth:truth:1.0.1" } @@ -149,10 +92,14 @@ object Dependencies { * ``` */ @JvmName("get") -fun getDependencyFromGroovy(path: String): String = Dependencies.resolveObject( - path.toLowerCase(US) - .split(".") -) +fun getDependencyFromGroovy(path: String): String = try { + Dependencies.resolveObject( + path.toLowerCase(US) + .split(".") + ) +} catch (e: Throwable) { + throw IllegalArgumentException("Error resolving dependency: $path", e) +} private tailrec fun Any.resolveObject(pathParts: List): String { require(pathParts.isNotEmpty()) From fd09920db7a12e11a9654c8a78f7f8516759fa43 Mon Sep 17 00:00:00 2001 From: Zach Klippenstein Date: Tue, 12 May 2020 11:23:53 -0700 Subject: [PATCH 05/67] Upgrade workflow and coroutine dependencies to latest versions. --- buildSrc/src/main/java/Dependencies.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt index d48075ee1a..b2226f9600 100644 --- a/buildSrc/src/main/java/Dependencies.kt +++ b/buildSrc/src/main/java/Dependencies.kt @@ -22,7 +22,7 @@ object Versions { const val compose = "0.1.0-dev10" const val kotlin = "1.3.71" const val targetSdk = 29 - const val workflow = "0.26.0" + const val workflow = "0.28.0" } @Suppress("unused") From 4060030b6ea8a64690743cbba463c738363ebb89 Mon Sep 17 00:00:00 2001 From: Zach Klippenstein Date: Fri, 8 May 2020 09:58:18 -0700 Subject: [PATCH 06/67] Cleanup how ComposableViewFactory handles initial rendering. Duplicate of https://github.com/square/workflow/pull/1146 --- .../workflow/ui/compose/ComposeViewFactory.kt | 27 +++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactory.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactory.kt index b095f296f7..f239261673 100644 --- a/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactory.kt +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactory.kt @@ -23,6 +23,7 @@ import android.view.View import android.view.ViewGroup import android.widget.FrameLayout import androidx.compose.Composable +import androidx.compose.FrameManager import androidx.compose.Recomposer import androidx.compose.StructurallyEqual import androidx.compose.mutableStateOf @@ -78,26 +79,36 @@ internal class ComposeViewFactory( // Composable function, so we need to use ViewGroup.setContent. val composeContainer = FrameLayout(contextForNewView) - // This model will be used to feed state updates into the composition. + // Create a single MutableState to feed state updates into the composition. + // We could also have two separate MutableStates, but using a Pair both makes it clear and + // enforces that both values are always updated together. val renderState = mutableStateOf?>( + // This will be updated immediately by bindShowRendering below. value = null, areEquivalent = StructurallyEqual ) - // Entry point to the composition. - composeContainer.setContent(Recomposer.current()) { - // Don't compose anything until we have the first value (which should happen in the initial - // frame). - val (rendering, environment) = renderState.value ?: return@setContent - showRendering(rendering, environment) - } + // Models will throw if their properties are accessed when there is no frame open. Currently, + // that will be the case if the model is accessed before any other Compose infrastructure has + // ran, i.e. if this view factory is the first compose code to run in the app. + // I believe that eventually there will be a global frame that will make this unnecessary. + FrameManager.ensureStarted() + // Update the state whenever a new rendering is emitted. composeContainer.bindShowRendering( initialRendering, initialViewEnvironment ) { rendering, environment -> + // This lambda will be executed synchronously before bindShowRendering returns. renderState.value = Pair(rendering, environment) } + + // Entry point to the world of Compose. + composeContainer.setContent(Recomposer.current()) { + val (rendering, environment) = renderState.value!! + showRendering(rendering, environment) + } + return composeContainer } } From 40ab339d6fd46d54eade3010914b984524492845 Mon Sep 17 00:00:00 2001 From: Zach Klippenstein Date: Sat, 22 Feb 2020 18:12:02 -0800 Subject: [PATCH 07/67] Introduce ComposeWorkflow, a self-rendering Workflow. `ComposeWorkflow` is like a `StatelessWorkflow`, but it has a special `render` method: it's a `@Composable` function, and it functions like the body of a `bindCompose` lambda where the rendering is just the workflow's props and a `Sink` accepting `OutputT`s. --- core-compose/api/core-compose.api | 17 ++++ .../workflow/compose/ComposeRendering.kt | 51 +++++++++++ .../workflow/compose/ComposeWorkflow.kt | 57 +++++++++++++ .../workflow/compose/ComposeWorkflowImpl.kt | 84 +++++++++++++++++++ .../hello-compose-rendering/build.gradle.kts | 51 +++++++++++ .../HelloComposeRenderingTest.kt | 43 ++++++++++ .../src/main/AndroidManifest.xml | 36 ++++++++ .../HelloComposeRenderingActivity.kt | 38 +++++++++ .../HelloRenderingWorkflow.kt | 56 +++++++++++++ .../hellocomposerendering/HelloWorkflow.kt | 56 +++++++++++++ .../src/main/res/values/strings.xml | 18 ++++ .../src/main/res/values/styles.xml | 23 +++++ .../ui/compose/tooling/ComposeWorkflows.kt | 43 ++++++++++ settings.gradle.kts | 3 +- 14 files changed, 575 insertions(+), 1 deletion(-) create mode 100644 core-compose/src/main/java/com/squareup/workflow/compose/ComposeRendering.kt create mode 100644 core-compose/src/main/java/com/squareup/workflow/compose/ComposeWorkflow.kt create mode 100644 core-compose/src/main/java/com/squareup/workflow/compose/ComposeWorkflowImpl.kt create mode 100644 samples/hello-compose-rendering/build.gradle.kts create mode 100644 samples/hello-compose-rendering/src/androidTest/java/com/squareup/sample/hellocomposerendering/HelloComposeRenderingTest.kt create mode 100644 samples/hello-compose-rendering/src/main/AndroidManifest.xml create mode 100644 samples/hello-compose-rendering/src/main/java/com/squareup/sample/hellocomposerendering/HelloComposeRenderingActivity.kt create mode 100644 samples/hello-compose-rendering/src/main/java/com/squareup/sample/hellocomposerendering/HelloRenderingWorkflow.kt create mode 100644 samples/hello-compose-rendering/src/main/java/com/squareup/sample/hellocomposerendering/HelloWorkflow.kt create mode 100644 samples/hello-compose-rendering/src/main/res/values/strings.xml create mode 100644 samples/hello-compose-rendering/src/main/res/values/styles.xml create mode 100644 samples/nested-renderings/src/main/res/values/java/com/squareup/workflow/ui/compose/tooling/ComposeWorkflows.kt diff --git a/core-compose/api/core-compose.api b/core-compose/api/core-compose.api index 257b70fba0..1456cc39df 100644 --- a/core-compose/api/core-compose.api +++ b/core-compose/api/core-compose.api @@ -1,3 +1,20 @@ +public final class com/squareup/workflow/compose/ComposeRendering { + public static final field Companion Lcom/squareup/workflow/compose/ComposeRendering$Companion; + public static final fun ()V + public fun (Lkotlin/jvm/functions/Function2;)V +} + +public final class com/squareup/workflow/compose/ComposeRendering$Companion { + public final fun getFactory ()Lcom/squareup/workflow/ui/ViewFactory; + public final fun getNoopRendering ()Lcom/squareup/workflow/compose/ComposeRendering; +} + +public abstract class com/squareup/workflow/compose/ComposeWorkflow : com/squareup/workflow/Workflow { + public fun ()V + public fun asStatefulWorkflow ()Lcom/squareup/workflow/StatefulWorkflow; + public abstract fun render (Ljava/lang/Object;Lcom/squareup/workflow/Sink;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/compose/Composer;)V +} + public final class com/squareup/workflow/ui/compose/ComposeViewFactory : com/squareup/workflow/ui/ViewFactory { public fun (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function3;)V public fun buildView (Ljava/lang/Object;Lcom/squareup/workflow/ui/ViewEnvironment;Landroid/content/Context;Landroid/view/ViewGroup;)Landroid/view/View; diff --git a/core-compose/src/main/java/com/squareup/workflow/compose/ComposeRendering.kt b/core-compose/src/main/java/com/squareup/workflow/compose/ComposeRendering.kt new file mode 100644 index 0000000000..13c303e3f4 --- /dev/null +++ b/core-compose/src/main/java/com/squareup/workflow/compose/ComposeRendering.kt @@ -0,0 +1,51 @@ +/* + * Copyright 2020 Square Inc. + * + * 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:Suppress("RemoveEmptyParenthesesFromAnnotationEntry") + +package com.squareup.workflow.compose + +import androidx.compose.Composable +import com.squareup.workflow.compose.ComposeRendering.Companion.Factory +import com.squareup.workflow.compose.ComposeRendering.Companion.NoopRendering +import com.squareup.workflow.ui.ViewEnvironment +import com.squareup.workflow.ui.ViewFactory +import com.squareup.workflow.ui.compose.bindCompose + +/** + * A workflow rendering that renders itself using a [Composable] function. + * + * This is the rendering type of [ComposeWorkflow]. To stub out [ComposeWorkflow]s in `RenderTester` + * tests, use [NoopRendering]. + * + * To use this type, make sure your `ViewRegistry` registers [Factory]. + */ +class ComposeRendering internal constructor( + internal val render: @Composable() (ViewEnvironment) -> Unit +) { + companion object { + /** + * A [ViewFactory] that renders a [ComposeRendering]. + */ + val Factory: ViewFactory = bindCompose { rendering, environment -> + rendering.render(environment) + } + + /** + * A [ComposeRendering] that doesn't do anything. Useful for unit testing. + */ + val NoopRendering = ComposeRendering {} + } +} diff --git a/core-compose/src/main/java/com/squareup/workflow/compose/ComposeWorkflow.kt b/core-compose/src/main/java/com/squareup/workflow/compose/ComposeWorkflow.kt new file mode 100644 index 0000000000..abfbcd4852 --- /dev/null +++ b/core-compose/src/main/java/com/squareup/workflow/compose/ComposeWorkflow.kt @@ -0,0 +1,57 @@ +/* + * Copyright 2020 Square Inc. + * + * 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.squareup.workflow.compose + +import androidx.compose.Composable +import com.squareup.workflow.Sink +import com.squareup.workflow.StatefulWorkflow +import com.squareup.workflow.Workflow +import com.squareup.workflow.ui.ViewEnvironment + +/** + * A stateless [Workflow][com.squareup.workflow.Workflow] that [renders][render] itself as + * [Composable] function. Effectively defines an inline + * [bindCompose][com.squareup.workflow.ui.compose.bindCompose]. + * + * This workflow does not have access to a [RenderContext][com.squareup.workflow.RenderContext] + * since render contexts are only valid during render passes, and this workflow's [render] method + * is invoked after the render pass, when view bindings are being shown. + * + * While this workflow is "stateless" in the usual workflow sense (it doesn't have a `StateT` type), + * since [render] is a Composable function, it can use all the usual Compose facilities for state + * management. + */ +abstract class ComposeWorkflow : + Workflow { + + /** + * Renders [props] using Compose. This function will be called to update the UI whenever the + * [props] or [viewEnvironment] changes. + * + * @param props The data to render. + * @param outputSink A [Sink] that can be used from UI event handlers to send outputs to this + * workflow's parent. + * @param viewEnvironment The [ViewEnvironment] passed down through the `ViewBinding` pipeline. + */ + @Composable abstract fun render( + props: PropsT, + outputSink: Sink, + viewEnvironment: ViewEnvironment + ) + + override fun asStatefulWorkflow(): StatefulWorkflow = + ComposeWorkflowImpl(this) +} diff --git a/core-compose/src/main/java/com/squareup/workflow/compose/ComposeWorkflowImpl.kt b/core-compose/src/main/java/com/squareup/workflow/compose/ComposeWorkflowImpl.kt new file mode 100644 index 0000000000..cdd4eca9c3 --- /dev/null +++ b/core-compose/src/main/java/com/squareup/workflow/compose/ComposeWorkflowImpl.kt @@ -0,0 +1,84 @@ +/* + * Copyright 2020 Square Inc. + * + * 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.squareup.workflow.compose + +import androidx.compose.MutableState +import androidx.compose.StructurallyEqual +import androidx.compose.mutableStateOf +import com.squareup.workflow.RenderContext +import com.squareup.workflow.Sink +import com.squareup.workflow.Snapshot +import com.squareup.workflow.StatefulWorkflow +import com.squareup.workflow.action +import com.squareup.workflow.compose.ComposeWorkflowImpl.State +import com.squareup.workflow.contraMap + +internal class ComposeWorkflowImpl( + private val workflow: ComposeWorkflow +) : StatefulWorkflow, OutputT, ComposeRendering>() { + + // This doesn't need to be a @Model, it only gets set once, before the composable ever runs. + class SinkHolder(var sink: Sink? = null) + + data class State( + val propsHolder: MutableState, + val sinkHolder: SinkHolder, + val rendering: ComposeRendering + ) + + override fun initialState( + props: PropsT, + snapshot: Snapshot? + ): State { + val propsHolder = mutableStateOf(props, areEquivalent = StructurallyEqual) + val sinkHolder = SinkHolder() + return State(propsHolder, sinkHolder, ComposeRendering { environment -> + // The sink will get set on the first render pass, so it should never be null. + val sink = sinkHolder.sink!! + // Important: Use the props from the MutableState, _not_ the one passed into render. + workflow.render(propsHolder.value, sink, environment) + }) + } + + override fun onPropsChanged( + old: PropsT, + new: PropsT, + state: State + ): State { + state.propsHolder.value = new + return state + } + + override fun render( + props: PropsT, + state: State, + context: RenderContext, OutputT> + ): ComposeRendering { + // The first render pass needs to cache the sink. The sink is reusable, so we can just pass the + // same one every time. + if (state.sinkHolder.sink == null) { + state.sinkHolder.sink = context.actionSink.contraMap(::forwardOutput) + } + + // onPropsChanged will ensure the rendering is re-composed when the props changes. + return state.rendering + } + + // Compiler bug doesn't let us call Snapshot.EMPTY. + override fun snapshotState(state: State): Snapshot = Snapshot.of("") + + private fun forwardOutput(output: OutputT) = action { setOutput(output) } +} diff --git a/samples/hello-compose-rendering/build.gradle.kts b/samples/hello-compose-rendering/build.gradle.kts new file mode 100644 index 0000000000..b5193c6f23 --- /dev/null +++ b/samples/hello-compose-rendering/build.gradle.kts @@ -0,0 +1,51 @@ +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +/* + * Copyright 2020 Square Inc. + * + * 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. + */ +plugins { + id("com.android.application") + kotlin("android") +} + +apply(from = rootProject.file(".buildscript/configure-android-defaults.gradle")) +apply(from = rootProject.file(".buildscript/android-sample-app.gradle")) + +android { + defaultConfig { + applicationId = "com.squareup.sample.hellocomposerendering" + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } +} + +apply(from = rootProject.file(".buildscript/configure-compose.gradle")) +tasks.withType { + kotlinOptions.apiVersion = "1.3" +} + +dependencies { + implementation(project(":core-compose")) + implementation(Dependencies.AndroidX.appcompat) + implementation(Dependencies.Compose.foundation) + implementation(Dependencies.Compose.layout) + implementation(Dependencies.Compose.material) + implementation(Dependencies.Workflow.core) + implementation(Dependencies.Workflow.runtime) + + androidTestImplementation(Dependencies.Compose.test) + androidTestImplementation(Dependencies.Test.AndroidX.junitExt) + androidTestImplementation(Dependencies.Test.junit) +} diff --git a/samples/hello-compose-rendering/src/androidTest/java/com/squareup/sample/hellocomposerendering/HelloComposeRenderingTest.kt b/samples/hello-compose-rendering/src/androidTest/java/com/squareup/sample/hellocomposerendering/HelloComposeRenderingTest.kt new file mode 100644 index 0000000000..8b98d717d6 --- /dev/null +++ b/samples/hello-compose-rendering/src/androidTest/java/com/squareup/sample/hellocomposerendering/HelloComposeRenderingTest.kt @@ -0,0 +1,43 @@ +/* + * Copyright 2020 Square Inc. + * + * 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.squareup.sample.hellocomposerendering + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.ui.test.android.AndroidComposeTestRule +import androidx.ui.test.assertIsDisplayed +import androidx.ui.test.doClick +import androidx.ui.test.findByText +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class HelloComposeRenderingTest { + + // Launches the activity. + @Rule @JvmField val composeRule = AndroidComposeTestRule() + + @Test fun togglesBetweenStates() { + findByText("Hello") + .assertIsDisplayed() + .doClick() + findByText("Goodbye") + .assertIsDisplayed() + .doClick() + findByText("Hello") + .assertIsDisplayed() + } +} diff --git a/samples/hello-compose-rendering/src/main/AndroidManifest.xml b/samples/hello-compose-rendering/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..dad2243e3b --- /dev/null +++ b/samples/hello-compose-rendering/src/main/AndroidManifest.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + diff --git a/samples/hello-compose-rendering/src/main/java/com/squareup/sample/hellocomposerendering/HelloComposeRenderingActivity.kt b/samples/hello-compose-rendering/src/main/java/com/squareup/sample/hellocomposerendering/HelloComposeRenderingActivity.kt new file mode 100644 index 0000000000..3bf820720e --- /dev/null +++ b/samples/hello-compose-rendering/src/main/java/com/squareup/sample/hellocomposerendering/HelloComposeRenderingActivity.kt @@ -0,0 +1,38 @@ +/* + * Copyright 2020 Square Inc. + * + * 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.squareup.sample.hellocomposerendering + +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import com.squareup.workflow.compose.ComposeRendering +import com.squareup.workflow.diagnostic.SimpleLoggingDiagnosticListener +import com.squareup.workflow.ui.ViewRegistry +import com.squareup.workflow.ui.WorkflowRunner +import com.squareup.workflow.ui.setContentWorkflow + +private val viewRegistry = ViewRegistry(ComposeRendering.Factory) + +class HelloComposeRenderingActivity : AppCompatActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentWorkflow(viewRegistry) { + WorkflowRunner.Config( + HelloWorkflow, + diagnosticListener = SimpleLoggingDiagnosticListener() + ) + } + } +} diff --git a/samples/hello-compose-rendering/src/main/java/com/squareup/sample/hellocomposerendering/HelloRenderingWorkflow.kt b/samples/hello-compose-rendering/src/main/java/com/squareup/sample/hellocomposerendering/HelloRenderingWorkflow.kt new file mode 100644 index 0000000000..72b1429f87 --- /dev/null +++ b/samples/hello-compose-rendering/src/main/java/com/squareup/sample/hellocomposerendering/HelloRenderingWorkflow.kt @@ -0,0 +1,56 @@ +/* + * Copyright 2020 Square Inc. + * + * 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.squareup.sample.hellocomposerendering + +import androidx.compose.Composable +import androidx.ui.core.Alignment +import androidx.ui.core.Modifier +import androidx.ui.foundation.Clickable +import androidx.ui.foundation.Text +import androidx.ui.layout.fillMaxSize +import androidx.ui.layout.wrapContentSize +import androidx.ui.material.MaterialTheme +import androidx.ui.material.ripple.ripple +import com.squareup.sample.hellocomposerendering.HelloRenderingWorkflow.Toggle +import com.squareup.workflow.Sink +import com.squareup.workflow.compose.ComposeWorkflow +import com.squareup.workflow.ui.ViewEnvironment + +/** + * A [ComposeWorkflow] that is used by [HelloWorkflow] to render the screen. + * + * This workflow has type `Workflow`. + */ +object HelloRenderingWorkflow : ComposeWorkflow() { + + object Toggle + + @Composable override fun render( + props: String, + outputSink: Sink, + viewEnvironment: ViewEnvironment + ) { + MaterialTheme { + Clickable( + onClick = { outputSink.send(Toggle) }, + modifier = Modifier.ripple(bounded = true) + .fillMaxSize() + ) { + Text(props, modifier = Modifier.wrapContentSize(Alignment.Center)) + } + } + } +} diff --git a/samples/hello-compose-rendering/src/main/java/com/squareup/sample/hellocomposerendering/HelloWorkflow.kt b/samples/hello-compose-rendering/src/main/java/com/squareup/sample/hellocomposerendering/HelloWorkflow.kt new file mode 100644 index 0000000000..2d784cfc92 --- /dev/null +++ b/samples/hello-compose-rendering/src/main/java/com/squareup/sample/hellocomposerendering/HelloWorkflow.kt @@ -0,0 +1,56 @@ +/* + * Copyright 2020 Square Inc. + * + * 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.squareup.sample.hellocomposerendering + +import com.squareup.sample.hellocomposerendering.HelloWorkflow.State +import com.squareup.sample.hellocomposerendering.HelloWorkflow.State.Goodbye +import com.squareup.sample.hellocomposerendering.HelloWorkflow.State.Hello +import com.squareup.workflow.RenderContext +import com.squareup.workflow.Snapshot +import com.squareup.workflow.StatefulWorkflow +import com.squareup.workflow.action +import com.squareup.workflow.compose.ComposeRendering +import com.squareup.workflow.parse + +object HelloWorkflow : StatefulWorkflow() { + enum class State { + Hello, + Goodbye; + + fun theOtherState(): State = when (this) { + Hello -> Goodbye + Goodbye -> Hello + } + } + + private val helloAction = action { + nextState = nextState.theOtherState() + } + + override fun initialState( + props: Unit, + snapshot: Snapshot? + ): State = snapshot?.bytes?.parse { source -> if (source.readInt() == 1) Hello else Goodbye } + ?: Hello + + override fun render( + props: Unit, + state: State, + context: RenderContext + ): ComposeRendering = context.renderChild(HelloRenderingWorkflow, state.name) { helloAction } + + override fun snapshotState(state: State): Snapshot = Snapshot.of(if (state == Hello) 1 else 0) +} diff --git a/samples/hello-compose-rendering/src/main/res/values/strings.xml b/samples/hello-compose-rendering/src/main/res/values/strings.xml new file mode 100644 index 0000000000..ad1e200b1f --- /dev/null +++ b/samples/hello-compose-rendering/src/main/res/values/strings.xml @@ -0,0 +1,18 @@ + + + Hello Compose Rendering + diff --git a/samples/hello-compose-rendering/src/main/res/values/styles.xml b/samples/hello-compose-rendering/src/main/res/values/styles.xml new file mode 100644 index 0000000000..1c37bd6d5b --- /dev/null +++ b/samples/hello-compose-rendering/src/main/res/values/styles.xml @@ -0,0 +1,23 @@ + + + + + + + diff --git a/samples/nested-renderings/src/main/res/values/java/com/squareup/workflow/ui/compose/tooling/ComposeWorkflows.kt b/samples/nested-renderings/src/main/res/values/java/com/squareup/workflow/ui/compose/tooling/ComposeWorkflows.kt new file mode 100644 index 0000000000..85d104451c --- /dev/null +++ b/samples/nested-renderings/src/main/res/values/java/com/squareup/workflow/ui/compose/tooling/ComposeWorkflows.kt @@ -0,0 +1,43 @@ +/* + * Copyright 2020 Square Inc. + * + * 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.squareup.workflow.ui.compose.tooling + +import androidx.compose.Composable +import androidx.compose.Immutable +import com.squareup.workflow.Sink +import com.squareup.workflow.compose.ComposeWorkflow +import com.squareup.workflow.ui.ViewBinding + +/** + * Draws this [ComposeWorkflow] using a special preview `ViewRegistry`. + * + * The sink passed to [ComposeWorkflow.render] will be a no-op implementation, since previews can't + * process input. + * + * Use inside `@Preview` Composable functions. + */ +//@Composable fun ComposeWorkflow.preview( +// props: PropsT, +// stubBinding: ViewBinding = PreviewStubViewBinding +//) { +// val containerHints = PreviewContainerHints(stubBinding) +// render(props, NoopSink, containerHints) +//} + +//@Immutable +//private object NoopSink : Sink { +// override fun send(value: Any?) = Unit +//} diff --git a/settings.gradle.kts b/settings.gradle.kts index 220ad639ef..1b542181a0 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -17,5 +17,6 @@ rootProject.name = "workflow-compose" include( ":core-compose", - ":samples:hello-compose-binding" + ":samples:hello-compose-binding", + ":samples:hello-compose-rendering" ) From 430d476bee1bd9bef8e52758b27c7561e29cd0e2 Mon Sep 17 00:00:00 2001 From: Zach Klippenstein Date: Wed, 30 Oct 2019 10:31:04 -0700 Subject: [PATCH 08/67] Add the ability to display nested renderings with bindCompose. This is the Compose analog to `WorkflowViewStub`, and delegates to `WorkflowViewStub` except when the child `ViewFactory` was also created with `bindCompose`. In that case, it just invokes that factory's Composable function directly to avoid jumping back out into legacy View land. --- build.gradle.kts | 9 +- core-compose/api/core-compose.api | 5 + .../workflow/ui/compose/ComposeViewFactory.kt | 23 +++- .../workflow/ui/compose/ViewEnvironments.kt | 60 +++++++++ .../workflow/ui/compose/ViewFactories.kt | 85 +++++++++++++ .../workflow/ui/compose/ViewRegistries.kt | 42 ++++++ samples/nested-renderings/build.gradle.kts | 48 +++++++ .../nestedrenderings/NestedRenderingsTest.kt | 58 +++++++++ .../src/main/AndroidManifest.xml | 38 ++++++ .../sample/nestedrenderings/LegacyRunner.kt | 40 ++++++ .../NestedRenderingsActivity.kt | 40 ++++++ .../nestedrenderings/RecursiveViewFactory.kt | 120 ++++++++++++++++++ .../nestedrenderings/RecursiveWorkflow.kt | 86 +++++++++++++ .../src/main/res/layout/legacy_view.xml | 36 ++++++ .../src/main/res/values/dimens.xml | 18 +++ .../src/main/res/values/strings.xml | 18 +++ .../src/main/res/values/styles.xml | 23 ++++ settings.gradle.kts | 3 +- 18 files changed, 744 insertions(+), 8 deletions(-) create mode 100644 core-compose/src/main/java/com/squareup/workflow/ui/compose/ViewEnvironments.kt create mode 100644 core-compose/src/main/java/com/squareup/workflow/ui/compose/ViewFactories.kt create mode 100644 core-compose/src/main/java/com/squareup/workflow/ui/compose/ViewRegistries.kt create mode 100644 samples/nested-renderings/build.gradle.kts create mode 100644 samples/nested-renderings/src/androidTest/java/com/squareup/sample/nestedrenderings/NestedRenderingsTest.kt create mode 100644 samples/nested-renderings/src/main/AndroidManifest.xml create mode 100644 samples/nested-renderings/src/main/java/com/squareup/sample/nestedrenderings/LegacyRunner.kt create mode 100644 samples/nested-renderings/src/main/java/com/squareup/sample/nestedrenderings/NestedRenderingsActivity.kt create mode 100644 samples/nested-renderings/src/main/java/com/squareup/sample/nestedrenderings/RecursiveViewFactory.kt create mode 100644 samples/nested-renderings/src/main/java/com/squareup/sample/nestedrenderings/RecursiveWorkflow.kt create mode 100644 samples/nested-renderings/src/main/res/layout/legacy_view.xml create mode 100644 samples/nested-renderings/src/main/res/values/dimens.xml create mode 100644 samples/nested-renderings/src/main/res/values/strings.xml create mode 100644 samples/nested-renderings/src/main/res/values/styles.xml diff --git a/build.gradle.kts b/build.gradle.kts index 320746cceb..fe33f0a496 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import org.jetbrains.dokka.gradle.DokkaTask import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import org.jlleitschuh.gradle.ktlint.KtlintExtension import org.jlleitschuh.gradle.ktlint.reporter.ReporterType @@ -38,6 +37,9 @@ buildscript { } } +// See https://stackoverflow.com/questions/25324880/detect-ide-environment-with-gradle +val isRunningFromIde get() = project.properties["android.injected.invoked.from.ide"] == "true" + subprojects { repositories { google() @@ -60,7 +62,10 @@ subprojects { tasks.withType() { kotlinOptions { - kotlinOptions.allWarningsAsErrors = true + // Allow warnings when running from IDE, makes it easier to experiment. + if (!isRunningFromIde) { + allWarningsAsErrors = true + } jvmTarget = "1.8" diff --git a/core-compose/api/core-compose.api b/core-compose/api/core-compose.api index 1456cc39df..3fca494e8e 100644 --- a/core-compose/api/core-compose.api +++ b/core-compose/api/core-compose.api @@ -21,6 +21,11 @@ public final class com/squareup/workflow/ui/compose/ComposeViewFactory : com/squ public fun getType ()Lkotlin/reflect/KClass; } +public final class com/squareup/workflow/ui/compose/ViewEnvironmentsKt { + public static final fun showRendering (Lcom/squareup/workflow/ui/ViewEnvironment;Ljava/lang/Object;Landroidx/ui/core/Modifier;Landroidx/compose/Composer;)V + public static synthetic fun showRendering$default (Lcom/squareup/workflow/ui/ViewEnvironment;Ljava/lang/Object;Landroidx/ui/core/Modifier;Landroidx/compose/Composer;ILjava/lang/Object;)V +} + public final class com/squareup/workflow/ui/core/compose/BuildConfig { public static final field BUILD_TYPE Ljava/lang/String; public static final field DEBUG Z diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactory.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactory.kt index f239261673..a095cbe70f 100644 --- a/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactory.kt +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactory.kt @@ -56,17 +56,30 @@ import kotlin.reflect.KClass * * val viewRegistry = ViewRegistry(FooBinding, …) * ``` + * + * ## Nesting child renderings + * + * Workflows can render other workflows, and renderings from one workflow can contain renderings + * from other workflows. These renderings may all be bound to their own [ViewFactory]s. Regular + * [ViewFactory]s and `LayoutRunner`s use + * [WorkflowViewStub][com.squareup.workflow.ui.WorkflowViewStub] to recursively show nested + * renderings using the [ViewRegistry][com.squareup.workflow.ui.ViewRegistry]. + * + * View factories defined using this function may also show nested renderings. Doing so is as simple + * as calling [ViewEnvironment.showRendering] and passing in the nested rendering. See the kdoc on + * that function for an example. */ inline fun bindCompose( - noinline showRendering: @Composable() (RenderingT, ViewEnvironment) -> Unit -): ViewFactory = ComposeViewFactory(RenderingT::class) { rendering, environment -> - showRendering(rendering, environment) -} + noinline showRendering: @Composable() ( + rendering: RenderingT, + environment: ViewEnvironment + ) -> Unit +): ViewFactory = ComposeViewFactory(RenderingT::class, showRendering) @PublishedApi internal class ComposeViewFactory( override val type: KClass, - private val showRendering: @Composable() (RenderingT, ViewEnvironment) -> Unit + internal val showRendering: @Composable() (RenderingT, ViewEnvironment) -> Unit ) : ViewFactory { override fun buildView( diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/ViewEnvironments.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/ViewEnvironments.kt new file mode 100644 index 0000000000..e7419396a5 --- /dev/null +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/ViewEnvironments.kt @@ -0,0 +1,60 @@ +/* + * Copyright 2020 Square Inc. + * + * 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.squareup.workflow.ui.compose + +import androidx.compose.Composable +import androidx.compose.remember +import androidx.ui.core.Modifier +import com.squareup.workflow.ui.ViewEnvironment +import com.squareup.workflow.ui.ViewRegistry + +/** + * Renders [rendering] into the composition using this [ViewEnvironment]'s + * [ViewRegistry][com.squareup.workflow.ui.ViewRegistry] to generate the view. + * + * This function fulfills a similar role as + * [WorkflowViewStub][com.squareup.workflow.ui.WorkflowViewStub], but is much more convenient to use + * from Composable functions. + * + * ## Example + * + * ``` + * data class FramedRendering( + * val borderColor: Color, + * val child: Any + * ) + * + * val FramedContainerViewFactory = bindCompose { rendering, environment -> + * Surface(border = Border(rendering.borderColor, 8.dp)) { + * environment.showRendering(rendering.child) + * } + * } + * ``` + * + * @param rendering The workflow rendering to display. May be of any type for which a + * [ViewFactory][com.squareup.workflow.ui.ViewFactory] has been registered in this + * environment's [ViewRegistry]. + * @param modifier A [Modifier] that will be applied to composable used to show [rendering]. + * + * @throws IllegalArgumentException if no factory can be found for [rendering]'s type. + */ +@Composable fun ViewEnvironment.showRendering( + rendering: Any, + modifier: Modifier = Modifier +) { + val viewRegistry = remember(this) { this[ViewRegistry] } + viewRegistry.showRendering(rendering, this, modifier) +} diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/ViewFactories.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/ViewFactories.kt new file mode 100644 index 0000000000..9fce25d9c6 --- /dev/null +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/ViewFactories.kt @@ -0,0 +1,85 @@ +/* + * Copyright 2020 Square Inc. + * + * 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.squareup.workflow.ui.compose + +import android.content.Context +import android.view.ViewGroup.LayoutParams.MATCH_PARENT +import android.widget.FrameLayout +import androidx.compose.Composable +import androidx.ui.core.Modifier +import androidx.ui.foundation.Box +import com.squareup.workflow.ui.ViewEnvironment +import com.squareup.workflow.ui.ViewFactory +import com.squareup.workflow.ui.WorkflowViewStub +import com.squareup.workflow.ui.compose.ComposableViewStubWrapper.Update + +/** + * Renders [rendering] into the composition using the `ViewRegistry` from the [ViewEnvironment] to + * determine how to draw it. + * + * To display a nested rendering from a [Composable view binding][bindCompose], use + * [ViewEnvironment.showRendering]. + * + * @see ViewEnvironment.showRendering + * @see com.squareup.workflow.ui.ViewRegistry.showRendering + */ +// Bug: IR compiler pukes on ViewFactory here. +@Composable internal fun ViewFactory.showRendering( + rendering: RenderingT, + viewEnvironment: ViewEnvironment, + modifier: Modifier = Modifier +) { + Box(modifier = modifier) { + // Fast path: If the child binding is also a Composable, we don't need to go through the legacy + // view system and can just invoke the binding's composable function directly. + if (this is ComposeViewFactory) { + showRendering(rendering, viewEnvironment) + } else { + // IntelliJ currently complains very loudly about this function call, but it actually compiles. + // The IDE tooling isn't currently able to recognize that the Compose compiler accepts this code. + ComposableViewStubWrapper(update = Update(rendering, viewEnvironment)) + } + } +} + +/** + * Wraps a [WorkflowViewStub] with an API that is more Compose-friendly. + * + * In particular, Compose will only generate `Emittable`s for views with a single constructor + * that takes a [Context]. + * + * See [this slack message](https://kotlinlang.slack.com/archives/CJLTWPH7S/p1576264533012000?thread_ts=1576262311.008800&cid=CJLTWPH7S). + */ +private class ComposableViewStubWrapper(context: Context) : FrameLayout(context) { + + data class Update( + val rendering: Any, + val viewEnvironment: ViewEnvironment + ) + + private val viewStub = WorkflowViewStub(context) + + init { + addView(viewStub) + } + + // Compose turns this into a parameter when you invoke this class as a Composable. + fun setUpdate(update: Update) { + viewStub.update(update.rendering, update.viewEnvironment) + } + + override fun getLayoutParams(): LayoutParams = LayoutParams(MATCH_PARENT, MATCH_PARENT) +} diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/ViewRegistries.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/ViewRegistries.kt new file mode 100644 index 0000000000..aec3b46843 --- /dev/null +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/ViewRegistries.kt @@ -0,0 +1,42 @@ +/* + * Copyright 2020 Square Inc. + * + * 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.squareup.workflow.ui.compose + +import androidx.compose.Composable +import androidx.compose.remember +import androidx.ui.core.Modifier +import com.squareup.workflow.ui.ViewEnvironment +import com.squareup.workflow.ui.ViewFactory +import com.squareup.workflow.ui.ViewRegistry + +/** + * Renders [rendering] into the composition using this [ViewRegistry] to determine how to draw it. + * + * To display a nested rendering from a [Composable view binding][bindCompose], use + * [ViewEnvironment.showRendering]. + * + * @see ViewEnvironment.showRendering + * @see ViewFactory.showRendering + */ +@Composable internal fun ViewRegistry.showRendering( + rendering: Any, + hints: ViewEnvironment, + modifier: Modifier = Modifier +) { + val renderingType = rendering::class + val viewFactory = remember(renderingType) { getFactoryFor(renderingType) } + viewFactory.showRendering(rendering, hints, modifier) +} diff --git a/samples/nested-renderings/build.gradle.kts b/samples/nested-renderings/build.gradle.kts new file mode 100644 index 0000000000..b381b70707 --- /dev/null +++ b/samples/nested-renderings/build.gradle.kts @@ -0,0 +1,48 @@ +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +/* + * Copyright 2020 Square Inc. + * + * 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. + */ +plugins { + id("com.android.application") + kotlin("android") +} + +apply(from = rootProject.file(".buildscript/android-sample-app.gradle")) +apply(from = rootProject.file(".buildscript/android-ui-tests.gradle")) + +android { + defaultConfig { + applicationId = "com.squareup.sample.nestedrenderings" + } +} + +apply(from = rootProject.file(".buildscript/configure-compose.gradle")) +tasks.withType { + kotlinOptions.apiVersion = "1.3" +} + +dependencies { + implementation(project(":core-compose")) + implementation(Dependencies.AndroidX.appcompat) + implementation(Dependencies.Compose.foundation) + implementation(Dependencies.Compose.layout) + implementation(Dependencies.Compose.material) + implementation(Dependencies.Workflow.UI.coreAndroid) + + androidTestImplementation(Dependencies.Compose.test) + androidTestImplementation(Dependencies.Test.junit) + androidTestImplementation(Dependencies.Test.truth) +} diff --git a/samples/nested-renderings/src/androidTest/java/com/squareup/sample/nestedrenderings/NestedRenderingsTest.kt b/samples/nested-renderings/src/androidTest/java/com/squareup/sample/nestedrenderings/NestedRenderingsTest.kt new file mode 100644 index 0000000000..bb8514c452 --- /dev/null +++ b/samples/nested-renderings/src/androidTest/java/com/squareup/sample/nestedrenderings/NestedRenderingsTest.kt @@ -0,0 +1,58 @@ +/* + * Copyright 2020 Square Inc. + * + * 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.squareup.sample.nestedrenderings + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.ui.test.android.AndroidComposeTestRule +import androidx.ui.test.assertIsDisplayed +import androidx.ui.test.doClick +import androidx.ui.test.findAllByText +import androidx.ui.test.findByText +import com.google.common.truth.Truth.assertThat +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +private const val ADD_BUTTON_TEXT = "Add Child" + +@RunWith(AndroidJUnit4::class) +class NestedRenderingsTest { + + // Launches the activity. + @Rule @JvmField val composeRule = AndroidComposeTestRule() + + @Test fun childrenAreAddedAndRemoved() { + val resetButton = findByText("Reset") + + findByText(ADD_BUTTON_TEXT) + .assertIsDisplayed() + .doClick() + + findAllByText(ADD_BUTTON_TEXT) + .also { addButtons -> + assertThat(addButtons).hasSize(2) + addButtons.forEach { it.doClick() } + } + + findAllByText(ADD_BUTTON_TEXT) + .also { addButtons -> + assertThat(addButtons).hasSize(4) + } + + resetButton.doClick() + assertThat(findAllByText(ADD_BUTTON_TEXT)).hasSize(1) + } +} diff --git a/samples/nested-renderings/src/main/AndroidManifest.xml b/samples/nested-renderings/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..a5385e3d19 --- /dev/null +++ b/samples/nested-renderings/src/main/AndroidManifest.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + diff --git a/samples/nested-renderings/src/main/java/com/squareup/sample/nestedrenderings/LegacyRunner.kt b/samples/nested-renderings/src/main/java/com/squareup/sample/nestedrenderings/LegacyRunner.kt new file mode 100644 index 0000000000..0643d23267 --- /dev/null +++ b/samples/nested-renderings/src/main/java/com/squareup/sample/nestedrenderings/LegacyRunner.kt @@ -0,0 +1,40 @@ +/* + * Copyright 2020 Square Inc. + * + * 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.squareup.sample.nestedrenderings + +import com.squareup.sample.nestedrenderings.RecursiveWorkflow.LegacyRendering +import com.squareup.sample.nestedrenderings.databinding.LegacyViewBinding +import com.squareup.workflow.ui.LayoutRunner +import com.squareup.workflow.ui.LayoutRunner.Companion.bind +import com.squareup.workflow.ui.ViewEnvironment +import com.squareup.workflow.ui.ViewFactory + +/** + * A [LayoutRunner] that renders [LegacyRendering]s using the legacy view framework. + */ +class LegacyRunner(private val binding: LegacyViewBinding) : LayoutRunner { + + override fun showRendering( + rendering: LegacyRendering, + viewEnvironment: ViewEnvironment + ) { + binding.stub.update(rendering.rendering, viewEnvironment) + } + + companion object : ViewFactory by bind( + LegacyViewBinding::inflate, ::LegacyRunner + ) +} diff --git a/samples/nested-renderings/src/main/java/com/squareup/sample/nestedrenderings/NestedRenderingsActivity.kt b/samples/nested-renderings/src/main/java/com/squareup/sample/nestedrenderings/NestedRenderingsActivity.kt new file mode 100644 index 0000000000..f14e994742 --- /dev/null +++ b/samples/nested-renderings/src/main/java/com/squareup/sample/nestedrenderings/NestedRenderingsActivity.kt @@ -0,0 +1,40 @@ +/* + * Copyright 2020 Square Inc. + * + * 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.squareup.sample.nestedrenderings + +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import com.squareup.workflow.diagnostic.SimpleLoggingDiagnosticListener +import com.squareup.workflow.ui.ViewRegistry +import com.squareup.workflow.ui.WorkflowRunner +import com.squareup.workflow.ui.setContentWorkflow + +private val viewRegistry = ViewRegistry( + RecursiveViewFactory, + LegacyRunner +) + +class NestedRenderingsActivity : AppCompatActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentWorkflow(viewRegistry) { + WorkflowRunner.Config( + RecursiveWorkflow, + diagnosticListener = SimpleLoggingDiagnosticListener() + ) + } + } +} diff --git a/samples/nested-renderings/src/main/java/com/squareup/sample/nestedrenderings/RecursiveViewFactory.kt b/samples/nested-renderings/src/main/java/com/squareup/sample/nestedrenderings/RecursiveViewFactory.kt new file mode 100644 index 0000000000..d6c4833a6c --- /dev/null +++ b/samples/nested-renderings/src/main/java/com/squareup/sample/nestedrenderings/RecursiveViewFactory.kt @@ -0,0 +1,120 @@ +/* + * Copyright 2020 Square Inc. + * + * 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:Suppress("RemoveEmptyParenthesesFromAnnotationEntry") + +package com.squareup.sample.nestedrenderings + +import androidx.compose.Composable +import androidx.compose.Providers +import androidx.compose.ambientOf +import androidx.compose.remember +import androidx.ui.core.Alignment.Companion.CenterHorizontally +import androidx.ui.core.Modifier +import androidx.ui.foundation.Text +import androidx.ui.graphics.Color +import androidx.ui.graphics.compositeOver +import androidx.ui.layout.Arrangement.SpaceEvenly +import androidx.ui.layout.Column +import androidx.ui.layout.FlowRow +import androidx.ui.layout.MainAxisAlignment +import androidx.ui.layout.SizeMode.Expand +import androidx.ui.layout.fillMaxSize +import androidx.ui.layout.padding +import androidx.ui.material.Button +import androidx.ui.material.Card +import androidx.ui.res.dimensionResource +import com.squareup.sample.nestedrenderings.RecursiveWorkflow.Rendering +import com.squareup.workflow.ui.ViewEnvironment +import com.squareup.workflow.ui.compose.bindCompose +import com.squareup.workflow.ui.compose.showRendering + +/** + * Ambient of [Color] to use as the background color for a [RecursiveViewFactory]. + */ +private val BackgroundColorAmbient = ambientOf { Color.Green } + +/** + * A `ViewFactory` that renders [RecursiveWorkflow.Rendering]s. + */ +val RecursiveViewFactory = bindCompose { rendering, viewEnvironment -> + // Every child should be drawn with a slightly-darker background color. + val color = BackgroundColorAmbient.current + val childColor = remember(color) { + color.copy(alpha = .9f) + .compositeOver(Color.Black) + } + + Card(color = color) { + Column( + Modifier.padding(dimensionResource(R.dimen.recursive_padding)) + .fillMaxSize(), + horizontalGravity = CenterHorizontally + ) { + Providers(BackgroundColorAmbient provides childColor) { + Children( + rendering.children, viewEnvironment, + // Pass a weight so that the column fills all the space not occupied by the buttons. + modifier = Modifier.weight(1f, fill = true) + ) + } + Buttons( + onAdd = rendering.onAddChildClicked, + onReset = rendering.onResetClicked + ) + } + } +} + +@Composable private fun Children( + children: List, + viewEnvironment: ViewEnvironment, + modifier: Modifier +) { + Column( + modifier = modifier, + verticalArrangement = SpaceEvenly, + horizontalGravity = CenterHorizontally + ) { + children.forEach { childRendering -> + viewEnvironment.showRendering( + childRendering, + // Pass a weight so all children are partitioned evenly within the total column space. + // Without the weight, each child is the full size of the parent. + modifier = Modifier.weight(1f, true) + .padding(dimensionResource(R.dimen.recursive_padding)) + ) + } + } +} + +@Composable private fun Buttons( + onAdd: () -> Unit, + onReset: () -> Unit +) { + // Use a FlowRow so the buttons will wrap when the parent is too narrow. + FlowRow( + mainAxisSize = Expand, + mainAxisAlignment = MainAxisAlignment.SpaceEvenly, + crossAxisSpacing = dimensionResource(R.dimen.recursive_padding) + ) { + Button(onClick = onAdd) { + Text("Add Child") + } + Button(onClick = onReset) { + Text("Reset") + } + } +} diff --git a/samples/nested-renderings/src/main/java/com/squareup/sample/nestedrenderings/RecursiveWorkflow.kt b/samples/nested-renderings/src/main/java/com/squareup/sample/nestedrenderings/RecursiveWorkflow.kt new file mode 100644 index 0000000000..c8780445b2 --- /dev/null +++ b/samples/nested-renderings/src/main/java/com/squareup/sample/nestedrenderings/RecursiveWorkflow.kt @@ -0,0 +1,86 @@ +/* + * Copyright 2020 Square Inc. + * + * 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.squareup.sample.nestedrenderings + +import com.squareup.sample.nestedrenderings.RecursiveWorkflow.LegacyRendering +import com.squareup.sample.nestedrenderings.RecursiveWorkflow.Rendering +import com.squareup.sample.nestedrenderings.RecursiveWorkflow.State +import com.squareup.workflow.RenderContext +import com.squareup.workflow.Snapshot +import com.squareup.workflow.StatefulWorkflow +import com.squareup.workflow.action +import com.squareup.workflow.renderChild + +/** + * A simple workflow that produces [Rendering]s of zero or more children. + * The rendering provides event handlers for adding children and resetting child count to zero. + * + * Every other (odd) rendering in the [Rendering.children] will be wrapped with a [LegacyRendering] + * to force it to go through the legacy view layer. This way this sample both demonstrates pass- + * through Composable renderings as well as adapting in both directions. + */ +object RecursiveWorkflow : StatefulWorkflow() { + + data class State(val children: Int = 0) + + /** + * A rendering from a [RecursiveWorkflow]. + * + * @param children A list of renderings to display as children of this rendering. + * @param onAddChildClicked Adds a child to [children]. + * @param onResetClicked Resets [children] to an empty list. + */ + data class Rendering( + val children: List, + val onAddChildClicked: () -> Unit, + val onResetClicked: () -> Unit + ) + + /** + * Wrapper around a [Rendering] that will be implemented using a legacy view. + */ + data class LegacyRendering(val rendering: Any) + + override fun initialState( + props: Unit, + snapshot: Snapshot? + ): State = State() + + override fun render( + props: Unit, + state: State, + context: RenderContext + ): Rendering { + return Rendering( + children = List(state.children) { i -> + val child = context.renderChild(RecursiveWorkflow, key = i.toString()) + if (i % 2 == 0) child else LegacyRendering(child) + }, + onAddChildClicked = { context.actionSink.send(addChild()) }, + onResetClicked = { context.actionSink.send(reset()) } + ) + } + + override fun snapshotState(state: State): Snapshot = Snapshot.EMPTY + + private fun addChild() = action { + nextState = nextState.copy(children = nextState.children + 1) + } + + private fun reset() = action { + nextState = State() + } +} diff --git a/samples/nested-renderings/src/main/res/layout/legacy_view.xml b/samples/nested-renderings/src/main/res/layout/legacy_view.xml new file mode 100644 index 0000000000..5ce02df3cf --- /dev/null +++ b/samples/nested-renderings/src/main/res/layout/legacy_view.xml @@ -0,0 +1,36 @@ + + + + + + + + diff --git a/samples/nested-renderings/src/main/res/values/dimens.xml b/samples/nested-renderings/src/main/res/values/dimens.xml new file mode 100644 index 0000000000..a5357738c4 --- /dev/null +++ b/samples/nested-renderings/src/main/res/values/dimens.xml @@ -0,0 +1,18 @@ + + + 16dp + diff --git a/samples/nested-renderings/src/main/res/values/strings.xml b/samples/nested-renderings/src/main/res/values/strings.xml new file mode 100644 index 0000000000..2fe25f7e14 --- /dev/null +++ b/samples/nested-renderings/src/main/res/values/strings.xml @@ -0,0 +1,18 @@ + + + Nested Renderings + diff --git a/samples/nested-renderings/src/main/res/values/styles.xml b/samples/nested-renderings/src/main/res/values/styles.xml new file mode 100644 index 0000000000..1c37bd6d5b --- /dev/null +++ b/samples/nested-renderings/src/main/res/values/styles.xml @@ -0,0 +1,23 @@ + + + + + + + diff --git a/settings.gradle.kts b/settings.gradle.kts index 1b542181a0..8d99743b2a 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -18,5 +18,6 @@ rootProject.name = "workflow-compose" include( ":core-compose", ":samples:hello-compose-binding", - ":samples:hello-compose-rendering" + ":samples:hello-compose-rendering", + ":samples:nested-renderings" ) From 0fed244d9c9a3aaffbf2158bb5b546532e8a476a Mon Sep 17 00:00:00 2001 From: Zach Klippenstein Date: Thu, 16 Jan 2020 11:04:09 -0800 Subject: [PATCH 09/67] Make nested bindCompose factories child compositions of outer bindCompose factories. Also introduces a `ComposeViewFactoryRoot` `ViewEnvironment` key to allow "initializing" ambients and other compose context for all nested composables. Fixes #9. --- buildSrc/src/main/java/Dependencies.kt | 1 + core-compose/api/core-compose.api | 21 +++ core-compose/build.gradle.kts | 7 +- .../src/androidTest/AndroidManifest.xml | 25 ++++ .../ui/compose/ComposeViewFactoryRootTest.kt | 96 +++++++++++++ .../workflow/ui/compose/ComposeSupport.kt | 133 ++++++++++++++++++ .../workflow/ui/compose/ComposeViewFactory.kt | 51 ++++++- .../ui/compose/ComposeViewFactoryRoot.kt | 63 +++++++++ .../ui/compose/CompositionContinuation.kt | 58 ++++++++ .../ui/compose/SafeComposeViewFactoryRoot.kt | 41 ++++++ .../workflow/ui/compose/ViewFactories.kt | 7 +- .../hellocomposebinding/HelloBinding.kt | 4 +- .../HelloBindingActivity.kt | 8 +- .../NestedRenderingsActivity.kt | 10 +- .../nestedrenderings/RecursiveViewFactory.kt | 2 +- 15 files changed, 514 insertions(+), 13 deletions(-) create mode 100644 core-compose/src/androidTest/AndroidManifest.xml create mode 100644 core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/ComposeViewFactoryRootTest.kt create mode 100644 core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeSupport.kt create mode 100644 core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactoryRoot.kt create mode 100644 core-compose/src/main/java/com/squareup/workflow/ui/compose/CompositionContinuation.kt create mode 100644 core-compose/src/main/java/com/squareup/workflow/ui/compose/SafeComposeViewFactoryRoot.kt diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt index b2226f9600..579f9d4c71 100644 --- a/buildSrc/src/main/java/Dependencies.kt +++ b/buildSrc/src/main/java/Dependencies.kt @@ -69,6 +69,7 @@ object Dependencies { } const val junit = "junit:junit:4.13" + const val kotlin = "org.jetbrains.kotlin:kotlin-test-junit:${Versions.kotlin}" const val truth = "com.google.truth:truth:1.0.1" } diff --git a/core-compose/api/core-compose.api b/core-compose/api/core-compose.api index 3fca494e8e..579885d896 100644 --- a/core-compose/api/core-compose.api +++ b/core-compose/api/core-compose.api @@ -15,12 +15,33 @@ public abstract class com/squareup/workflow/compose/ComposeWorkflow : com/square public abstract fun render (Ljava/lang/Object;Lcom/squareup/workflow/Sink;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/compose/Composer;)V } +public final class com/squareup/workflow/ui/compose/ComposeSupportKt { + public static final fun ()V +} + public final class com/squareup/workflow/ui/compose/ComposeViewFactory : com/squareup/workflow/ui/ViewFactory { public fun (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function3;)V public fun buildView (Ljava/lang/Object;Lcom/squareup/workflow/ui/ViewEnvironment;Landroid/content/Context;Landroid/view/ViewGroup;)Landroid/view/View; public fun getType ()Lkotlin/reflect/KClass; } +public abstract interface class com/squareup/workflow/ui/compose/ComposeViewFactoryRoot { + public static final field Companion Lcom/squareup/workflow/ui/compose/ComposeViewFactoryRoot$Companion; + public static fun ()V + public abstract fun wrap (Lkotlin/jvm/functions/Function1;Landroidx/compose/Composer;)V +} + +public final class com/squareup/workflow/ui/compose/ComposeViewFactoryRoot$Companion : com/squareup/workflow/ui/ViewEnvironmentKey { + public static final fun ()V + public fun getDefault ()Lcom/squareup/workflow/ui/compose/ComposeViewFactoryRoot; + public synthetic fun getDefault ()Ljava/lang/Object; +} + +public final class com/squareup/workflow/ui/compose/ComposeViewFactoryRootKt { + public static final fun ComposeViewFactoryRoot (Lkotlin/jvm/functions/Function2;)Lcom/squareup/workflow/ui/compose/ComposeViewFactoryRoot; + public static final fun withComposeViewFactoryRoot (Lcom/squareup/workflow/ui/ViewEnvironment;Lkotlin/jvm/functions/Function2;)Lcom/squareup/workflow/ui/ViewEnvironment; +} + public final class com/squareup/workflow/ui/compose/ViewEnvironmentsKt { public static final fun showRendering (Lcom/squareup/workflow/ui/ViewEnvironment;Ljava/lang/Object;Landroidx/ui/core/Modifier;Landroidx/compose/Composer;)V public static synthetic fun showRendering$default (Lcom/squareup/workflow/ui/ViewEnvironment;Ljava/lang/Object;Landroidx/ui/core/Modifier;Landroidx/compose/Composer;ILjava/lang/Object;)V diff --git a/core-compose/build.gradle.kts b/core-compose/build.gradle.kts index 230318f1c3..784f8c5323 100644 --- a/core-compose/build.gradle.kts +++ b/core-compose/build.gradle.kts @@ -27,6 +27,7 @@ java { apply(from = rootProject.file(".buildscript/configure-maven-publish.gradle")) apply(from = rootProject.file(".buildscript/configure-android-defaults.gradle")) +apply(from = rootProject.file(".buildscript/android-ui-tests.gradle")) apply(from = rootProject.file(".buildscript/configure-compose.gradle")) tasks.withType { @@ -42,6 +43,8 @@ dependencies { implementation(Dependencies.Compose.tooling) implementation(Dependencies.Workflow.runtime) - testImplementation(Dependencies.Test.junit) - testImplementation(Dependencies.Test.truth) + androidTestImplementation(Dependencies.Compose.test) + androidTestImplementation(Dependencies.Test.junit) + androidTestImplementation(Dependencies.Test.kotlin) + androidTestImplementation(Dependencies.Test.truth) } diff --git a/core-compose/src/androidTest/AndroidManifest.xml b/core-compose/src/androidTest/AndroidManifest.xml new file mode 100644 index 0000000000..9ed9131013 --- /dev/null +++ b/core-compose/src/androidTest/AndroidManifest.xml @@ -0,0 +1,25 @@ + + + + + + + + diff --git a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/ComposeViewFactoryRootTest.kt b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/ComposeViewFactoryRootTest.kt new file mode 100644 index 0000000000..be85cbcede --- /dev/null +++ b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/ComposeViewFactoryRootTest.kt @@ -0,0 +1,96 @@ +/* + * Copyright 2020 Square Inc. + * + * 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.squareup.workflow.ui.compose + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.ui.foundation.Text +import androidx.ui.layout.Column +import androidx.ui.semantics.Semantics +import androidx.ui.test.assertIsDisplayed +import androidx.ui.test.createComposeRule +import androidx.ui.test.findByText +import com.google.common.truth.Truth.assertThat +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import kotlin.test.assertFailsWith + +@RunWith(AndroidJUnit4::class) +class ComposeViewFactoryRootTest { + + @Rule @JvmField val composeTestRule = createComposeRule() + + @Test fun safeComposeViewFactoryRoot_wraps_content() { + val wrapped = ComposeViewFactoryRoot { content -> + Column { + Text("Parent") + content() + } + } + val safeRoot = SafeComposeViewFactoryRoot(wrapped) + + composeTestRule.setContent { + safeRoot.wrap { + // Need an explicit semantics container, otherwise both Texts will be merged into a single + // Semantics object with the text "Parent\nChild". + Semantics(container = true) { + Text("Child") + } + } + } + + findByText("Parent") + .assertIsDisplayed() + findByText("Child").assertIsDisplayed() + } + + @Test fun safeComposeViewFactoryRoot_throws_whenChildrenNotInvoked() { + val wrapped = ComposeViewFactoryRoot { } + val safeRoot = SafeComposeViewFactoryRoot(wrapped) + + val error = assertFailsWith { + composeTestRule.setContent { + safeRoot.wrap {} + } + } + + assertThat(error).hasMessageThat() + .isEqualTo( + "Expected ComposableDecorator to invoke children exactly once, but was invoked 0 times." + ) + } + + @Test fun safeComposeViewFactoryRoot_throws_whenChildrenInvokedMultipleTimes() { + val wrapped = ComposeViewFactoryRoot { children -> + children() + children() + } + val safeRoot = SafeComposeViewFactoryRoot(wrapped) + + val error = assertFailsWith { + composeTestRule.setContent { + safeRoot.wrap { + Text("Hello") + } + } + } + + assertThat(error).hasMessageThat() + .isEqualTo( + "Expected ComposableDecorator to invoke children exactly once, but was invoked 2 times." + ) + } +} diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeSupport.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeSupport.kt new file mode 100644 index 0000000000..d195a269f3 --- /dev/null +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeSupport.kt @@ -0,0 +1,133 @@ +/* + * Copyright 2020 Square Inc. + * + * 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:Suppress("RemoveEmptyParenthesesFromAnnotationEntry") + +package com.squareup.workflow.ui.compose + +import android.content.Context +import android.view.View +import android.view.ViewGroup +import androidx.compose.Composable +import androidx.compose.Composition +import androidx.compose.CompositionReference +import androidx.compose.Recomposer +import androidx.compose.compositionFor +import androidx.lifecycle.LifecycleOwner +import androidx.ui.node.UiComposer +import com.squareup.workflow.ui.compose.ReflectionSupport.ANDROID_OWNER_CLASS +import com.squareup.workflow.ui.compose.ReflectionSupport.androidOwnerView +import com.squareup.workflow.ui.compose.ReflectionSupport.createOwner +import com.squareup.workflow.ui.compose.ReflectionSupport.createWrappedContent +import com.squareup.workflow.ui.compose.ReflectionSupport.ownerRoot +import com.squareup.workflow.ui.core.compose.R + +private typealias AndroidOwner = Any +private typealias WrappedComposition = Composition +private typealias LayoutNode = Any + +private val DefaultLayoutParams = ViewGroup.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT +) + +/** + * Copy of the built-in [setContent] function that takes an additional parent + * [CompositionReference]. This will eventually be built-in to Compose, but until then this function + * uses a bunch of reflection to access private Compose APIs. + * + * Once this ships in Compose, this whole file should be deleted. + * + * Tracked with Google [here](https://issuetracker.google.com/issues/156527485). + * Note that ambient _changes_ also don't seem to get propagated currently, that bug is tracked + * [here](https://issuetracker.google.com/issues/156527486). + */ +internal fun ViewGroup.setContent( + recomposer: Recomposer, + parent: CompositionReference, + content: @Composable() () -> Unit +): Composition { + val composeView: AndroidOwner = + if (childCount > 0) { + getChildAt(0).takeIf(ANDROID_OWNER_CLASS::isInstance) + } else { + removeAllViews(); null + } + ?: createOwner(context).also { addView(androidOwnerView(it), DefaultLayoutParams) } + return doSetContent(context, composeView, recomposer, parent, content) +} + +/** + * This is almost an exact copy of the private `doSetContent` function in Compose, but + * it also accepts a parent [CompositionReference]. + */ +private fun doSetContent( + context: Context, + owner: AndroidOwner, + recomposer: Recomposer, + parent: CompositionReference, + content: @Composable() () -> Unit +): Composition { + // val original = compositionFor(context, owner.root, recomposer) + val container = ownerRoot(owner) + val original = compositionFor( + container = container, + recomposer = recomposer, + parent = parent, + composerFactory = { slotTable, factoryRecomposer -> + UiComposer(context, container, slotTable, factoryRecomposer) + } + ) + + val wrapped = androidOwnerView(owner).getTag(R.id.wrapped_composition_tag) + as? WrappedComposition + // ?: WrappedComposition(owner, original).also { + ?: createWrappedContent(owner, original).also { + androidOwnerView(owner).setTag(R.id.wrapped_composition_tag, it) + } + wrapped.setContent(content) + return wrapped +} + +private object ReflectionSupport { + + val ANDROID_OWNER_CLASS = Class.forName("androidx.ui.core.AndroidOwner") + private val WRAPPED_COMPOSITION_CLASS = Class.forName("androidx.ui.core.WrappedComposition") + private val ANDROID_OWNER_KT_CLASS = Class.forName("androidx.ui.core.AndroidOwnerKt") + + private val WRAPPED_COMPOSITION_CTOR = + WRAPPED_COMPOSITION_CLASS.getConstructor(ANDROID_OWNER_CLASS, Composition::class.java) + + private val CREATE_OWNER_FUN = + ANDROID_OWNER_KT_CLASS.getMethod("createOwner", Context::class.java, LifecycleOwner::class.java) + private val ANDROID_OWNER_ROOT_GETTER = ANDROID_OWNER_CLASS.getMethod("getRoot") + + init { + WRAPPED_COMPOSITION_CTOR.isAccessible = true + } + + fun createOwner(context: Context): AndroidOwner = + CREATE_OWNER_FUN.invoke(null, context, null) as AndroidOwner + + fun ownerRoot(owner: AndroidOwner): LayoutNode = + ANDROID_OWNER_ROOT_GETTER.invoke(owner) as LayoutNode + + fun createWrappedContent( + owner: AndroidOwner, + original: Composition + ): WrappedComposition = WRAPPED_COMPOSITION_CTOR.newInstance(owner, original) as Composition + + fun androidOwnerView(owner: AndroidOwner): View = owner as View +} diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactory.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactory.kt index a095cbe70f..aa021169da 100644 --- a/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactory.kt +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactory.kt @@ -47,9 +47,7 @@ import kotlin.reflect.KClass * * @Composable * private fun showFoo(foo: FooRendering) { - * MaterialTheme { - * Text(foo.message) - * } + * Text(foo.message) * } * * … @@ -68,6 +66,19 @@ import kotlin.reflect.KClass * View factories defined using this function may also show nested renderings. Doing so is as simple * as calling [ViewEnvironment.showRendering] and passing in the nested rendering. See the kdoc on * that function for an example. + * + * Nested renderings will have access to any ambients defined in outer composable, even if there are + * legacy views in between them, as long as the [ViewEnvironment] is propagated continuously between + * the two factories. + * + * ## Initializing Compose context + * + * Often all the [bindCompose] factories in an app need to share some context – for example, certain + * ambients need to be provided, such as `MaterialTheme`. To configure this shared context, include + * a [ComposeViewFactoryRoot] in your top-level [ViewEnvironment] (e.g. by using + * [withComposeViewFactoryRoot]). The first time a [bindCompose] is used to show a rendering, its + * [showRendering] function will be wrapped with the [ComposeViewFactoryRoot]. See the documentation + * on [ComposeViewFactoryRoot] for more information. */ inline fun bindCompose( noinline showRendering: @Composable() ( @@ -117,11 +128,43 @@ internal class ComposeViewFactory( } // Entry point to the world of Compose. - composeContainer.setContent(Recomposer.current()) { + composeContainer.setOrContinueContent(initialViewEnvironment) { val (rendering, environment) = renderState.value!! showRendering(rendering, environment) } return composeContainer } + + /** + * Starts composing [content] into this [ViewGroup]. + * + * It will either propagate the composition context from any outer [ComposeViewFactory]s, or if + * this is the first [ComposeViewFactory] in the tree, it will initialize it using the + * [ComposeViewFactoryRoot] if present. + * + * This function relies on [ViewFactory.showRendering] adding the [CompositionContinuation] to the + * [ViewEnvironment]. + */ + private fun ViewGroup.setOrContinueContent( + initialViewEnvironment: ViewEnvironment, + content: @Composable() () -> Unit + ) { + val (compositionReference, recomposer) = initialViewEnvironment[CompositionContinuation] + if (compositionReference != null && recomposer != null) { + // Somewhere above us in the workflow rendering tree, there's another bindCompose factory. + // We need to link to its composition reference so we inherit its ambients. + setContent(recomposer, compositionReference, content) + } else { + // This is the first bindCompose factory in the rendering tree, so we need to initialize it + // with the ComposableDecorator if present. + val decorator = initialViewEnvironment[ComposeViewFactoryRoot] + val safeDecorator = SafeComposeViewFactoryRoot(decorator) + setContent(Recomposer.current()) { + safeDecorator.wrap { + content() + } + } + } + } } diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactoryRoot.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactoryRoot.kt new file mode 100644 index 0000000000..25ed41e1c4 --- /dev/null +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactoryRoot.kt @@ -0,0 +1,63 @@ +/* + * Copyright 2020 Square Inc. + * + * 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:Suppress("RemoveEmptyParenthesesFromAnnotationEntry") + +package com.squareup.workflow.ui.compose + +import androidx.compose.Composable +import androidx.compose.Direct +import com.squareup.workflow.ui.ViewEnvironment +import com.squareup.workflow.ui.ViewEnvironmentKey + +/** + * A `@Composable` function that is stored in a [ViewEnvironment] and will be used to wrap the first + * [bindCompose] composition. This can be used to setup any ambients that all [bindCompose] + * factories need access to, such as ambients that specify the UI theme. + * + * This function will called once, to wrap the _highest-level_ [bindCompose] in the tree. However, + * ambients are propagated down to child [bindCompose] compositions, so any ambients provided here + * will be available in _all_ [bindCompose] compositions. + */ +interface ComposeViewFactoryRoot { + + @Composable fun wrap(content: @Composable() () -> Unit) + + companion object : ViewEnvironmentKey(ComposeViewFactoryRoot::class) { + override val default: ComposeViewFactoryRoot get() = NoopComposeViewFactoryRoot + } +} + +/** + * Adds a [ComposeViewFactoryRoot] to this [ViewEnvironment] that uses [wrapper] to wrap the first + * [bindCompose] composition. See [ComposeViewFactoryRoot] for more information. + */ +fun ViewEnvironment.withComposeViewFactoryRoot( + wrapper: @Composable() (content: @Composable() () -> Unit) -> Unit +): ViewEnvironment = this + (ComposeViewFactoryRoot to ComposeViewFactoryRoot(wrapper)) + +// This could be inline, but that makes the Compose compiler puke. +@Suppress("FunctionName") +fun ComposeViewFactoryRoot( + wrapper: @Composable() (content: @Composable() () -> Unit) -> Unit +): ComposeViewFactoryRoot = object : ComposeViewFactoryRoot { + @Composable override fun wrap(content: @Composable() () -> Unit) = wrapper(content) +} + +private object NoopComposeViewFactoryRoot : ComposeViewFactoryRoot { + @Direct @Composable override fun wrap(content: @Composable() () -> Unit) { + content() + } +} diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/CompositionContinuation.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/CompositionContinuation.kt new file mode 100644 index 0000000000..4f7980e3b7 --- /dev/null +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/CompositionContinuation.kt @@ -0,0 +1,58 @@ +/* + * Copyright 2020 Square Inc. + * + * 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.squareup.workflow.ui.compose + +import androidx.compose.Composable +import androidx.compose.CompositionReference +import androidx.compose.Recomposer +import androidx.compose.compositionReference +import androidx.compose.currentComposer +import com.squareup.workflow.ui.ViewEnvironment +import com.squareup.workflow.ui.ViewEnvironmentKey + +/** + * Holds a [CompositionReference] and a [Recomposer] that can be used to [setContent] to create a + * composition that is a child of another composition. Child compositions get ambients and other + * compose context from their parent, which allows ambients provided around a [showRendering] call + * to be read by nested [bindCompose] factories. + * + * When [showRendering] is called, it will store an instance of this class in the [ViewEnvironment]. + * [ComposeViewFactory] will then pull the continuation out of the environment and use it to link + * its composition to the outer one. + */ +internal data class CompositionContinuation( + val reference: CompositionReference? = null, + val recomposer: Recomposer? = null +) { + companion object : ViewEnvironmentKey( + CompositionContinuation::class + ) { + override val default: CompositionContinuation + get() = CompositionContinuation() + } +} + +/** + * Creates a [CompositionContinuation] from the current point in the composition and adds it to this + * [ViewEnvironment]. + */ +@Composable internal fun ViewEnvironment.withCompositionContinuation(): ViewEnvironment { + val compositionReference = CompositionContinuation( + reference = compositionReference(), + recomposer = currentComposer.recomposer + ) + return this + (CompositionContinuation to compositionReference) +} diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/SafeComposeViewFactoryRoot.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/SafeComposeViewFactoryRoot.kt new file mode 100644 index 0000000000..804b71550e --- /dev/null +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/SafeComposeViewFactoryRoot.kt @@ -0,0 +1,41 @@ +/* + * Copyright 2020 Square Inc. + * + * 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:Suppress("RemoveEmptyParenthesesFromAnnotationEntry") + +package com.squareup.workflow.ui.compose + +import androidx.compose.Composable + +/** + * [ComposeViewFactoryRoot] that asserts that the [wrap] method invokes its children parameter + * exactly once, and throws an [IllegalStateException] if not. + */ +internal class SafeComposeViewFactoryRoot( + private val delegate: ComposeViewFactoryRoot +) : ComposeViewFactoryRoot { + + @Composable override fun wrap(content: @Composable() () -> Unit) { + var childrenCalledCount = 0 + delegate.wrap { + childrenCalledCount++ + content() + } + check(childrenCalledCount == 1) { + "Expected ComposableDecorator to invoke children exactly once, " + + "but was invoked $childrenCalledCount times." + } + } +} diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/ViewFactories.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/ViewFactories.kt index 9fce25d9c6..5b052a4c61 100644 --- a/core-compose/src/main/java/com/squareup/workflow/ui/compose/ViewFactories.kt +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/ViewFactories.kt @@ -48,9 +48,14 @@ import com.squareup.workflow.ui.compose.ComposableViewStubWrapper.Update if (this is ComposeViewFactory) { showRendering(rendering, viewEnvironment) } else { + // Plumb the current composition "context" through the ViewEnvironment so any nested composable + // factories get access to any ambients currently in effect. + // See setOrContinueContent(). + val newEnvironment = viewEnvironment.withCompositionContinuation() + // IntelliJ currently complains very loudly about this function call, but it actually compiles. // The IDE tooling isn't currently able to recognize that the Compose compiler accepts this code. - ComposableViewStubWrapper(update = Update(rendering, viewEnvironment)) + ComposableViewStubWrapper(update = Update(rendering, newEnvironment)) } } } diff --git a/samples/hello-compose-binding/src/main/java/com/squareup/sample/hellocomposebinding/HelloBinding.kt b/samples/hello-compose-binding/src/main/java/com/squareup/sample/hellocomposebinding/HelloBinding.kt index 1ab0b009c8..c208ac000b 100644 --- a/samples/hello-compose-binding/src/main/java/com/squareup/sample/hellocomposebinding/HelloBinding.kt +++ b/samples/hello-compose-binding/src/main/java/com/squareup/sample/hellocomposebinding/HelloBinding.kt @@ -30,9 +30,7 @@ import com.squareup.sample.hellocomposebinding.HelloWorkflow.Rendering import com.squareup.workflow.ui.compose.bindCompose val HelloBinding = bindCompose { rendering, _ -> - MaterialTheme { - DrawHelloRendering(rendering) - } + DrawHelloRendering(rendering) } @Composable diff --git a/samples/hello-compose-binding/src/main/java/com/squareup/sample/hellocomposebinding/HelloBindingActivity.kt b/samples/hello-compose-binding/src/main/java/com/squareup/sample/hellocomposebinding/HelloBindingActivity.kt index 4f6302e61a..47971e359c 100644 --- a/samples/hello-compose-binding/src/main/java/com/squareup/sample/hellocomposebinding/HelloBindingActivity.kt +++ b/samples/hello-compose-binding/src/main/java/com/squareup/sample/hellocomposebinding/HelloBindingActivity.kt @@ -17,17 +17,23 @@ package com.squareup.sample.hellocomposebinding import android.os.Bundle import androidx.appcompat.app.AppCompatActivity +import androidx.ui.material.MaterialTheme import com.squareup.workflow.diagnostic.SimpleLoggingDiagnosticListener +import com.squareup.workflow.ui.ViewEnvironment import com.squareup.workflow.ui.ViewRegistry import com.squareup.workflow.ui.WorkflowRunner +import com.squareup.workflow.ui.compose.withComposeViewFactoryRoot import com.squareup.workflow.ui.setContentWorkflow private val viewRegistry = ViewRegistry(HelloBinding) +private val containerHints = ViewEnvironment(viewRegistry).withComposeViewFactoryRoot { content -> + MaterialTheme(content = content) +} class HelloBindingActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContentWorkflow(viewRegistry) { + setContentWorkflow(containerHints) { WorkflowRunner.Config( HelloWorkflow, diagnosticListener = SimpleLoggingDiagnosticListener() diff --git a/samples/nested-renderings/src/main/java/com/squareup/sample/nestedrenderings/NestedRenderingsActivity.kt b/samples/nested-renderings/src/main/java/com/squareup/sample/nestedrenderings/NestedRenderingsActivity.kt index f14e994742..bd4d021dd0 100644 --- a/samples/nested-renderings/src/main/java/com/squareup/sample/nestedrenderings/NestedRenderingsActivity.kt +++ b/samples/nested-renderings/src/main/java/com/squareup/sample/nestedrenderings/NestedRenderingsActivity.kt @@ -17,9 +17,13 @@ package com.squareup.sample.nestedrenderings import android.os.Bundle import androidx.appcompat.app.AppCompatActivity +import androidx.compose.Providers +import androidx.ui.graphics.Color import com.squareup.workflow.diagnostic.SimpleLoggingDiagnosticListener +import com.squareup.workflow.ui.ViewEnvironment import com.squareup.workflow.ui.ViewRegistry import com.squareup.workflow.ui.WorkflowRunner +import com.squareup.workflow.ui.compose.withComposeViewFactoryRoot import com.squareup.workflow.ui.setContentWorkflow private val viewRegistry = ViewRegistry( @@ -27,10 +31,14 @@ private val viewRegistry = ViewRegistry( LegacyRunner ) +private val viewEnvironment = ViewEnvironment(viewRegistry).withComposeViewFactoryRoot { content -> + Providers(BackgroundColorAmbient provides Color.Green, children = content) +} + class NestedRenderingsActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContentWorkflow(viewRegistry) { + setContentWorkflow(viewEnvironment) { WorkflowRunner.Config( RecursiveWorkflow, diagnosticListener = SimpleLoggingDiagnosticListener() diff --git a/samples/nested-renderings/src/main/java/com/squareup/sample/nestedrenderings/RecursiveViewFactory.kt b/samples/nested-renderings/src/main/java/com/squareup/sample/nestedrenderings/RecursiveViewFactory.kt index d6c4833a6c..1fb087cba6 100644 --- a/samples/nested-renderings/src/main/java/com/squareup/sample/nestedrenderings/RecursiveViewFactory.kt +++ b/samples/nested-renderings/src/main/java/com/squareup/sample/nestedrenderings/RecursiveViewFactory.kt @@ -44,7 +44,7 @@ import com.squareup.workflow.ui.compose.showRendering /** * Ambient of [Color] to use as the background color for a [RecursiveViewFactory]. */ -private val BackgroundColorAmbient = ambientOf { Color.Green } +val BackgroundColorAmbient = ambientOf { error("No background color specified") } /** * A `ViewFactory` that renders [RecursiveWorkflow.Rendering]s. From 15b06ca7163f9b12e4f6f4c5a312012d32be89aa Mon Sep 17 00:00:00 2001 From: Zach Klippenstein Date: Wed, 13 May 2020 19:58:04 -0700 Subject: [PATCH 10/67] Move all common UI test dependencies into shared setup file. --- .buildscript/android-ui-tests.gradle | 3 +++ core-compose/build.gradle.kts | 5 ----- samples/hello-compose-binding/build.gradle.kts | 3 --- samples/hello-compose-rendering/build.gradle.kts | 7 +------ samples/nested-renderings/build.gradle.kts | 4 ---- 5 files changed, 4 insertions(+), 18 deletions(-) diff --git a/.buildscript/android-ui-tests.gradle b/.buildscript/android-ui-tests.gradle index 4c86c81920..120e3c2e0f 100644 --- a/.buildscript/android-ui-tests.gradle +++ b/.buildscript/android-ui-tests.gradle @@ -5,6 +5,9 @@ android { } dependencies { + androidTestImplementation Deps.get("compose.test") androidTestImplementation Deps.get("test.androidx.espresso.core") androidTestImplementation Deps.get("test.androidx.junitExt") + androidTestImplementation Deps.get("test.kotlin") + androidTestImplementation Deps.get("test.truth") } diff --git a/core-compose/build.gradle.kts b/core-compose/build.gradle.kts index 784f8c5323..93b7b2a25c 100644 --- a/core-compose/build.gradle.kts +++ b/core-compose/build.gradle.kts @@ -42,9 +42,4 @@ dependencies { implementation(Dependencies.Compose.layout) implementation(Dependencies.Compose.tooling) implementation(Dependencies.Workflow.runtime) - - androidTestImplementation(Dependencies.Compose.test) - androidTestImplementation(Dependencies.Test.junit) - androidTestImplementation(Dependencies.Test.kotlin) - androidTestImplementation(Dependencies.Test.truth) } diff --git a/samples/hello-compose-binding/build.gradle.kts b/samples/hello-compose-binding/build.gradle.kts index 4484111d16..98259b471c 100644 --- a/samples/hello-compose-binding/build.gradle.kts +++ b/samples/hello-compose-binding/build.gradle.kts @@ -41,7 +41,4 @@ dependencies { implementation(Dependencies.Compose.material) implementation(Dependencies.Compose.tooling) implementation(Dependencies.Compose.foundation) - - androidTestImplementation(Dependencies.Compose.test) - androidTestImplementation(Dependencies.Test.junit) } diff --git a/samples/hello-compose-rendering/build.gradle.kts b/samples/hello-compose-rendering/build.gradle.kts index b5193c6f23..88103a2c4b 100644 --- a/samples/hello-compose-rendering/build.gradle.kts +++ b/samples/hello-compose-rendering/build.gradle.kts @@ -22,12 +22,11 @@ plugins { apply(from = rootProject.file(".buildscript/configure-android-defaults.gradle")) apply(from = rootProject.file(".buildscript/android-sample-app.gradle")) +apply(from = rootProject.file(".buildscript/android-ui-tests.gradle")) android { defaultConfig { applicationId = "com.squareup.sample.hellocomposerendering" - - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } } @@ -44,8 +43,4 @@ dependencies { implementation(Dependencies.Compose.material) implementation(Dependencies.Workflow.core) implementation(Dependencies.Workflow.runtime) - - androidTestImplementation(Dependencies.Compose.test) - androidTestImplementation(Dependencies.Test.AndroidX.junitExt) - androidTestImplementation(Dependencies.Test.junit) } diff --git a/samples/nested-renderings/build.gradle.kts b/samples/nested-renderings/build.gradle.kts index b381b70707..bcbc695a26 100644 --- a/samples/nested-renderings/build.gradle.kts +++ b/samples/nested-renderings/build.gradle.kts @@ -41,8 +41,4 @@ dependencies { implementation(Dependencies.Compose.layout) implementation(Dependencies.Compose.material) implementation(Dependencies.Workflow.UI.coreAndroid) - - androidTestImplementation(Dependencies.Compose.test) - androidTestImplementation(Dependencies.Test.junit) - androidTestImplementation(Dependencies.Test.truth) } From bee8f42937e30421db310f0e4b7cab7bf7b3cb3d Mon Sep 17 00:00:00 2001 From: Zach Klippenstein Date: Thu, 14 May 2020 09:26:40 -0700 Subject: [PATCH 11/67] Add previews to composables in samples. Requires forcing the version of the coroutine libraries that the Workflow core artifacts transitively bring in down to 1.3.0, due to a bug in version dev10 of Compose (see #13, and [this slack message](https://kotlinlang.slack.com/archives/CJLTWPH7S/p1589450805381700?thread_ts=1588232959.054500&cid=CJLTWPH7S)). --- .buildscript/android-sample-app.gradle | 1 + build.gradle.kts | 13 +++++++++ .../hello-compose-binding/build.gradle.kts | 1 - .../hellocomposebinding/HelloBinding.kt | 13 ++------- .../HelloRenderingWorkflow.kt | 29 ++++++++++++++----- 5 files changed, 38 insertions(+), 19 deletions(-) diff --git a/.buildscript/android-sample-app.gradle b/.buildscript/android-sample-app.gradle index 4de6a954a8..6b99ace96f 100644 --- a/.buildscript/android-sample-app.gradle +++ b/.buildscript/android-sample-app.gradle @@ -2,6 +2,7 @@ apply from: rootProject.file('.buildscript/configure-android-defaults.gradle') dependencies { implementation(Deps.get("androidx.appcompat")) + implementation(Deps.get("compose.tooling")) implementation(Deps.get("timber")) implementation(Deps.get("workflow.core")) implementation(Deps.get("workflow.runtime")) diff --git a/build.gradle.kts b/build.gradle.kts index fe33f0a496..0275a16906 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -47,6 +47,19 @@ subprojects { jcenter() } + configurations.all { + resolutionStrategy { + // There's an issue with running Compose @Previews in Android Studio with newer versions of + // the coroutines library (exception about an unresolved symbol). Until that's fixed, we have + // to force the coroutines version brought in by Workflows to 1.3.0. + // See https://github.com/square/workflow-kotlin-compose/issues/13. + force( + "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.0", + "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.0" + ) + } + } + apply(plugin = "org.jlleitschuh.gradle.ktlint") apply(plugin = "io.gitlab.arturbosch.detekt") afterEvaluate { diff --git a/samples/hello-compose-binding/build.gradle.kts b/samples/hello-compose-binding/build.gradle.kts index 4484111d16..0cafed814e 100644 --- a/samples/hello-compose-binding/build.gradle.kts +++ b/samples/hello-compose-binding/build.gradle.kts @@ -39,7 +39,6 @@ dependencies { implementation(Dependencies.Compose.layout) implementation(Dependencies.Compose.material) - implementation(Dependencies.Compose.tooling) implementation(Dependencies.Compose.foundation) androidTestImplementation(Dependencies.Compose.test) diff --git a/samples/hello-compose-binding/src/main/java/com/squareup/sample/hellocomposebinding/HelloBinding.kt b/samples/hello-compose-binding/src/main/java/com/squareup/sample/hellocomposebinding/HelloBinding.kt index c208ac000b..d49017d004 100644 --- a/samples/hello-compose-binding/src/main/java/com/squareup/sample/hellocomposebinding/HelloBinding.kt +++ b/samples/hello-compose-binding/src/main/java/com/squareup/sample/hellocomposebinding/HelloBinding.kt @@ -22,8 +22,6 @@ import androidx.ui.foundation.Clickable import androidx.ui.foundation.Text import androidx.ui.layout.fillMaxSize import androidx.ui.layout.wrapContentSize -import androidx.ui.material.MaterialTheme -import androidx.ui.material.Surface import androidx.ui.material.ripple.ripple import androidx.ui.tooling.preview.Preview import com.squareup.sample.hellocomposebinding.HelloWorkflow.Rendering @@ -44,12 +42,7 @@ private fun DrawHelloRendering(rendering: Rendering) { } } -@Preview(heightDp = 150) -@Composable -fun DrawHelloRenderingPreview() { - MaterialTheme { - Surface { - DrawHelloRendering(Rendering("Hello!", onClick = {})) - } - } +@Preview(heightDp = 150, showBackground = true) +@Composable private fun DrawHelloRenderingPreview() { + DrawHelloRendering(Rendering("Hello!", onClick = {})) } diff --git a/samples/hello-compose-rendering/src/main/java/com/squareup/sample/hellocomposerendering/HelloRenderingWorkflow.kt b/samples/hello-compose-rendering/src/main/java/com/squareup/sample/hellocomposerendering/HelloRenderingWorkflow.kt index 72b1429f87..1df824b11d 100644 --- a/samples/hello-compose-rendering/src/main/java/com/squareup/sample/hellocomposerendering/HelloRenderingWorkflow.kt +++ b/samples/hello-compose-rendering/src/main/java/com/squareup/sample/hellocomposerendering/HelloRenderingWorkflow.kt @@ -24,6 +24,7 @@ import androidx.ui.layout.fillMaxSize import androidx.ui.layout.wrapContentSize import androidx.ui.material.MaterialTheme import androidx.ui.material.ripple.ripple +import androidx.ui.tooling.preview.Preview import com.squareup.sample.hellocomposerendering.HelloRenderingWorkflow.Toggle import com.squareup.workflow.Sink import com.squareup.workflow.compose.ComposeWorkflow @@ -43,14 +44,26 @@ object HelloRenderingWorkflow : ComposeWorkflow() { outputSink: Sink, viewEnvironment: ViewEnvironment ) { - MaterialTheme { - Clickable( - onClick = { outputSink.send(Toggle) }, - modifier = Modifier.ripple(bounded = true) - .fillMaxSize() - ) { - Text(props, modifier = Modifier.wrapContentSize(Alignment.Center)) - } + Hello(props, onClick = { outputSink.send(Toggle) }) + } +} + +@Composable private fun Hello( + text: String, + onClick: () -> Unit +) { + MaterialTheme { + Clickable( + onClick = onClick, + modifier = Modifier.ripple(bounded = true) + .fillMaxSize() + ) { + Text(text, modifier = Modifier.wrapContentSize(Alignment.Center)) } } } + +@Preview(showBackground = true) +@Composable private fun HelloRenderingWorkflowPreview() { + Hello("hello", onClick = {}) +} From d3e4f38370a73ba67be41817517de7bea4531aaa Mon Sep 17 00:00:00 2001 From: Zach Klippenstein Date: Thu, 30 Jan 2020 07:59:28 -0800 Subject: [PATCH 12/67] Introduce tooling module with support for previewing ViewBindings with Compose's Preview. --- .buildscript/android-sample-app.gradle | 2 +- compose-tooling/api/compose-tooling.api | 17 ++ compose-tooling/build.gradle.kts | 43 ++++ compose-tooling/gradle.properties | 18 ++ .../tooling/PreviewComposeWorkflowTest.kt | 154 +++++++++++++++ .../compose/tooling/PreviewViewFactoryTest.kt | 187 ++++++++++++++++++ compose-tooling/src/main/AndroidManifest.xml | 16 ++ .../ui/compose/tooling/ComposeWorkflows.kt | 61 ++++++ .../compose/tooling/PlaceholderViewFactory.kt | 145 ++++++++++++++ .../compose/tooling/PreviewViewEnvironment.kt | 69 +++++++ .../ui/compose/tooling/ViewFactories.kt | 52 +++++ core-compose/api/core-compose.api | 4 + .../workflow/compose/ComposeWorkflow.kt | 21 ++ .../workflow/ui/compose/ViewFactories.kt | 6 +- .../hellocomposebinding/HelloBinding.kt | 8 +- .../HelloRenderingWorkflow.kt | 26 +-- .../sample/nestedrenderings/LegacyRunner.kt | 13 ++ .../nestedrenderings/RecursiveViewFactory.kt | 20 ++ settings.gradle.kts | 1 + 19 files changed, 839 insertions(+), 24 deletions(-) create mode 100644 compose-tooling/api/compose-tooling.api create mode 100644 compose-tooling/build.gradle.kts create mode 100644 compose-tooling/gradle.properties create mode 100644 compose-tooling/src/androidTest/java/com/squareup/workflow/ui/compose/tooling/PreviewComposeWorkflowTest.kt create mode 100644 compose-tooling/src/androidTest/java/com/squareup/workflow/ui/compose/tooling/PreviewViewFactoryTest.kt create mode 100644 compose-tooling/src/main/AndroidManifest.xml create mode 100644 compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/ComposeWorkflows.kt create mode 100644 compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/PlaceholderViewFactory.kt create mode 100644 compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/PreviewViewEnvironment.kt create mode 100644 compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/ViewFactories.kt diff --git a/.buildscript/android-sample-app.gradle b/.buildscript/android-sample-app.gradle index 6b99ace96f..e9203bcec8 100644 --- a/.buildscript/android-sample-app.gradle +++ b/.buildscript/android-sample-app.gradle @@ -1,8 +1,8 @@ apply from: rootProject.file('.buildscript/configure-android-defaults.gradle') dependencies { + implementation(project(":compose-tooling")) implementation(Deps.get("androidx.appcompat")) - implementation(Deps.get("compose.tooling")) implementation(Deps.get("timber")) implementation(Deps.get("workflow.core")) implementation(Deps.get("workflow.runtime")) diff --git a/compose-tooling/api/compose-tooling.api b/compose-tooling/api/compose-tooling.api new file mode 100644 index 0000000000..685ec8306d --- /dev/null +++ b/compose-tooling/api/compose-tooling.api @@ -0,0 +1,17 @@ +public final class com/squareup/workflow/ui/compose/tooling/BuildConfig { + public static final field BUILD_TYPE Ljava/lang/String; + public static final field DEBUG Z + public static final field LIBRARY_PACKAGE_NAME Ljava/lang/String; + public fun ()V +} + +public final class com/squareup/workflow/ui/compose/tooling/ComposeWorkflowsKt { + public static final fun preview (Lcom/squareup/workflow/compose/ComposeWorkflow;Ljava/lang/Object;Landroidx/ui/core/Modifier;Landroidx/ui/core/Modifier;Lkotlin/jvm/functions/Function1;Landroidx/compose/Composer;)V + public static synthetic fun preview$default (Lcom/squareup/workflow/compose/ComposeWorkflow;Ljava/lang/Object;Landroidx/ui/core/Modifier;Landroidx/ui/core/Modifier;Lkotlin/jvm/functions/Function1;Landroidx/compose/Composer;ILjava/lang/Object;)V +} + +public final class com/squareup/workflow/ui/compose/tooling/ViewFactoriesKt { + public static final fun preview (Lcom/squareup/workflow/ui/ViewFactory;Ljava/lang/Object;Landroidx/ui/core/Modifier;Landroidx/ui/core/Modifier;Lkotlin/jvm/functions/Function1;Landroidx/compose/Composer;)V + public static synthetic fun preview$default (Lcom/squareup/workflow/ui/ViewFactory;Ljava/lang/Object;Landroidx/ui/core/Modifier;Landroidx/ui/core/Modifier;Lkotlin/jvm/functions/Function1;Landroidx/compose/Composer;ILjava/lang/Object;)V +} + diff --git a/compose-tooling/build.gradle.kts b/compose-tooling/build.gradle.kts new file mode 100644 index 0000000000..299b6230e5 --- /dev/null +++ b/compose-tooling/build.gradle.kts @@ -0,0 +1,43 @@ +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +/* + * Copyright 2020 Square Inc. + * + * 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. + */ +plugins { + id("com.android.library") + kotlin("android") +} + +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} + +apply(from = rootProject.file(".buildscript/configure-maven-publish.gradle")) +apply(from = rootProject.file(".buildscript/configure-android-defaults.gradle")) +apply(from = rootProject.file(".buildscript/android-ui-tests.gradle")) + +apply(from = rootProject.file(".buildscript/configure-compose.gradle")) +tasks.withType { + kotlinOptions.apiVersion = "1.3" +} + +dependencies { + api(project(":core-compose")) + api(Dependencies.Compose.tooling) + api(Dependencies.Kotlin.stdlib) + + implementation(Dependencies.Compose.foundation) +} diff --git a/compose-tooling/gradle.properties b/compose-tooling/gradle.properties new file mode 100644 index 0000000000..a2027bd02d --- /dev/null +++ b/compose-tooling/gradle.properties @@ -0,0 +1,18 @@ +# +# Copyright 2020 Square Inc. +# +# 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_ARTIFACT_ID=workflow-ui-compose-tooling +POM_NAME=Workflow UI Compose Tooling +POM_PACKAGING=aar diff --git a/compose-tooling/src/androidTest/java/com/squareup/workflow/ui/compose/tooling/PreviewComposeWorkflowTest.kt b/compose-tooling/src/androidTest/java/com/squareup/workflow/ui/compose/tooling/PreviewComposeWorkflowTest.kt new file mode 100644 index 0000000000..7feab89d93 --- /dev/null +++ b/compose-tooling/src/androidTest/java/com/squareup/workflow/ui/compose/tooling/PreviewComposeWorkflowTest.kt @@ -0,0 +1,154 @@ +/* + * Copyright 2020 Square Inc. + * + * 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:Suppress("TestFunctionName", "PrivatePropertyName") + +package com.squareup.workflow.ui.compose.tooling + +import androidx.compose.Composable +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.ui.core.Modifier +import androidx.ui.foundation.Text +import androidx.ui.layout.Column +import androidx.ui.layout.size +import androidx.ui.semantics.Semantics +import androidx.ui.test.assertIsDisplayed +import androidx.ui.test.assertIsNotDisplayed +import androidx.ui.test.createComposeRule +import androidx.ui.test.findByText +import androidx.ui.tooling.preview.Preview +import androidx.ui.unit.dp +import com.squareup.workflow.Workflow +import com.squareup.workflow.compose.composed +import com.squareup.workflow.ui.ViewEnvironmentKey +import com.squareup.workflow.ui.compose.showRendering +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +/** + * Duplicate of [PreviewViewFactoryTest] but for [com.squareup.workflow.compose.ComposeWorkflow]. + */ +@RunWith(AndroidJUnit4::class) +class PreviewComposeWorkflowTest { + + @Rule @JvmField val composeRule = createComposeRule() + + @Test fun singleChild() { + composeRule.setContent { + ParentWithOneChildPreview() + } + + findByText("one").assertIsDisplayed() + findByText("two").assertIsDisplayed() + } + + @Test fun twoChildren() { + composeRule.setContent { + ParentWithTwoChildrenPreview() + } + + findByText("one").assertIsDisplayed() + findByText("two").assertIsDisplayed() + findByText("three").assertIsDisplayed() + } + + @Test fun modifierIsApplied() { + composeRule.setContent { + ParentWithModifier() + } + + // The view factory will be rendered with size (0,0), so it should be reported as not displayed. + findByText("one").assertIsNotDisplayed() + findByText("two").assertIsNotDisplayed() + } + + @Test fun placeholderModifierIsApplied() { + composeRule.setContent { + ParentWithPlaceholderModifier() + } + + // The child will be rendered with size (0,0), so it should be reported as not displayed. + findByText("one").assertIsDisplayed() + findByText("two").assertIsNotDisplayed() + } + + @Test fun customViewEnvironment() { + composeRule.setContent { + ParentConsumesCustomKeyPreview() + } + + findByText("foo").assertIsDisplayed() + } + + private val ParentWithOneChild = + Workflow.composed, Nothing> { props, _, environment -> + Column { + Text(props.first) + Semantics(container = true, mergeAllDescendants = true) { + environment.showRendering(rendering = props.second) + } + } + } + + @Preview @Composable private fun ParentWithOneChildPreview() { + ParentWithOneChild.preview(Pair("one", "two")) + } + + private val ParentWithTwoChildren = + Workflow.composed, Nothing> { props, _, environment -> + Column { + Semantics(container = true) { + environment.showRendering(rendering = props.first) + } + Text(props.second) + Semantics(container = true) { + environment.showRendering(rendering = props.third) + } + } + } + + @Preview @Composable private fun ParentWithTwoChildrenPreview() { + ParentWithTwoChildren.preview(Triple("one", "two", "three")) + } + + @Preview @Composable private fun ParentWithModifier() { + ParentWithOneChild.preview( + Pair("one", "two"), + modifier = Modifier.size(0.dp) + ) + } + + @Preview @Composable private fun ParentWithPlaceholderModifier() { + ParentWithOneChild.preview( + Pair("one", "two"), + placeholderModifier = Modifier.size(0.dp) + ) + } + + object TestEnvironmentKey : ViewEnvironmentKey(String::class) { + override val default: String get() = error("Not specified") + } + + private val ParentConsumesCustomKey = Workflow.composed { _, _, environment -> + Text(environment[TestEnvironmentKey]) + } + + @Preview @Composable private fun ParentConsumesCustomKeyPreview() { + ParentConsumesCustomKey.preview(Unit) { + it + (TestEnvironmentKey to "foo") + } + } +} diff --git a/compose-tooling/src/androidTest/java/com/squareup/workflow/ui/compose/tooling/PreviewViewFactoryTest.kt b/compose-tooling/src/androidTest/java/com/squareup/workflow/ui/compose/tooling/PreviewViewFactoryTest.kt new file mode 100644 index 0000000000..cab900a545 --- /dev/null +++ b/compose-tooling/src/androidTest/java/com/squareup/workflow/ui/compose/tooling/PreviewViewFactoryTest.kt @@ -0,0 +1,187 @@ +/* + * Copyright 2020 Square Inc. + * + * 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:Suppress("TestFunctionName", "PrivatePropertyName") + +package com.squareup.workflow.ui.compose.tooling + +import androidx.compose.Composable +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.ui.core.Modifier +import androidx.ui.foundation.Text +import androidx.ui.layout.Column +import androidx.ui.layout.size +import androidx.ui.semantics.Semantics +import androidx.ui.test.assertIsDisplayed +import androidx.ui.test.assertIsNotDisplayed +import androidx.ui.test.createComposeRule +import androidx.ui.test.findByText +import androidx.ui.tooling.preview.Preview +import androidx.ui.unit.dp +import com.squareup.workflow.ui.ViewEnvironmentKey +import com.squareup.workflow.ui.compose.bindCompose +import com.squareup.workflow.ui.compose.showRendering +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class PreviewViewFactoryTest { + + @Rule @JvmField val composeRule = createComposeRule() + + @Test fun singleChild() { + composeRule.setContent { + ParentWithOneChildPreview() + } + + findByText("one").assertIsDisplayed() + findByText("two").assertIsDisplayed() + } + + @Test fun twoChildren() { + composeRule.setContent { + ParentWithTwoChildrenPreview() + } + + findByText("one").assertIsDisplayed() + findByText("two").assertIsDisplayed() + findByText("three").assertIsDisplayed() + } + + @Test fun recursive() { + composeRule.setContent { + ParentRecursivePreview() + } + + findByText("one").assertIsDisplayed() + findByText("two").assertIsDisplayed() + findByText("three").assertIsDisplayed() + } + + @Test fun modifierIsApplied() { + composeRule.setContent { + ParentWithModifier() + } + + // The view factory will be rendered with size (0,0), so it should be reported as not displayed. + findByText("one").assertIsNotDisplayed() + findByText("two").assertIsNotDisplayed() + } + + @Test fun placeholderModifierIsApplied() { + composeRule.setContent { + ParentWithPlaceholderModifier() + } + + // The child will be rendered with size (0,0), so it should be reported as not displayed. + findByText("one").assertIsDisplayed() + findByText("two").assertIsNotDisplayed() + } + + @Test fun customViewEnvironment() { + composeRule.setContent { + ParentConsumesCustomKeyPreview() + } + + findByText("foo").assertIsDisplayed() + } + + private val ParentWithOneChild = bindCompose> { rendering, environment -> + Column { + Text(rendering.first) + Semantics(container = true, mergeAllDescendants = true) { + environment.showRendering(rendering = rendering.second) + } + } + } + + @Preview @Composable private fun ParentWithOneChildPreview() { + ParentWithOneChild.preview(Pair("one", "two")) + } + + private val ParentWithTwoChildren = + bindCompose> { rendering, environment -> + Column { + Semantics(container = true) { + environment.showRendering(rendering = rendering.first) + } + Text(rendering.second) + Semantics(container = true) { + environment.showRendering(rendering = rendering.third) + } + } + } + + @Preview @Composable private fun ParentWithTwoChildrenPreview() { + ParentWithTwoChildren.preview(Triple("one", "two", "three")) + } + + data class RecursiveRendering( + val text: String, + val child: RecursiveRendering? = null + ) + + private val ParentRecursive = bindCompose { rendering, environment -> + Column { + Text(rendering.text) + rendering.child?.let { child -> + Semantics(container = true) { + environment.showRendering(rendering = child) + } + } + } + } + + @Preview @Composable private fun ParentRecursivePreview() { + ParentRecursive.preview( + RecursiveRendering( + text = "one", + child = RecursiveRendering( + text = "two", + child = RecursiveRendering(text = "three") + ) + ) + ) + } + + @Preview @Composable private fun ParentWithModifier() { + ParentWithOneChild.preview( + Pair("one", "two"), + modifier = Modifier.size(0.dp) + ) + } + + @Preview @Composable private fun ParentWithPlaceholderModifier() { + ParentWithOneChild.preview( + Pair("one", "two"), + placeholderModifier = Modifier.size(0.dp) + ) + } + + object TestEnvironmentKey : ViewEnvironmentKey(String::class) { + override val default: String get() = error("Not specified") + } + + private val ParentConsumesCustomKey = bindCompose { _, environment -> + Text(environment[TestEnvironmentKey]) + } + + @Preview @Composable private fun ParentConsumesCustomKeyPreview() { + ParentConsumesCustomKey.preview(Unit) { + it + (TestEnvironmentKey to "foo") + } + } +} diff --git a/compose-tooling/src/main/AndroidManifest.xml b/compose-tooling/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..7b52cef9d5 --- /dev/null +++ b/compose-tooling/src/main/AndroidManifest.xml @@ -0,0 +1,16 @@ + + diff --git a/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/ComposeWorkflows.kt b/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/ComposeWorkflows.kt new file mode 100644 index 0000000000..c01324edca --- /dev/null +++ b/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/ComposeWorkflows.kt @@ -0,0 +1,61 @@ +/* + * Copyright 2020 Square Inc. + * + * 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:Suppress("RemoveEmptyParenthesesFromAnnotationEntry") + +package com.squareup.workflow.ui.compose.tooling + +import androidx.compose.Composable +import androidx.ui.core.Modifier +import androidx.ui.foundation.Box +import com.squareup.workflow.Sink +import com.squareup.workflow.compose.ComposeWorkflow +import com.squareup.workflow.ui.ViewEnvironment +import com.squareup.workflow.ui.ViewFactory +import com.squareup.workflow.ui.ViewRegistry + +/** + * Draws this [ComposeWorkflow] using a special preview [ViewRegistry]. + * + * Use inside `@Preview` Composable functions. + * + * *Note: [props] must be the `PropsT` of this [ComposeWorkflow], even though the type system does + * not enforce this constraint. This is due to a Compose compiler bug tracked + * [here](https://issuetracker.google.com/issues/156527332).* + * + * @param modifier [Modifier] that will be applied to this [ViewFactory]. + * @param placeholderModifier [Modifier] that will be applied to any nested renderings this factory + * shows. + * @param viewEnvironmentUpdater Function that configures the [ViewEnvironment] passed to this + * factory. + */ +// TODO(https://issuetracker.google.com/issues/156527332) Should be ViewFactory +@Composable fun ComposeWorkflow<*, *>.preview( + props: PropsT, + modifier: Modifier = Modifier, + placeholderModifier: Modifier = Modifier, + viewEnvironmentUpdater: ((ViewEnvironment) -> ViewEnvironment)? = null +) { + val previewEnvironment = previewViewEnvironment(placeholderModifier, viewEnvironmentUpdater) + Box(modifier = modifier) { + // Cast is needed due to bug that prevents the receiver from using PropsT. + @Suppress("UNCHECKED_CAST") + (this as ComposeWorkflow).render(props, NoopSink, previewEnvironment) + } +} + +private object NoopSink : Sink { + override fun send(value: Any) = Unit +} diff --git a/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/PlaceholderViewFactory.kt b/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/PlaceholderViewFactory.kt new file mode 100644 index 0000000000..75056be8d8 --- /dev/null +++ b/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/PlaceholderViewFactory.kt @@ -0,0 +1,145 @@ +/* + * Copyright 2020 Square Inc. + * + * 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:Suppress("SameParameterValue", "DEPRECATION") + +package com.squareup.workflow.ui.compose.tooling + +import androidx.compose.Composable +import androidx.ui.core.DrawScope +import androidx.ui.core.Modifier +import androidx.ui.core.clipToBounds +import androidx.ui.core.drawBehind +import androidx.ui.foundation.Box +import androidx.ui.foundation.Text +import androidx.ui.foundation.drawBorder +import androidx.ui.geometry.Offset +import androidx.ui.graphics.Color +import androidx.ui.graphics.Paint +import androidx.ui.graphics.Shadow +import androidx.ui.graphics.withSave +import androidx.ui.graphics.withSaveLayer +import androidx.ui.layout.fillMaxSize +import androidx.ui.text.TextStyle +import androidx.ui.text.style.TextAlign +import androidx.ui.tooling.preview.Preview +import androidx.ui.unit.Dp +import androidx.ui.unit.dp +import androidx.ui.unit.px +import androidx.ui.unit.toRect +import com.squareup.workflow.ui.ViewFactory +import com.squareup.workflow.ui.compose.bindCompose + +/** + * A [ViewFactory] that will be used any time a [PreviewViewRegistry] is asked to show a rendering. + * It displays a placeholder graphic and the rendering's `toString()` result. + */ +internal fun placeholderViewFactory(modifier: Modifier): ViewFactory = + bindCompose { rendering, _ -> + Text( + modifier = modifier/*.fillMaxSize()*/ + .clipToBounds() + .drawBehind { + withSaveLayer(size.toRect(), Paint().apply { alpha = .2f }) { + drawRect(size.toRect(), Paint().apply { color = Color.Gray }) + drawCrossHatch( + color = Color.Red, + strokeWidth = 2.dp, + spaceWidth = 5.dp, + angle = 45f + ) + } + }, + text = rendering.toString(), + style = TextStyle( + textAlign = TextAlign.Center, + color = Color.White, + shadow = Shadow(blurRadius = 5.px, color = Color.Black) + ) + ) + } + +@Preview(widthDp = 200, heightDp = 200) +@Composable private fun PreviewStubViewBindingOnWhite() { + Box(backgroundColor = Color.White) { + placeholderViewFactory(Modifier).preview( + rendering = "preview", + modifier = Modifier.fillMaxSize() + .drawBorder(size = 1.dp, color = Color.Red) + ) + } +} + +@Preview(widthDp = 200, heightDp = 200) +@Composable private fun PreviewStubViewBindingOnBlack() { + Box(backgroundColor = Color.Black) { + placeholderViewFactory(Modifier).preview( + rendering = "preview", + modifier = Modifier.fillMaxSize() + .drawBorder(size = 1.dp, color = Color.Red) + ) + } +} + +private fun DrawScope.drawCrossHatch( + color: Color, + strokeWidth: Dp, + spaceWidth: Dp, + angle: Float +) { + drawHatch(color, strokeWidth, spaceWidth, angle) + drawHatch(color, strokeWidth, spaceWidth, angle + 90) +} + +private fun DrawScope.drawHatch( + color: Color, + strokeWidth: Dp, + spaceWidth: Dp, + angle: Float +) { + val strokeWidthPx = strokeWidth.toPx() + .value + val paint = Paint().also { + it.color = color.scaleColors(.5f) + it.strokeWidth = strokeWidthPx + } + + withSave { + val halfWidth = size.width.value / 2 + val halfHeight = size.height.value / 2 + translate(halfWidth, halfHeight) + rotate(angle) + translate(-halfWidth, -halfHeight) + + // Draw outside our bounds to fill the space even when rotated. + val left = -size.width.value + val right = size.width.value * 2 + val top = -size.height.value + val bottom = size.height.value * 2 + + var y = top + strokeWidthPx * 2f + while (y < bottom) { + drawLine( + Offset(left, y), + Offset(right, y), + paint + ) + y += spaceWidth.toPx().value * 2 + } + } +} + +private fun Color.scaleColors(factor: Float) = + copy(red = red * factor, green = green * factor, blue = blue * factor) diff --git a/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/PreviewViewEnvironment.kt b/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/PreviewViewEnvironment.kt new file mode 100644 index 0000000000..05a86cf86b --- /dev/null +++ b/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/PreviewViewEnvironment.kt @@ -0,0 +1,69 @@ +/* + * Copyright 2020 Square Inc. + * + * 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.squareup.workflow.ui.compose.tooling + +import androidx.compose.Composable +import androidx.compose.Immutable +import androidx.compose.remember +import androidx.ui.core.Modifier +import com.squareup.workflow.ui.ViewEnvironment +import com.squareup.workflow.ui.ViewFactory +import com.squareup.workflow.ui.ViewRegistry +import kotlin.reflect.KClass + +/** + * Creates and [remember]s a [ViewEnvironment] that has a special [ViewRegistry] and any additional + * elements as configured by [viewEnvironmentUpdater]. + * + * The [ViewRegistry] will contain [mainFactory] if specified, as well as a [placeholderViewFactory] + * that will be used to show any renderings that don't match [mainFactory]'s type. All placeholders + * will have [placeholderModifier] applied. + */ +@Composable internal fun previewViewEnvironment( + placeholderModifier: Modifier, + viewEnvironmentUpdater: ((ViewEnvironment) -> ViewEnvironment)? = null, + mainFactory: ViewFactory<*>? = null +): ViewEnvironment { + val viewRegistry = remember(mainFactory, placeholderModifier) { + PreviewViewRegistry(mainFactory, placeholderViewFactory(placeholderModifier)) + } + return remember(viewRegistry, viewEnvironmentUpdater) { + ViewEnvironment(viewRegistry).let { environment -> + // Give the preview a chance to add its own elements to the ViewEnvironment. + viewEnvironmentUpdater?.let { it(environment) } ?: environment + } + } +} + +/** + * A [ViewRegistry] that uses [mainFactory] for rendering [RenderingT]s, and [placeholderFactory] + * for all other [showRendering][com.squareup.workflow.ui.compose.showRendering] calls. + */ +@Immutable +private class PreviewViewRegistry( + private val mainFactory: ViewFactory? = null, + private val placeholderFactory: ViewFactory +) : ViewRegistry { + override val keys: Set> get() = mainFactory?.let { setOf(it.type) } ?: emptySet() + + @Suppress("UNCHECKED_CAST") + override fun getFactoryFor( + renderingType: KClass + ): ViewFactory = when (renderingType) { + mainFactory?.type -> mainFactory + else -> placeholderFactory + } as ViewFactory +} diff --git a/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/ViewFactories.kt b/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/ViewFactories.kt new file mode 100644 index 0000000000..4314486f60 --- /dev/null +++ b/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/ViewFactories.kt @@ -0,0 +1,52 @@ +/* + * Copyright 2020 Square Inc. + * + * 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:Suppress("RemoveEmptyParenthesesFromAnnotationEntry") + +package com.squareup.workflow.ui.compose.tooling + +import androidx.compose.Composable +import androidx.ui.core.Modifier +import com.squareup.workflow.ui.ViewEnvironment +import com.squareup.workflow.ui.ViewFactory +import com.squareup.workflow.ui.ViewRegistry +import com.squareup.workflow.ui.compose.showRendering + +/** + * Draws this [ViewFactory] using a special preview [ViewRegistry]. + * + * Use inside `@Preview` Composable functions. + * + * *Note: [rendering] must be the same type as this [ViewFactory], even though the type system does + * not enforce this constraint. This is due to a Compose compiler bug tracked + * [here](https://issuetracker.google.com/issues/156527332).* + * + * @param modifier [Modifier] that will be applied to this [ViewFactory]. + * @param placeholderModifier [Modifier] that will be applied to any nested renderings this factory + * shows. + * @param viewEnvironmentUpdater Function that configures the [ViewEnvironment] passed to this + * factory. + */ +// TODO(https://issuetracker.google.com/issues/156527332) Should be ViewFactory +@Composable fun ViewFactory<*>.preview( + rendering: RenderingT, + modifier: Modifier = Modifier, + placeholderModifier: Modifier = Modifier, + viewEnvironmentUpdater: ((ViewEnvironment) -> ViewEnvironment)? = null +) { + val previewEnvironment = + previewViewEnvironment(placeholderModifier, viewEnvironmentUpdater, mainFactory = this) + previewEnvironment.showRendering(rendering, modifier) +} diff --git a/core-compose/api/core-compose.api b/core-compose/api/core-compose.api index 579885d896..9b005b93ca 100644 --- a/core-compose/api/core-compose.api +++ b/core-compose/api/core-compose.api @@ -15,6 +15,10 @@ public abstract class com/squareup/workflow/compose/ComposeWorkflow : com/square public abstract fun render (Ljava/lang/Object;Lcom/squareup/workflow/Sink;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/compose/Composer;)V } +public final class com/squareup/workflow/compose/ComposeWorkflowKt { + public static final fun composed (Lcom/squareup/workflow/Workflow$Companion;Lkotlin/jvm/functions/Function4;)Lcom/squareup/workflow/compose/ComposeWorkflow; +} + public final class com/squareup/workflow/ui/compose/ComposeSupportKt { public static final fun ()V } diff --git a/core-compose/src/main/java/com/squareup/workflow/compose/ComposeWorkflow.kt b/core-compose/src/main/java/com/squareup/workflow/compose/ComposeWorkflow.kt index abfbcd4852..3f435d222d 100644 --- a/core-compose/src/main/java/com/squareup/workflow/compose/ComposeWorkflow.kt +++ b/core-compose/src/main/java/com/squareup/workflow/compose/ComposeWorkflow.kt @@ -13,6 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +@file:Suppress("RemoveEmptyParenthesesFromAnnotationEntry") + package com.squareup.workflow.compose import androidx.compose.Composable @@ -55,3 +57,22 @@ abstract class ComposeWorkflow : override fun asStatefulWorkflow(): StatefulWorkflow = ComposeWorkflowImpl(this) } + +/** + * Returns a [ComposeWorkflow] that renders itself using the given [render] function. + */ +inline fun Workflow.Companion.composed( + crossinline render: @Composable() ( + props: PropsT, + outputSink: Sink, + environment: ViewEnvironment + ) -> Unit +): ComposeWorkflow = object : ComposeWorkflow() { + @Composable override fun render( + props: PropsT, + outputSink: Sink, + viewEnvironment: ViewEnvironment + ) { + render(props, outputSink, viewEnvironment) + } +} diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/ViewFactories.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/ViewFactories.kt index 5b052a4c61..0dc5afbd2a 100644 --- a/core-compose/src/main/java/com/squareup/workflow/ui/compose/ViewFactories.kt +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/ViewFactories.kt @@ -33,10 +33,14 @@ import com.squareup.workflow.ui.compose.ComposableViewStubWrapper.Update * To display a nested rendering from a [Composable view binding][bindCompose], use * [ViewEnvironment.showRendering]. * + * *Note: [rendering] must be the same type as this [ViewFactory], even though the type system does + * not enforce this constraint. This is due to a Compose compiler bug tracked + * [here](https://issuetracker.google.com/issues/156527332).* + * * @see ViewEnvironment.showRendering * @see com.squareup.workflow.ui.ViewRegistry.showRendering */ -// Bug: IR compiler pukes on ViewFactory here. +// TODO(https://issuetracker.google.com/issues/156527332) Should be ViewFactory @Composable internal fun ViewFactory.showRendering( rendering: RenderingT, viewEnvironment: ViewEnvironment, diff --git a/samples/hello-compose-binding/src/main/java/com/squareup/sample/hellocomposebinding/HelloBinding.kt b/samples/hello-compose-binding/src/main/java/com/squareup/sample/hellocomposebinding/HelloBinding.kt index d49017d004..1f06ed116d 100644 --- a/samples/hello-compose-binding/src/main/java/com/squareup/sample/hellocomposebinding/HelloBinding.kt +++ b/samples/hello-compose-binding/src/main/java/com/squareup/sample/hellocomposebinding/HelloBinding.kt @@ -26,13 +26,9 @@ import androidx.ui.material.ripple.ripple import androidx.ui.tooling.preview.Preview import com.squareup.sample.hellocomposebinding.HelloWorkflow.Rendering import com.squareup.workflow.ui.compose.bindCompose +import com.squareup.workflow.ui.compose.tooling.preview val HelloBinding = bindCompose { rendering, _ -> - DrawHelloRendering(rendering) -} - -@Composable -private fun DrawHelloRendering(rendering: Rendering) { Clickable( modifier = Modifier.fillMaxSize() .ripple(bounded = true), @@ -44,5 +40,5 @@ private fun DrawHelloRendering(rendering: Rendering) { @Preview(heightDp = 150, showBackground = true) @Composable private fun DrawHelloRenderingPreview() { - DrawHelloRendering(Rendering("Hello!", onClick = {})) + HelloBinding.preview(Rendering("Hello!", onClick = {})) } diff --git a/samples/hello-compose-rendering/src/main/java/com/squareup/sample/hellocomposerendering/HelloRenderingWorkflow.kt b/samples/hello-compose-rendering/src/main/java/com/squareup/sample/hellocomposerendering/HelloRenderingWorkflow.kt index 1df824b11d..1285fe1bc7 100644 --- a/samples/hello-compose-rendering/src/main/java/com/squareup/sample/hellocomposerendering/HelloRenderingWorkflow.kt +++ b/samples/hello-compose-rendering/src/main/java/com/squareup/sample/hellocomposerendering/HelloRenderingWorkflow.kt @@ -29,6 +29,7 @@ import com.squareup.sample.hellocomposerendering.HelloRenderingWorkflow.Toggle import com.squareup.workflow.Sink import com.squareup.workflow.compose.ComposeWorkflow import com.squareup.workflow.ui.ViewEnvironment +import com.squareup.workflow.ui.compose.tooling.preview /** * A [ComposeWorkflow] that is used by [HelloWorkflow] to render the screen. @@ -44,26 +45,19 @@ object HelloRenderingWorkflow : ComposeWorkflow() { outputSink: Sink, viewEnvironment: ViewEnvironment ) { - Hello(props, onClick = { outputSink.send(Toggle) }) - } -} - -@Composable private fun Hello( - text: String, - onClick: () -> Unit -) { - MaterialTheme { - Clickable( - onClick = onClick, - modifier = Modifier.ripple(bounded = true) - .fillMaxSize() - ) { - Text(text, modifier = Modifier.wrapContentSize(Alignment.Center)) + MaterialTheme { + Clickable( + onClick = { outputSink.send(Toggle) }, + modifier = Modifier.ripple(bounded = true) + .fillMaxSize() + ) { + Text(props, modifier = Modifier.wrapContentSize(Alignment.Center)) + } } } } @Preview(showBackground = true) @Composable private fun HelloRenderingWorkflowPreview() { - Hello("hello", onClick = {}) + HelloRenderingWorkflow.preview(props = "hello") } diff --git a/samples/nested-renderings/src/main/java/com/squareup/sample/nestedrenderings/LegacyRunner.kt b/samples/nested-renderings/src/main/java/com/squareup/sample/nestedrenderings/LegacyRunner.kt index 0643d23267..c7c680f4b4 100644 --- a/samples/nested-renderings/src/main/java/com/squareup/sample/nestedrenderings/LegacyRunner.kt +++ b/samples/nested-renderings/src/main/java/com/squareup/sample/nestedrenderings/LegacyRunner.kt @@ -15,12 +15,17 @@ */ package com.squareup.sample.nestedrenderings +import androidx.compose.Composable +import androidx.ui.core.Modifier +import androidx.ui.layout.fillMaxSize +import androidx.ui.tooling.preview.Preview import com.squareup.sample.nestedrenderings.RecursiveWorkflow.LegacyRendering import com.squareup.sample.nestedrenderings.databinding.LegacyViewBinding import com.squareup.workflow.ui.LayoutRunner import com.squareup.workflow.ui.LayoutRunner.Companion.bind import com.squareup.workflow.ui.ViewEnvironment import com.squareup.workflow.ui.ViewFactory +import com.squareup.workflow.ui.compose.tooling.preview /** * A [LayoutRunner] that renders [LegacyRendering]s using the legacy view framework. @@ -38,3 +43,11 @@ class LegacyRunner(private val binding: LegacyViewBinding) : LayoutRunner { rendering, viewEnvironment - } } +@Preview +@Composable private fun RecursiveViewFactoryPreview() { + Providers(BackgroundColorAmbient provides Color.Green) { + RecursiveViewFactory.preview( + Rendering( + children = listOf( + "foo", + Rendering( + children = listOf("bar"), + onAddChildClicked = {}, onResetClicked = {} + ) + ), onAddChildClicked = {}, onResetClicked = {} + ), + placeholderModifier = Modifier.fillMaxSize() + ) + } +} + @Composable private fun Children( children: List, viewEnvironment: ViewEnvironment, diff --git a/settings.gradle.kts b/settings.gradle.kts index 8d99743b2a..ce3c6763e6 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -16,6 +16,7 @@ rootProject.name = "workflow-compose" include( + ":compose-tooling", ":core-compose", ":samples:hello-compose-binding", ":samples:hello-compose-rendering", From 50bfb925fc5210c644348fc968e8568c87978fef Mon Sep 17 00:00:00 2001 From: Zach Klippenstein Date: Thu, 7 Nov 2019 12:59:38 -0800 Subject: [PATCH 13/67] Introduce WorkflowContainer for running a workflow inside a Compose app. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is the third flavor of integration, it replaces `setContentWorkflow`, `WorkflowLayout`, `WorkflowRunnerViewModel`, etc., and makes it really easy to run a `Workflow` inside a pure Compose app. It's compatible with `ViewEnvironment`/`ViewRegistry`, but doesn't require it – it gives you the root rendering, and you can do whatever you want with it. It also supports running root `ComposeWorkflow`s directly, since they are self-rendering. --- buildSrc/src/main/java/Dependencies.kt | 1 + core-compose/api/core-compose.api | 19 + core-compose/build.gradle.kts | 2 +- .../ui/compose/WorkflowContainerTest.kt | 202 ++++++++++ .../workflow/ui/compose/WorkflowContainer.kt | 344 ++++++++++++++++++ samples/hello-compose/build.gradle.kts | 48 +++ .../src/main/AndroidManifest.xml | 38 ++ .../com/squareup/sample/hellocompose/App.kt | 57 +++ .../sample/hellocompose/HelloBinding.kt | 36 ++ .../hellocompose/HelloComposeActivity.kt | 29 ++ .../sample/hellocompose/HelloWorkflow.kt | 64 ++++ .../src/main/res/values/strings.xml | 18 + .../src/main/res/values/styles.xml | 23 ++ settings.gradle.kts | 1 + 14 files changed, 881 insertions(+), 1 deletion(-) create mode 100644 core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/WorkflowContainerTest.kt create mode 100644 core-compose/src/main/java/com/squareup/workflow/ui/compose/WorkflowContainer.kt create mode 100644 samples/hello-compose/build.gradle.kts create mode 100644 samples/hello-compose/src/main/AndroidManifest.xml create mode 100644 samples/hello-compose/src/main/java/com/squareup/sample/hellocompose/App.kt create mode 100644 samples/hello-compose/src/main/java/com/squareup/sample/hellocompose/HelloBinding.kt create mode 100644 samples/hello-compose/src/main/java/com/squareup/sample/hellocompose/HelloComposeActivity.kt create mode 100644 samples/hello-compose/src/main/java/com/squareup/sample/hellocompose/HelloWorkflow.kt create mode 100644 samples/hello-compose/src/main/res/values/strings.xml create mode 100644 samples/hello-compose/src/main/res/values/styles.xml diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt index 579f9d4c71..d15a07bc72 100644 --- a/buildSrc/src/main/java/Dependencies.kt +++ b/buildSrc/src/main/java/Dependencies.kt @@ -37,6 +37,7 @@ object Dependencies { const val foundation = "androidx.ui:ui-foundation:${Versions.compose}" const val layout = "androidx.ui:ui-layout:${Versions.compose}" const val material = "androidx.ui:ui-material:${Versions.compose}" + const val savedstate = "androidx.ui:ui-saved-instance-state:${Versions.compose}" const val test = "androidx.ui:ui-test:${Versions.compose}" const val tooling = "androidx.ui:ui-tooling:${Versions.compose}" } diff --git a/core-compose/api/core-compose.api b/core-compose/api/core-compose.api index 9b005b93ca..096578eb36 100644 --- a/core-compose/api/core-compose.api +++ b/core-compose/api/core-compose.api @@ -51,6 +51,25 @@ public final class com/squareup/workflow/ui/compose/ViewEnvironmentsKt { public static synthetic fun showRendering$default (Lcom/squareup/workflow/ui/ViewEnvironment;Ljava/lang/Object;Landroidx/ui/core/Modifier;Landroidx/compose/Composer;ILjava/lang/Object;)V } +public final class com/squareup/workflow/ui/compose/WorkflowContainerKt { + public static final fun WorkflowContainer (Lcom/squareup/workflow/Workflow;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Lkotlin/jvm/functions/Function2;Landroidx/compose/Composer;)V + public static final fun WorkflowContainer (Lcom/squareup/workflow/Workflow;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;)V + public static final fun WorkflowContainer (Lcom/squareup/workflow/Workflow;Lcom/squareup/workflow/ui/ViewEnvironment;Ljava/lang/Object;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;)V + public static final fun WorkflowContainer (Lcom/squareup/workflow/Workflow;Lcom/squareup/workflow/ui/ViewEnvironment;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;)V + public static final fun WorkflowContainer (Lcom/squareup/workflow/Workflow;Lcom/squareup/workflow/ui/ViewEnvironment;Lkotlin/jvm/functions/Function1;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;)V + public static final fun WorkflowContainer (Lcom/squareup/workflow/Workflow;Ljava/lang/Object;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Lkotlin/jvm/functions/Function2;Landroidx/compose/Composer;)V + public static final fun WorkflowContainer (Lcom/squareup/workflow/Workflow;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Lkotlin/jvm/functions/Function2;Landroidx/compose/Composer;)V + public static final fun WorkflowContainer (Lcom/squareup/workflow/Workflow;Lkotlin/jvm/functions/Function1;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Lkotlin/jvm/functions/Function2;Landroidx/compose/Composer;)V + public static synthetic fun WorkflowContainer$default (Lcom/squareup/workflow/Workflow;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Lkotlin/jvm/functions/Function2;Landroidx/compose/Composer;ILjava/lang/Object;)V + public static synthetic fun WorkflowContainer$default (Lcom/squareup/workflow/Workflow;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;ILjava/lang/Object;)V + public static synthetic fun WorkflowContainer$default (Lcom/squareup/workflow/Workflow;Lcom/squareup/workflow/ui/ViewEnvironment;Ljava/lang/Object;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;ILjava/lang/Object;)V + public static synthetic fun WorkflowContainer$default (Lcom/squareup/workflow/Workflow;Lcom/squareup/workflow/ui/ViewEnvironment;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;ILjava/lang/Object;)V + public static synthetic fun WorkflowContainer$default (Lcom/squareup/workflow/Workflow;Lcom/squareup/workflow/ui/ViewEnvironment;Lkotlin/jvm/functions/Function1;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;ILjava/lang/Object;)V + public static synthetic fun WorkflowContainer$default (Lcom/squareup/workflow/Workflow;Ljava/lang/Object;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Lkotlin/jvm/functions/Function2;Landroidx/compose/Composer;ILjava/lang/Object;)V + public static synthetic fun WorkflowContainer$default (Lcom/squareup/workflow/Workflow;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Lkotlin/jvm/functions/Function2;Landroidx/compose/Composer;ILjava/lang/Object;)V + public static synthetic fun WorkflowContainer$default (Lcom/squareup/workflow/Workflow;Lkotlin/jvm/functions/Function1;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Lkotlin/jvm/functions/Function2;Landroidx/compose/Composer;ILjava/lang/Object;)V +} + public final class com/squareup/workflow/ui/core/compose/BuildConfig { public static final field BUILD_TYPE Ljava/lang/String; public static final field DEBUG Z diff --git a/core-compose/build.gradle.kts b/core-compose/build.gradle.kts index 93b7b2a25c..cc3559f862 100644 --- a/core-compose/build.gradle.kts +++ b/core-compose/build.gradle.kts @@ -40,6 +40,6 @@ dependencies { implementation(Dependencies.Compose.foundation) implementation(Dependencies.Compose.layout) - implementation(Dependencies.Compose.tooling) + implementation(Dependencies.Compose.savedstate) implementation(Dependencies.Workflow.runtime) } diff --git a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/WorkflowContainerTest.kt b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/WorkflowContainerTest.kt new file mode 100644 index 0000000000..716b937591 --- /dev/null +++ b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/WorkflowContainerTest.kt @@ -0,0 +1,202 @@ +/* + * Copyright 2020 Square Inc. + * + * 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:Suppress("RemoveEmptyParenthesesFromAnnotationEntry") + +package com.squareup.workflow.ui.compose + +import androidx.compose.FrameManager +import androidx.compose.Providers +import androidx.compose.mutableStateOf +import androidx.compose.onActive +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.ui.foundation.Clickable +import androidx.ui.foundation.Text +import androidx.ui.layout.Column +import androidx.ui.savedinstancestate.UiSavedStateRegistry +import androidx.ui.savedinstancestate.UiSavedStateRegistryAmbient +import androidx.ui.test.createComposeRule +import androidx.ui.test.doClick +import androidx.ui.test.findByText +import androidx.ui.test.waitForIdle +import com.google.common.truth.Truth.assertThat +import com.squareup.workflow.RenderContext +import com.squareup.workflow.Snapshot +import com.squareup.workflow.StatefulWorkflow +import com.squareup.workflow.Workflow +import com.squareup.workflow.action +import com.squareup.workflow.parse +import com.squareup.workflow.readUtf8WithLength +import com.squareup.workflow.stateless +import com.squareup.workflow.ui.compose.WorkflowContainerTest.SnapshottingWorkflow.SnapshottedRendering +import com.squareup.workflow.writeUtf8WithLength +import okio.ByteString +import okio.ByteString.Companion.decodeBase64 +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class WorkflowContainerTest { + + @Rule @JvmField val composeRule = createComposeRule() + + @Test fun passesPropsThrough() { + val workflow = Workflow.stateless { it } + + composeRule.setContent { + WorkflowContainer(workflow, "foo") { + assertThat(it).isEqualTo("foo") + } + } + } + + @Test fun seesPropsAndRenderingUpdates() { + val workflow = Workflow.stateless { it } + val props = mutableStateOf("foo") + + composeRule.setContent { + WorkflowContainer(workflow, props.value) { + Text(it) + } + } + + findByText("foo").assertExists() + FrameManager.framed { + props.value = "bar" + } + findByText("bar").assertExists() + } + + @Test fun invokesOutputCallback() { + val workflow = Workflow.stateless Unit> { + { string -> actionSink.send(action { setOutput(string) }) } + } + + val receivedOutputs = mutableListOf() + composeRule.setContent { + WorkflowContainer(workflow, onOutput = { receivedOutputs += it }) { sendOutput -> + Column { + Clickable(onClick = { sendOutput("one") }) { + Text("send one") + } + Clickable(onClick = { sendOutput("two") }) { + Text("send two") + } + } + } + } + + waitForIdle() + assertThat(receivedOutputs).isEmpty() + findByText("send one").doClick() + + waitForIdle() + assertThat(receivedOutputs).isEqualTo(listOf("one")) + findByText("send two").doClick() + + waitForIdle() + assertThat(receivedOutputs).isEqualTo(listOf("one", "two")) + } + + @Test fun savesSnapshot() { + val savedStateRegistry = UiSavedStateRegistry(emptyMap()) { true } + + composeRule.setContent { + Providers(UiSavedStateRegistryAmbient provides savedStateRegistry) { + WorkflowContainerImpl( + SnapshottingWorkflow, + props = Unit, + onOutput = {}, + snapshotKey = SNAPSHOT_KEY + ) { (string, updateString) -> + onActive { + assertThat(string).isEmpty() + updateString("foo") + } + } + } + } + + waitForIdle() + val savedValues = FrameManager.framed { + savedStateRegistry.performSave() + } + println("saved keys: ${savedValues.keys}") + // Relying on the int key across all runtimes might be flaky, might need to pass explicit key. + val snapshot = ByteString.of(*(savedValues.getValue(SNAPSHOT_KEY) as ByteArray)) + println("snapshot: ${snapshot.base64()}") + assertThat(snapshot).isEqualTo(EXPECTED_SNAPSHOT) + } + + @Test fun restoresSnapshot() { + val restoreValues = mapOf(SNAPSHOT_KEY to EXPECTED_SNAPSHOT.toByteArray()) + val savedStateRegistry = UiSavedStateRegistry(restoreValues) { true } + + composeRule.setContent { + Providers(UiSavedStateRegistryAmbient provides savedStateRegistry) { + WorkflowContainerImpl( + SnapshottingWorkflow, + props = Unit, + onOutput = {}, + snapshotKey = "workflow-snapshot" + ) { (string) -> + onActive { + assertThat(string).isEqualTo("foo") + } + Text(string) + } + } + } + + findByText("foo").assertExists() + } + + private companion object { + const val SNAPSHOT_KEY = "workflow-snapshot" + val EXPECTED_SNAPSHOT = "AAAABwAAAANmb28AAAAA".decodeBase64()!! + } + + // Seems to be a problem accessing Workflow.stateful. + private object SnapshottingWorkflow : + StatefulWorkflow() { + + data class SnapshottedRendering( + val string: String, + val updateString: (String) -> Unit + ) + + override fun initialState( + props: Unit, + snapshot: Snapshot? + ): String = snapshot?.bytes?.parse { it.readUtf8WithLength() } ?: "" + + override fun render( + props: Unit, + state: String, + context: RenderContext + ) = SnapshottedRendering( + string = state, + updateString = { newString -> context.actionSink.send(updateString(newString)) } + ) + + override fun snapshotState(state: String): Snapshot = + Snapshot.write { it.writeUtf8WithLength(state) } + + private fun updateString(newString: String) = action { + nextState = newString + } + } +} diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/WorkflowContainer.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/WorkflowContainer.kt new file mode 100644 index 0000000000..c7900697a9 --- /dev/null +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/WorkflowContainer.kt @@ -0,0 +1,344 @@ +/* + * Copyright 2020 Square Inc. + * + * 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:Suppress( + "EXPERIMENTAL_API_USAGE", + "FunctionNaming", + "NOTHING_TO_INLINE", + "RemoveEmptyParenthesesFromAnnotationEntry" +) + +package com.squareup.workflow.ui.compose + +import androidx.annotation.VisibleForTesting +import androidx.annotation.VisibleForTesting.PRIVATE +import androidx.compose.Composable +import androidx.compose.Direct +import androidx.compose.Pivotal +import androidx.compose.State +import androidx.compose.onDispose +import androidx.compose.remember +import androidx.compose.state +import androidx.ui.core.CoroutineContextAmbient +import androidx.ui.core.Modifier +import androidx.ui.foundation.Box +import androidx.ui.savedinstancestate.Saver +import androidx.ui.savedinstancestate.SaverScope +import androidx.ui.savedinstancestate.UiSavedStateRegistryAmbient +import androidx.ui.savedinstancestate.savedInstanceState +import com.squareup.workflow.Snapshot +import com.squareup.workflow.Workflow +import com.squareup.workflow.compose.ComposeRendering +import com.squareup.workflow.diagnostic.WorkflowDiagnosticListener +import com.squareup.workflow.launchWorkflowIn +import com.squareup.workflow.ui.ViewEnvironment +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.cancel +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.channels.Channel.Factory.CONFLATED +import kotlinx.coroutines.flow.consumeAsFlow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import okio.ByteString +import kotlin.coroutines.CoroutineContext + +/** + * Render a [Workflow]'s renderings. + * + * When this function is first composed it will start a new runtime. This runtime will be restarted + * any time [workflow], [diagnosticListener], or the `CoroutineContext` + * changes. The runtime will be cancelled when this function stops composing. + * + * @param workflow The [Workflow] to render. + * @param props The props to render the root workflow with. If this value changes between calls, + * the workflow runtime will re-render with the new props. + * @param onOutput A function that will be invoked any time the root workflow emits an output. + * @param diagnosticListener A [WorkflowDiagnosticListener] to configure on the runtime. + * @param content A [Composable] function that gets executed every time the root workflow spits + * out a new rendering. + */ +@Direct +@Composable fun WorkflowContainer( + workflow: Workflow, + props: PropsT, + onOutput: (OutputT) -> Unit, + modifier: Modifier = Modifier, + diagnosticListener: WorkflowDiagnosticListener? = null, + content: @Composable() (rendering: RenderingT) -> Unit +) { + WorkflowContainerImpl(workflow, props, onOutput, modifier, diagnosticListener, content = content) +} + +/** + * Render a [Workflow]'s renderings. + * + * When this function is first composed it will start a new runtime. This runtime will be restarted + * any time [workflow], [diagnosticListener], or the `CoroutineContext` + * changes. The runtime will be cancelled when this function stops composing. + * + * @param workflow The [Workflow] to render. + * @param onOutput A function that will be invoked any time the root workflow emits an output. + * @param diagnosticListener A [WorkflowDiagnosticListener] to configure on the runtime. + * @param content A [Composable] function that gets executed every time the root workflow spits + * out a new rendering. + */ +@Composable inline fun WorkflowContainer( + workflow: Workflow, + noinline onOutput: (OutputT) -> Unit, + modifier: Modifier = Modifier, + diagnosticListener: WorkflowDiagnosticListener? = null, + noinline content: @Composable() (rendering: RenderingT) -> Unit +) { + WorkflowContainer(workflow, Unit, onOutput, modifier, diagnosticListener, content) +} + +/** + * Render a [Workflow]'s renderings. + * + * When this function is first composed it will start a new runtime. This runtime will be restarted + * any time [workflow], [diagnosticListener], or the `CoroutineContext` + * changes. The runtime will be cancelled when this function stops composing. + * + * @param workflow The [Workflow] to render. + * @param props The props to render the root workflow with. If this value changes between calls, + * the workflow runtime will re-render with the new props. + * @param diagnosticListener A [WorkflowDiagnosticListener] to configure on the runtime. + * @param content A [Composable] function that gets executed every time the root workflow spits + * out a new rendering. + */ +@Composable inline fun WorkflowContainer( + workflow: Workflow, + props: PropsT, + modifier: Modifier = Modifier, + diagnosticListener: WorkflowDiagnosticListener? = null, + noinline content: @Composable() (rendering: RenderingT) -> Unit +) { + WorkflowContainer(workflow, props, {}, modifier, diagnosticListener, content) +} + +/** + * Render a [Workflow]'s renderings. + * + * When this function is first composed it will start a new runtime. This runtime will be restarted + * any time [workflow], [diagnosticListener], or the `CoroutineContext` + * changes. The runtime will be cancelled when this function stops composing. + * + * @param workflow The [Workflow] to render. + * @param diagnosticListener A [WorkflowDiagnosticListener] to configure on the runtime. + * @param content A [Composable] function that gets executed every time the root workflow spits + * out a new rendering. + */ +@Composable inline fun WorkflowContainer( + workflow: Workflow, + modifier: Modifier = Modifier, + diagnosticListener: WorkflowDiagnosticListener? = null, + noinline content: @Composable() (rendering: RenderingT) -> Unit +) { + WorkflowContainer(workflow, Unit, {}, modifier, diagnosticListener, content) +} + +/** + * Render a [Workflow]'s renderings. + * + * When this function is first composed it will start a new runtime. This runtime will be restarted + * any time [workflow], [diagnosticListener], or the `CoroutineContext` + * changes. The runtime will be cancelled when this function stops composing. + * + * @param workflow The [Workflow] to render. + * @param viewEnvironment The [ViewEnvironment] used to show the [ComposeRendering]s emitted by + * the workflow. + * @param props The props to render the root workflow with. If this value changes between calls, + * the workflow runtime will re-render with the new props. + * @param onOutput A function that will be invoked any time the root workflow emits an output. + * @param diagnosticListener A [WorkflowDiagnosticListener] to configure on the runtime. + */ +@Direct +@Composable fun WorkflowContainer( + workflow: Workflow, + viewEnvironment: ViewEnvironment, + props: PropsT, + onOutput: (OutputT) -> Unit, + modifier: Modifier = Modifier, + diagnosticListener: WorkflowDiagnosticListener? = null +) { + WorkflowContainer(workflow, props, onOutput, modifier, diagnosticListener) { rendering -> + rendering.render(viewEnvironment) + } +} + +/** + * Render a [Workflow]'s renderings. + * + * When this function is first composed it will start a new runtime. This runtime will be restarted + * any time [workflow], [diagnosticListener], or the `CoroutineContext` + * changes. The runtime will be cancelled when this function stops composing. + * + * @param workflow The [Workflow] to render. + * @param viewEnvironment The [ViewEnvironment] used to show the [ComposeRendering]s emitted by + * the workflow. + * @param onOutput A function that will be invoked any time the root workflow emits an output. + * @param diagnosticListener A [WorkflowDiagnosticListener] to configure on the runtime. + */ +@Composable inline fun WorkflowContainer( + workflow: Workflow, + viewEnvironment: ViewEnvironment, + noinline onOutput: (OutputT) -> Unit, + modifier: Modifier = Modifier, + diagnosticListener: WorkflowDiagnosticListener? = null +) { + WorkflowContainer(workflow, viewEnvironment, Unit, onOutput, modifier, diagnosticListener) +} + +/** + * Render a [Workflow]'s renderings. + * + * When this function is first composed it will start a new runtime. This runtime will be restarted + * any time [workflow], [diagnosticListener], or the `CoroutineContext` + * changes. The runtime will be cancelled when this function stops composing. + * + * @param workflow The [Workflow] to render. + * @param viewEnvironment The [ViewEnvironment] used to show the [ComposeRendering]s emitted by + * the workflow. + * @param props The props to render the root workflow with. If this value changes between calls, + * the workflow runtime will re-render with the new props. + * @param diagnosticListener A [WorkflowDiagnosticListener] to configure on the runtime. + */ +@Composable inline fun WorkflowContainer( + workflow: Workflow, + viewEnvironment: ViewEnvironment, + props: PropsT, + modifier: Modifier = Modifier, + diagnosticListener: WorkflowDiagnosticListener? = null +) { + WorkflowContainer(workflow, viewEnvironment, props, {}, modifier, diagnosticListener) +} + +/** + * Render a [Workflow]'s renderings. + * + * When this function is first composed it will start a new runtime. This runtime will be restarted + * any time [workflow], [diagnosticListener], or the `CoroutineContext` + * changes. The runtime will be cancelled when this function stops composing. + * + * @param workflow The [Workflow] to render. + * @param viewEnvironment The [ViewEnvironment] used to show the [ComposeRendering]s emitted by + * the workflow. + * @param diagnosticListener A [WorkflowDiagnosticListener] to configure on the runtime. + */ +@Composable inline fun WorkflowContainer( + workflow: Workflow, + viewEnvironment: ViewEnvironment, + modifier: Modifier = Modifier, + diagnosticListener: WorkflowDiagnosticListener? = null +) { + WorkflowContainer(workflow, viewEnvironment, Unit, {}, modifier, diagnosticListener) +} + +/** + * Internal version of [WorkflowContainer] that accepts extra parameters for testing. + */ +@VisibleForTesting(otherwise = PRIVATE) +@Composable internal fun WorkflowContainerImpl( + workflow: Workflow, + props: PropsT, + onOutput: (OutputT) -> Unit, + modifier: Modifier = Modifier, + diagnosticListener: WorkflowDiagnosticListener? = null, + snapshotKey: String? = null, + content: @Composable() (rendering: RenderingT) -> Unit +) { + @Suppress("DEPRECATION") + val rendering = renderAsState( + workflow, props, onOutput, CoroutineContextAmbient.current, diagnosticListener, snapshotKey + ) + + Box(modifier = modifier) { + content(rendering.value) + } +} + +/** + * @param snapshotKey Allows tests to pass in a custom key to use to save/restore the snapshot from + * the [UiSavedStateRegistryAmbient]. If null, will use the default key based on source location. + */ +@Composable private fun renderAsState( + @Pivotal workflow: Workflow, + props: PropsT, + onOutput: (OutputT) -> Unit, + @Pivotal coroutineContext: CoroutineContext, + @Pivotal diagnosticListener: WorkflowDiagnosticListener?, + snapshotKey: String? +): State { + // This can be a StateFlow once coroutines is upgraded to 1.3.6. + val propsChannel = remember { Channel(capacity = CONFLATED) } + propsChannel.offer(props) + + // Need a mutable holder for onOutput so the outputs subscriber created in the onActive block + // will always be able to see the latest value. + val outputCallback = remember { OutputCallback(onOutput) } + outputCallback.onOutput = onOutput + + val renderingState = state { null } + val snapshotState = savedInstanceState(key = snapshotKey, saver = SnapshotSaver) { null } + + // We can't use onActive/on(Pre)Commit because they won't run their callback until after this + // function returns, and we need to run this immediately so we get the rendering synchronously. + val workflowScope = remember { + val coroutineScope = CoroutineScope(coroutineContext + Dispatchers.Main.immediate) + val propsFlow = propsChannel.consumeAsFlow() + .distinctUntilChanged() + + launchWorkflowIn(coroutineScope, workflow, propsFlow, snapshotState.value) { session -> + session.diagnosticListener = diagnosticListener + + // Don't call onOutput directly, since out captured reference won't be changed if the + // if a different argument is passed to observeWorkflow. + session.outputs.onEach { outputCallback.onOutput(it) } + .launchIn(this) + + session.renderingsAndSnapshots + .onEach { (rendering, snapshot) -> + renderingState.value = rendering + snapshotState.value = snapshot + } + .launchIn(this) + } + + return@remember coroutineScope + } + + onDispose { + workflowScope.cancel() + } + + // The value is guaranteed to be set before returning, so this cast is fine. + @Suppress("UNCHECKED_CAST") + return renderingState as State +} + +private object SnapshotSaver : Saver { + override fun SaverScope.save(value: Snapshot?): ByteArray { + return value?.bytes?.toByteArray() ?: ByteArray(0) + } + + override fun restore(value: ByteArray): Snapshot? { + return value.takeUnless { it.isEmpty() } + ?.let { bytes -> Snapshot.of(ByteString.of(*bytes)) } + } +} + +private class OutputCallback(var onOutput: (OutputT) -> Unit) diff --git a/samples/hello-compose/build.gradle.kts b/samples/hello-compose/build.gradle.kts new file mode 100644 index 0000000000..bf0b4fb0dc --- /dev/null +++ b/samples/hello-compose/build.gradle.kts @@ -0,0 +1,48 @@ +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +/* + * Copyright 2020 Square Inc. + * + * 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. + */ + +plugins { + id("com.android.application") + kotlin("android") +} + +apply(from = rootProject.file(".buildscript/configure-android-defaults.gradle")) +apply(from = rootProject.file(".buildscript/android-sample-app.gradle")) +apply(from = rootProject.file(".buildscript/android-ui-tests.gradle")) + +android { + defaultConfig { + applicationId = "com.squareup.sample.hellocompose" + minSdkVersion(25) + } +} + +apply(from = rootProject.file(".buildscript/configure-compose.gradle")) +tasks.withType { + kotlinOptions.apiVersion = "1.3" +} + +dependencies { + implementation(project(":core-compose")) + implementation(Dependencies.AndroidX.appcompat) + implementation(Dependencies.Compose.layout) + implementation(Dependencies.Compose.material) + implementation(Dependencies.Compose.foundation) + + debugImplementation(project(":compose-tooling")) +} diff --git a/samples/hello-compose/src/main/AndroidManifest.xml b/samples/hello-compose/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..14ee442651 --- /dev/null +++ b/samples/hello-compose/src/main/AndroidManifest.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + diff --git a/samples/hello-compose/src/main/java/com/squareup/sample/hellocompose/App.kt b/samples/hello-compose/src/main/java/com/squareup/sample/hellocompose/App.kt new file mode 100644 index 0000000000..c6676a2430 --- /dev/null +++ b/samples/hello-compose/src/main/java/com/squareup/sample/hellocompose/App.kt @@ -0,0 +1,57 @@ +/* + * Copyright 2020 Square Inc. + * + * 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.squareup.sample.hellocompose + +import androidx.compose.Composable +import androidx.ui.core.Modifier +import androidx.ui.foundation.drawBorder +import androidx.ui.foundation.shape.corner.RoundedCornerShape +import androidx.ui.graphics.Color +import androidx.ui.material.MaterialTheme +import androidx.ui.tooling.preview.Preview +import androidx.ui.unit.dp +import com.squareup.workflow.diagnostic.SimpleLoggingDiagnosticListener +import com.squareup.workflow.ui.ViewEnvironment +import com.squareup.workflow.ui.ViewRegistry +import com.squareup.workflow.ui.compose.WorkflowContainer +import com.squareup.workflow.ui.compose.showRendering + +private val viewRegistry = ViewRegistry(HelloBinding) +private val viewEnvironment = ViewEnvironment(viewRegistry) + +@Composable fun App() { + WorkflowContainer( + workflow = HelloWorkflow, + diagnosticListener = SimpleLoggingDiagnosticListener() + ) { rendering -> + MaterialTheme { + viewEnvironment.showRendering( + rendering, + modifier = Modifier.drawBorder( + shape = RoundedCornerShape(10.dp), + size = 10.dp, + color = Color.Magenta + ) + ) + } + } +} + +// This preview is broken in dev10, Compose runtime throws an ArrayIndexOutOfBoundsException. +@Preview(showBackground = true) +@Composable private fun AppPreview() { + App() +} diff --git a/samples/hello-compose/src/main/java/com/squareup/sample/hellocompose/HelloBinding.kt b/samples/hello-compose/src/main/java/com/squareup/sample/hellocompose/HelloBinding.kt new file mode 100644 index 0000000000..277c0590f2 --- /dev/null +++ b/samples/hello-compose/src/main/java/com/squareup/sample/hellocompose/HelloBinding.kt @@ -0,0 +1,36 @@ +/* + * Copyright 2020 Square Inc. + * + * 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.squareup.sample.hellocompose + +import androidx.ui.core.Alignment +import androidx.ui.core.Modifier +import androidx.ui.foundation.Clickable +import androidx.ui.foundation.Text +import androidx.ui.layout.fillMaxSize +import androidx.ui.layout.wrapContentSize +import androidx.ui.material.ripple.ripple +import com.squareup.sample.hellocompose.HelloWorkflow.Rendering +import com.squareup.workflow.ui.compose.bindCompose + +val HelloBinding = bindCompose { rendering, _ -> + Clickable( + onClick = { rendering.onClick() }, + modifier = Modifier.ripple(bounded = true) + .fillMaxSize() + ) { + Text(rendering.message, modifier = Modifier.wrapContentSize(Alignment.Center)) + } +} diff --git a/samples/hello-compose/src/main/java/com/squareup/sample/hellocompose/HelloComposeActivity.kt b/samples/hello-compose/src/main/java/com/squareup/sample/hellocompose/HelloComposeActivity.kt new file mode 100644 index 0000000000..2f9058bc24 --- /dev/null +++ b/samples/hello-compose/src/main/java/com/squareup/sample/hellocompose/HelloComposeActivity.kt @@ -0,0 +1,29 @@ +/* + * Copyright 2020 Square Inc. + * + * 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.squareup.sample.hellocompose + +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import androidx.ui.core.setContent + +class HelloComposeActivity : AppCompatActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContent { + App() + } + } +} diff --git a/samples/hello-compose/src/main/java/com/squareup/sample/hellocompose/HelloWorkflow.kt b/samples/hello-compose/src/main/java/com/squareup/sample/hellocompose/HelloWorkflow.kt new file mode 100644 index 0000000000..84f4bd651e --- /dev/null +++ b/samples/hello-compose/src/main/java/com/squareup/sample/hellocompose/HelloWorkflow.kt @@ -0,0 +1,64 @@ +/* + * Copyright 2020 Square Inc. + * + * 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.squareup.sample.hellocompose + +import com.squareup.sample.hellocompose.HelloWorkflow.Rendering +import com.squareup.sample.hellocompose.HelloWorkflow.State +import com.squareup.sample.hellocompose.HelloWorkflow.State.Goodbye +import com.squareup.sample.hellocompose.HelloWorkflow.State.Hello +import com.squareup.workflow.RenderContext +import com.squareup.workflow.Snapshot +import com.squareup.workflow.StatefulWorkflow +import com.squareup.workflow.action +import com.squareup.workflow.parse + +object HelloWorkflow : StatefulWorkflow() { + enum class State { + Hello, + Goodbye; + + fun theOtherState(): State = when (this) { + Hello -> Goodbye + Goodbye -> Hello + } + } + + data class Rendering( + val message: String, + val onClick: () -> Unit + ) + + private val helloAction = action { + nextState = nextState.theOtherState() + } + + override fun initialState( + props: Unit, + snapshot: Snapshot? + ): State = snapshot?.bytes?.parse { source -> if (source.readInt() == 1) Hello else Goodbye } + ?: Hello + + override fun render( + props: Unit, + state: State, + context: RenderContext + ): Rendering = Rendering( + message = state.name, + onClick = { context.actionSink.send(helloAction) } + ) + + override fun snapshotState(state: State): Snapshot = Snapshot.of(if (state == Hello) 1 else 0) +} diff --git a/samples/hello-compose/src/main/res/values/strings.xml b/samples/hello-compose/src/main/res/values/strings.xml new file mode 100644 index 0000000000..e83d220934 --- /dev/null +++ b/samples/hello-compose/src/main/res/values/strings.xml @@ -0,0 +1,18 @@ + + + Hello Compose + diff --git a/samples/hello-compose/src/main/res/values/styles.xml b/samples/hello-compose/src/main/res/values/styles.xml new file mode 100644 index 0000000000..1c37bd6d5b --- /dev/null +++ b/samples/hello-compose/src/main/res/values/styles.xml @@ -0,0 +1,23 @@ + + + + + + + diff --git a/settings.gradle.kts b/settings.gradle.kts index ce3c6763e6..a273408d87 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -18,6 +18,7 @@ rootProject.name = "workflow-compose" include( ":compose-tooling", ":core-compose", + ":samples:hello-compose", ":samples:hello-compose-binding", ":samples:hello-compose-rendering", ":samples:nested-renderings" From debc500d6dfc4f9de38d801f0c8adec3391688cf Mon Sep 17 00:00:00 2001 From: Zach Klippenstein Date: Fri, 15 May 2020 16:31:48 -0700 Subject: [PATCH 14/67] Tidy up the package structure. This change reorganizes some of the wrapper logic to live closer to related code, and moves a bunch of internal-only helper code into an internal package to make the public API more browseable. --- compose-tooling/api/compose-tooling.api | 4 +- .../tooling/PreviewComposeWorkflowTest.kt | 2 +- .../ui/compose/tooling/ComposeWorkflows.kt | 2 +- core-compose/api/core-compose.api | 36 ++++++++-------- .../ui/compose/ComposeViewFactoryRootTest.kt | 1 + .../{ => ui}/compose/ComposeRendering.kt | 7 ++- .../workflow/ui/compose/ComposeViewFactory.kt | 3 ++ .../{ => ui}/compose/ComposeWorkflow.kt | 3 +- .../workflow/ui/compose/ViewEnvironments.kt | 1 + .../workflow/ui/compose/WorkflowContainer.kt | 1 - .../compose/{ => internal}/ComposeSupport.kt | 12 +++--- .../compose/internal}/ComposeWorkflowImpl.kt | 6 ++- .../{ => internal}/CompositionContinuation.kt | 2 +- .../SafeComposeViewFactoryRoot.kt | 3 +- .../compose/{ => internal}/ViewFactories.kt | 5 ++- .../compose/{ => internal}/ViewRegistries.kt | 2 +- .../HelloComposeRenderingActivity.kt | 2 +- .../HelloRenderingWorkflow.kt | 2 +- .../hellocomposerendering/HelloWorkflow.kt | 2 +- .../ui/compose/tooling/ComposeWorkflows.kt | 43 ------------------- 20 files changed, 52 insertions(+), 87 deletions(-) rename core-compose/src/main/java/com/squareup/workflow/{ => ui}/compose/ComposeRendering.kt (86%) rename core-compose/src/main/java/com/squareup/workflow/{ => ui}/compose/ComposeWorkflow.kt (96%) rename core-compose/src/main/java/com/squareup/workflow/ui/compose/{ => internal}/ComposeSupport.kt (90%) rename core-compose/src/main/java/com/squareup/workflow/{compose => ui/compose/internal}/ComposeWorkflowImpl.kt (92%) rename core-compose/src/main/java/com/squareup/workflow/ui/compose/{ => internal}/CompositionContinuation.kt (97%) rename core-compose/src/main/java/com/squareup/workflow/ui/compose/{ => internal}/SafeComposeViewFactoryRoot.kt (92%) rename core-compose/src/main/java/com/squareup/workflow/ui/compose/{ => internal}/ViewFactories.kt (94%) rename core-compose/src/main/java/com/squareup/workflow/ui/compose/{ => internal}/ViewRegistries.kt (96%) delete mode 100644 samples/nested-renderings/src/main/res/values/java/com/squareup/workflow/ui/compose/tooling/ComposeWorkflows.kt diff --git a/compose-tooling/api/compose-tooling.api b/compose-tooling/api/compose-tooling.api index 685ec8306d..c283e9b945 100644 --- a/compose-tooling/api/compose-tooling.api +++ b/compose-tooling/api/compose-tooling.api @@ -6,8 +6,8 @@ public final class com/squareup/workflow/ui/compose/tooling/BuildConfig { } public final class com/squareup/workflow/ui/compose/tooling/ComposeWorkflowsKt { - public static final fun preview (Lcom/squareup/workflow/compose/ComposeWorkflow;Ljava/lang/Object;Landroidx/ui/core/Modifier;Landroidx/ui/core/Modifier;Lkotlin/jvm/functions/Function1;Landroidx/compose/Composer;)V - public static synthetic fun preview$default (Lcom/squareup/workflow/compose/ComposeWorkflow;Ljava/lang/Object;Landroidx/ui/core/Modifier;Landroidx/ui/core/Modifier;Lkotlin/jvm/functions/Function1;Landroidx/compose/Composer;ILjava/lang/Object;)V + public static final fun preview (Lcom/squareup/workflow/ui/compose/ComposeWorkflow;Ljava/lang/Object;Landroidx/ui/core/Modifier;Landroidx/ui/core/Modifier;Lkotlin/jvm/functions/Function1;Landroidx/compose/Composer;)V + public static synthetic fun preview$default (Lcom/squareup/workflow/ui/compose/ComposeWorkflow;Ljava/lang/Object;Landroidx/ui/core/Modifier;Landroidx/ui/core/Modifier;Lkotlin/jvm/functions/Function1;Landroidx/compose/Composer;ILjava/lang/Object;)V } public final class com/squareup/workflow/ui/compose/tooling/ViewFactoriesKt { diff --git a/compose-tooling/src/androidTest/java/com/squareup/workflow/ui/compose/tooling/PreviewComposeWorkflowTest.kt b/compose-tooling/src/androidTest/java/com/squareup/workflow/ui/compose/tooling/PreviewComposeWorkflowTest.kt index 7feab89d93..8b9b542ec7 100644 --- a/compose-tooling/src/androidTest/java/com/squareup/workflow/ui/compose/tooling/PreviewComposeWorkflowTest.kt +++ b/compose-tooling/src/androidTest/java/com/squareup/workflow/ui/compose/tooling/PreviewComposeWorkflowTest.kt @@ -31,7 +31,7 @@ import androidx.ui.test.findByText import androidx.ui.tooling.preview.Preview import androidx.ui.unit.dp import com.squareup.workflow.Workflow -import com.squareup.workflow.compose.composed +import com.squareup.workflow.ui.compose.composed import com.squareup.workflow.ui.ViewEnvironmentKey import com.squareup.workflow.ui.compose.showRendering import org.junit.Rule diff --git a/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/ComposeWorkflows.kt b/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/ComposeWorkflows.kt index c01324edca..97be59805e 100644 --- a/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/ComposeWorkflows.kt +++ b/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/ComposeWorkflows.kt @@ -21,7 +21,7 @@ import androidx.compose.Composable import androidx.ui.core.Modifier import androidx.ui.foundation.Box import com.squareup.workflow.Sink -import com.squareup.workflow.compose.ComposeWorkflow +import com.squareup.workflow.ui.compose.ComposeWorkflow import com.squareup.workflow.ui.ViewEnvironment import com.squareup.workflow.ui.ViewFactory import com.squareup.workflow.ui.ViewRegistry diff --git a/core-compose/api/core-compose.api b/core-compose/api/core-compose.api index 096578eb36..2005694d51 100644 --- a/core-compose/api/core-compose.api +++ b/core-compose/api/core-compose.api @@ -1,26 +1,12 @@ -public final class com/squareup/workflow/compose/ComposeRendering { - public static final field Companion Lcom/squareup/workflow/compose/ComposeRendering$Companion; +public final class com/squareup/workflow/ui/compose/ComposeRendering { + public static final field Companion Lcom/squareup/workflow/ui/compose/ComposeRendering$Companion; public static final fun ()V public fun (Lkotlin/jvm/functions/Function2;)V } -public final class com/squareup/workflow/compose/ComposeRendering$Companion { +public final class com/squareup/workflow/ui/compose/ComposeRendering$Companion { public final fun getFactory ()Lcom/squareup/workflow/ui/ViewFactory; - public final fun getNoopRendering ()Lcom/squareup/workflow/compose/ComposeRendering; -} - -public abstract class com/squareup/workflow/compose/ComposeWorkflow : com/squareup/workflow/Workflow { - public fun ()V - public fun asStatefulWorkflow ()Lcom/squareup/workflow/StatefulWorkflow; - public abstract fun render (Ljava/lang/Object;Lcom/squareup/workflow/Sink;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/compose/Composer;)V -} - -public final class com/squareup/workflow/compose/ComposeWorkflowKt { - public static final fun composed (Lcom/squareup/workflow/Workflow$Companion;Lkotlin/jvm/functions/Function4;)Lcom/squareup/workflow/compose/ComposeWorkflow; -} - -public final class com/squareup/workflow/ui/compose/ComposeSupportKt { - public static final fun ()V + public final fun getNoopRendering ()Lcom/squareup/workflow/ui/compose/ComposeRendering; } public final class com/squareup/workflow/ui/compose/ComposeViewFactory : com/squareup/workflow/ui/ViewFactory { @@ -46,6 +32,16 @@ public final class com/squareup/workflow/ui/compose/ComposeViewFactoryRootKt { public static final fun withComposeViewFactoryRoot (Lcom/squareup/workflow/ui/ViewEnvironment;Lkotlin/jvm/functions/Function2;)Lcom/squareup/workflow/ui/ViewEnvironment; } +public abstract class com/squareup/workflow/ui/compose/ComposeWorkflow : com/squareup/workflow/Workflow { + public fun ()V + public fun asStatefulWorkflow ()Lcom/squareup/workflow/StatefulWorkflow; + public abstract fun render (Ljava/lang/Object;Lcom/squareup/workflow/Sink;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/compose/Composer;)V +} + +public final class com/squareup/workflow/ui/compose/ComposeWorkflowKt { + public static final fun composed (Lcom/squareup/workflow/Workflow$Companion;Lkotlin/jvm/functions/Function4;)Lcom/squareup/workflow/ui/compose/ComposeWorkflow; +} + public final class com/squareup/workflow/ui/compose/ViewEnvironmentsKt { public static final fun showRendering (Lcom/squareup/workflow/ui/ViewEnvironment;Ljava/lang/Object;Landroidx/ui/core/Modifier;Landroidx/compose/Composer;)V public static synthetic fun showRendering$default (Lcom/squareup/workflow/ui/ViewEnvironment;Ljava/lang/Object;Landroidx/ui/core/Modifier;Landroidx/compose/Composer;ILjava/lang/Object;)V @@ -70,6 +66,10 @@ public final class com/squareup/workflow/ui/compose/WorkflowContainerKt { public static synthetic fun WorkflowContainer$default (Lcom/squareup/workflow/Workflow;Lkotlin/jvm/functions/Function1;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Lkotlin/jvm/functions/Function2;Landroidx/compose/Composer;ILjava/lang/Object;)V } +public final class com/squareup/workflow/ui/compose/internal/ComposeSupportKt { + public static final fun ()V +} + public final class com/squareup/workflow/ui/core/compose/BuildConfig { public static final field BUILD_TYPE Ljava/lang/String; public static final field DEBUG Z diff --git a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/ComposeViewFactoryRootTest.kt b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/ComposeViewFactoryRootTest.kt index be85cbcede..e27c64551f 100644 --- a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/ComposeViewFactoryRootTest.kt +++ b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/ComposeViewFactoryRootTest.kt @@ -23,6 +23,7 @@ import androidx.ui.test.assertIsDisplayed import androidx.ui.test.createComposeRule import androidx.ui.test.findByText import com.google.common.truth.Truth.assertThat +import com.squareup.workflow.ui.compose.internal.SafeComposeViewFactoryRoot import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith diff --git a/core-compose/src/main/java/com/squareup/workflow/compose/ComposeRendering.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeRendering.kt similarity index 86% rename from core-compose/src/main/java/com/squareup/workflow/compose/ComposeRendering.kt rename to core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeRendering.kt index 13c303e3f4..466fcafd51 100644 --- a/core-compose/src/main/java/com/squareup/workflow/compose/ComposeRendering.kt +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeRendering.kt @@ -15,14 +15,13 @@ */ @file:Suppress("RemoveEmptyParenthesesFromAnnotationEntry") -package com.squareup.workflow.compose +package com.squareup.workflow.ui.compose import androidx.compose.Composable -import com.squareup.workflow.compose.ComposeRendering.Companion.Factory -import com.squareup.workflow.compose.ComposeRendering.Companion.NoopRendering +import com.squareup.workflow.ui.compose.ComposeRendering.Companion.Factory +import com.squareup.workflow.ui.compose.ComposeRendering.Companion.NoopRendering import com.squareup.workflow.ui.ViewEnvironment import com.squareup.workflow.ui.ViewFactory -import com.squareup.workflow.ui.compose.bindCompose /** * A workflow rendering that renders itself using a [Composable] function. diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactory.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactory.kt index aa021169da..2afb6797d9 100644 --- a/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactory.kt +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactory.kt @@ -31,6 +31,9 @@ import androidx.ui.core.setContent import com.squareup.workflow.ui.ViewEnvironment import com.squareup.workflow.ui.ViewFactory import com.squareup.workflow.ui.bindShowRendering +import com.squareup.workflow.ui.compose.internal.CompositionContinuation +import com.squareup.workflow.ui.compose.internal.SafeComposeViewFactoryRoot +import com.squareup.workflow.ui.compose.internal.setContent import kotlin.reflect.KClass /** diff --git a/core-compose/src/main/java/com/squareup/workflow/compose/ComposeWorkflow.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeWorkflow.kt similarity index 96% rename from core-compose/src/main/java/com/squareup/workflow/compose/ComposeWorkflow.kt rename to core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeWorkflow.kt index 3f435d222d..1a30bcb6f0 100644 --- a/core-compose/src/main/java/com/squareup/workflow/compose/ComposeWorkflow.kt +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeWorkflow.kt @@ -15,13 +15,14 @@ */ @file:Suppress("RemoveEmptyParenthesesFromAnnotationEntry") -package com.squareup.workflow.compose +package com.squareup.workflow.ui.compose import androidx.compose.Composable import com.squareup.workflow.Sink import com.squareup.workflow.StatefulWorkflow import com.squareup.workflow.Workflow import com.squareup.workflow.ui.ViewEnvironment +import com.squareup.workflow.ui.compose.internal.ComposeWorkflowImpl /** * A stateless [Workflow][com.squareup.workflow.Workflow] that [renders][render] itself as diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/ViewEnvironments.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/ViewEnvironments.kt index e7419396a5..cdb618f5f2 100644 --- a/core-compose/src/main/java/com/squareup/workflow/ui/compose/ViewEnvironments.kt +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/ViewEnvironments.kt @@ -20,6 +20,7 @@ import androidx.compose.remember import androidx.ui.core.Modifier import com.squareup.workflow.ui.ViewEnvironment import com.squareup.workflow.ui.ViewRegistry +import com.squareup.workflow.ui.compose.internal.showRendering /** * Renders [rendering] into the composition using this [ViewEnvironment]'s diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/WorkflowContainer.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/WorkflowContainer.kt index c7900697a9..643af54f20 100644 --- a/core-compose/src/main/java/com/squareup/workflow/ui/compose/WorkflowContainer.kt +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/WorkflowContainer.kt @@ -40,7 +40,6 @@ import androidx.ui.savedinstancestate.UiSavedStateRegistryAmbient import androidx.ui.savedinstancestate.savedInstanceState import com.squareup.workflow.Snapshot import com.squareup.workflow.Workflow -import com.squareup.workflow.compose.ComposeRendering import com.squareup.workflow.diagnostic.WorkflowDiagnosticListener import com.squareup.workflow.launchWorkflowIn import com.squareup.workflow.ui.ViewEnvironment diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeSupport.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ComposeSupport.kt similarity index 90% rename from core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeSupport.kt rename to core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ComposeSupport.kt index d195a269f3..506217277e 100644 --- a/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeSupport.kt +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ComposeSupport.kt @@ -15,7 +15,7 @@ */ @file:Suppress("RemoveEmptyParenthesesFromAnnotationEntry") -package com.squareup.workflow.ui.compose +package com.squareup.workflow.ui.compose.internal import android.content.Context import android.view.View @@ -27,11 +27,11 @@ import androidx.compose.Recomposer import androidx.compose.compositionFor import androidx.lifecycle.LifecycleOwner import androidx.ui.node.UiComposer -import com.squareup.workflow.ui.compose.ReflectionSupport.ANDROID_OWNER_CLASS -import com.squareup.workflow.ui.compose.ReflectionSupport.androidOwnerView -import com.squareup.workflow.ui.compose.ReflectionSupport.createOwner -import com.squareup.workflow.ui.compose.ReflectionSupport.createWrappedContent -import com.squareup.workflow.ui.compose.ReflectionSupport.ownerRoot +import com.squareup.workflow.ui.compose.internal.ReflectionSupport.ANDROID_OWNER_CLASS +import com.squareup.workflow.ui.compose.internal.ReflectionSupport.androidOwnerView +import com.squareup.workflow.ui.compose.internal.ReflectionSupport.createOwner +import com.squareup.workflow.ui.compose.internal.ReflectionSupport.createWrappedContent +import com.squareup.workflow.ui.compose.internal.ReflectionSupport.ownerRoot import com.squareup.workflow.ui.core.compose.R private typealias AndroidOwner = Any diff --git a/core-compose/src/main/java/com/squareup/workflow/compose/ComposeWorkflowImpl.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ComposeWorkflowImpl.kt similarity index 92% rename from core-compose/src/main/java/com/squareup/workflow/compose/ComposeWorkflowImpl.kt rename to core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ComposeWorkflowImpl.kt index cdd4eca9c3..efdb89fb16 100644 --- a/core-compose/src/main/java/com/squareup/workflow/compose/ComposeWorkflowImpl.kt +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ComposeWorkflowImpl.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.squareup.workflow.compose +package com.squareup.workflow.ui.compose.internal import androidx.compose.MutableState import androidx.compose.StructurallyEqual @@ -23,8 +23,10 @@ import com.squareup.workflow.Sink import com.squareup.workflow.Snapshot import com.squareup.workflow.StatefulWorkflow import com.squareup.workflow.action -import com.squareup.workflow.compose.ComposeWorkflowImpl.State +import com.squareup.workflow.ui.compose.internal.ComposeWorkflowImpl.State import com.squareup.workflow.contraMap +import com.squareup.workflow.ui.compose.ComposeRendering +import com.squareup.workflow.ui.compose.ComposeWorkflow internal class ComposeWorkflowImpl( private val workflow: ComposeWorkflow diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/CompositionContinuation.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/CompositionContinuation.kt similarity index 97% rename from core-compose/src/main/java/com/squareup/workflow/ui/compose/CompositionContinuation.kt rename to core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/CompositionContinuation.kt index 4f7980e3b7..4d789b9a53 100644 --- a/core-compose/src/main/java/com/squareup/workflow/ui/compose/CompositionContinuation.kt +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/CompositionContinuation.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.squareup.workflow.ui.compose +package com.squareup.workflow.ui.compose.internal import androidx.compose.Composable import androidx.compose.CompositionReference diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/SafeComposeViewFactoryRoot.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/SafeComposeViewFactoryRoot.kt similarity index 92% rename from core-compose/src/main/java/com/squareup/workflow/ui/compose/SafeComposeViewFactoryRoot.kt rename to core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/SafeComposeViewFactoryRoot.kt index 804b71550e..c2460be208 100644 --- a/core-compose/src/main/java/com/squareup/workflow/ui/compose/SafeComposeViewFactoryRoot.kt +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/SafeComposeViewFactoryRoot.kt @@ -15,9 +15,10 @@ */ @file:Suppress("RemoveEmptyParenthesesFromAnnotationEntry") -package com.squareup.workflow.ui.compose +package com.squareup.workflow.ui.compose.internal import androidx.compose.Composable +import com.squareup.workflow.ui.compose.ComposeViewFactoryRoot /** * [ComposeViewFactoryRoot] that asserts that the [wrap] method invokes its children parameter diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/ViewFactories.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ViewFactories.kt similarity index 94% rename from core-compose/src/main/java/com/squareup/workflow/ui/compose/ViewFactories.kt rename to core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ViewFactories.kt index 0dc5afbd2a..edd9290f51 100644 --- a/core-compose/src/main/java/com/squareup/workflow/ui/compose/ViewFactories.kt +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ViewFactories.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.squareup.workflow.ui.compose +package com.squareup.workflow.ui.compose.internal import android.content.Context import android.view.ViewGroup.LayoutParams.MATCH_PARENT @@ -24,7 +24,8 @@ import androidx.ui.foundation.Box import com.squareup.workflow.ui.ViewEnvironment import com.squareup.workflow.ui.ViewFactory import com.squareup.workflow.ui.WorkflowViewStub -import com.squareup.workflow.ui.compose.ComposableViewStubWrapper.Update +import com.squareup.workflow.ui.compose.ComposeViewFactory +import com.squareup.workflow.ui.compose.internal.ComposableViewStubWrapper.Update /** * Renders [rendering] into the composition using the `ViewRegistry` from the [ViewEnvironment] to diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/ViewRegistries.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ViewRegistries.kt similarity index 96% rename from core-compose/src/main/java/com/squareup/workflow/ui/compose/ViewRegistries.kt rename to core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ViewRegistries.kt index aec3b46843..80f6f2aa02 100644 --- a/core-compose/src/main/java/com/squareup/workflow/ui/compose/ViewRegistries.kt +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ViewRegistries.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.squareup.workflow.ui.compose +package com.squareup.workflow.ui.compose.internal import androidx.compose.Composable import androidx.compose.remember diff --git a/samples/hello-compose-rendering/src/main/java/com/squareup/sample/hellocomposerendering/HelloComposeRenderingActivity.kt b/samples/hello-compose-rendering/src/main/java/com/squareup/sample/hellocomposerendering/HelloComposeRenderingActivity.kt index 3bf820720e..2877316da1 100644 --- a/samples/hello-compose-rendering/src/main/java/com/squareup/sample/hellocomposerendering/HelloComposeRenderingActivity.kt +++ b/samples/hello-compose-rendering/src/main/java/com/squareup/sample/hellocomposerendering/HelloComposeRenderingActivity.kt @@ -17,7 +17,7 @@ package com.squareup.sample.hellocomposerendering import android.os.Bundle import androidx.appcompat.app.AppCompatActivity -import com.squareup.workflow.compose.ComposeRendering +import com.squareup.workflow.ui.compose.ComposeRendering import com.squareup.workflow.diagnostic.SimpleLoggingDiagnosticListener import com.squareup.workflow.ui.ViewRegistry import com.squareup.workflow.ui.WorkflowRunner diff --git a/samples/hello-compose-rendering/src/main/java/com/squareup/sample/hellocomposerendering/HelloRenderingWorkflow.kt b/samples/hello-compose-rendering/src/main/java/com/squareup/sample/hellocomposerendering/HelloRenderingWorkflow.kt index 1285fe1bc7..1cd1906cc2 100644 --- a/samples/hello-compose-rendering/src/main/java/com/squareup/sample/hellocomposerendering/HelloRenderingWorkflow.kt +++ b/samples/hello-compose-rendering/src/main/java/com/squareup/sample/hellocomposerendering/HelloRenderingWorkflow.kt @@ -27,7 +27,7 @@ import androidx.ui.material.ripple.ripple import androidx.ui.tooling.preview.Preview import com.squareup.sample.hellocomposerendering.HelloRenderingWorkflow.Toggle import com.squareup.workflow.Sink -import com.squareup.workflow.compose.ComposeWorkflow +import com.squareup.workflow.ui.compose.ComposeWorkflow import com.squareup.workflow.ui.ViewEnvironment import com.squareup.workflow.ui.compose.tooling.preview diff --git a/samples/hello-compose-rendering/src/main/java/com/squareup/sample/hellocomposerendering/HelloWorkflow.kt b/samples/hello-compose-rendering/src/main/java/com/squareup/sample/hellocomposerendering/HelloWorkflow.kt index 2d784cfc92..5654a75a4f 100644 --- a/samples/hello-compose-rendering/src/main/java/com/squareup/sample/hellocomposerendering/HelloWorkflow.kt +++ b/samples/hello-compose-rendering/src/main/java/com/squareup/sample/hellocomposerendering/HelloWorkflow.kt @@ -22,7 +22,7 @@ import com.squareup.workflow.RenderContext import com.squareup.workflow.Snapshot import com.squareup.workflow.StatefulWorkflow import com.squareup.workflow.action -import com.squareup.workflow.compose.ComposeRendering +import com.squareup.workflow.ui.compose.ComposeRendering import com.squareup.workflow.parse object HelloWorkflow : StatefulWorkflow() { diff --git a/samples/nested-renderings/src/main/res/values/java/com/squareup/workflow/ui/compose/tooling/ComposeWorkflows.kt b/samples/nested-renderings/src/main/res/values/java/com/squareup/workflow/ui/compose/tooling/ComposeWorkflows.kt deleted file mode 100644 index 85d104451c..0000000000 --- a/samples/nested-renderings/src/main/res/values/java/com/squareup/workflow/ui/compose/tooling/ComposeWorkflows.kt +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2020 Square Inc. - * - * 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.squareup.workflow.ui.compose.tooling - -import androidx.compose.Composable -import androidx.compose.Immutable -import com.squareup.workflow.Sink -import com.squareup.workflow.compose.ComposeWorkflow -import com.squareup.workflow.ui.ViewBinding - -/** - * Draws this [ComposeWorkflow] using a special preview `ViewRegistry`. - * - * The sink passed to [ComposeWorkflow.render] will be a no-op implementation, since previews can't - * process input. - * - * Use inside `@Preview` Composable functions. - */ -//@Composable fun ComposeWorkflow.preview( -// props: PropsT, -// stubBinding: ViewBinding = PreviewStubViewBinding -//) { -// val containerHints = PreviewContainerHints(stubBinding) -// render(props, NoopSink, containerHints) -//} - -//@Immutable -//private object NoopSink : Sink { -// override fun send(value: Any?) = Unit -//} From 56165a1c463df7ab5e009f08957f89fe082b062d Mon Sep 17 00:00:00 2001 From: Zach Klippenstein Date: Fri, 15 May 2020 18:27:56 -0700 Subject: [PATCH 15/67] Upgrade gradle to latest version. --- gradle/wrapper/gradle-wrapper.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index fd0c5a38e9..21e622da69 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.4-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.4.1-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From 84fa59076a67228a6cc26bbfebb05ebe3007abe5 Mon Sep 17 00:00:00 2001 From: Zach Klippenstein Date: Fri, 15 May 2020 18:04:41 -0700 Subject: [PATCH 16/67] Upgrade Compose to dev11. Input to legacy views hosted inside a composition seems to (partially) work now! The positions are a little off, but you can see buttons being clicked. There's a bug in our subcomposition infra that causes infinite rerenders, and prevents updates from actually showing up in legacy views, but I'll fix that in a follow- up. --- buildSrc/src/main/java/Dependencies.kt | 2 +- .../compose/tooling/PlaceholderViewFactory.kt | 75 +++++++++---------- .../nestedrenderings/NestedRenderingsTest.kt | 29 ++++--- .../ui/compose/tooling/ComposeWorkflows.kt | 43 ----------- 4 files changed, 54 insertions(+), 95 deletions(-) delete mode 100644 samples/nested-renderings/src/main/res/values/java/com/squareup/workflow/ui/compose/tooling/ComposeWorkflows.kt diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt index d15a07bc72..9ac3f8f256 100644 --- a/buildSrc/src/main/java/Dependencies.kt +++ b/buildSrc/src/main/java/Dependencies.kt @@ -19,7 +19,7 @@ import java.util.Locale.US import kotlin.reflect.full.declaredMembers object Versions { - const val compose = "0.1.0-dev10" + const val compose = "0.1.0-dev11" const val kotlin = "1.3.71" const val targetSdk = 29 const val workflow = "0.28.0" diff --git a/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/PlaceholderViewFactory.kt b/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/PlaceholderViewFactory.kt index 75056be8d8..fae90a780b 100644 --- a/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/PlaceholderViewFactory.kt +++ b/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/PlaceholderViewFactory.kt @@ -29,7 +29,9 @@ import androidx.ui.geometry.Offset import androidx.ui.graphics.Color import androidx.ui.graphics.Paint import androidx.ui.graphics.Shadow -import androidx.ui.graphics.withSave +import androidx.ui.graphics.painter.Stroke +import androidx.ui.graphics.painter.drawCanvas +import androidx.ui.graphics.painter.rotate import androidx.ui.graphics.withSaveLayer import androidx.ui.layout.fillMaxSize import androidx.ui.text.TextStyle @@ -49,17 +51,19 @@ import com.squareup.workflow.ui.compose.bindCompose internal fun placeholderViewFactory(modifier: Modifier): ViewFactory = bindCompose { rendering, _ -> Text( - modifier = modifier/*.fillMaxSize()*/ + modifier = modifier .clipToBounds() .drawBehind { - withSaveLayer(size.toRect(), Paint().apply { alpha = .2f }) { - drawRect(size.toRect(), Paint().apply { color = Color.Gray }) - drawCrossHatch( - color = Color.Red, - strokeWidth = 2.dp, - spaceWidth = 5.dp, - angle = 45f - ) + drawCanvas { canvas, size -> + canvas.withSaveLayer(size.toRect(), Paint().apply { alpha = .2f }) { + canvas.drawRect(size.toRect(), Paint().apply { color = Color.Gray }) + drawCrossHatch( + color = Color.Red, + strokeWidth = 2.dp, + spaceWidth = 5.dp, + angle = 45f + ) + } } }, text = rendering.toString(), @@ -74,25 +78,25 @@ internal fun placeholderViewFactory(modifier: Modifier): ViewFactory = @Preview(widthDp = 200, heightDp = 200) @Composable private fun PreviewStubViewBindingOnWhite() { Box(backgroundColor = Color.White) { - placeholderViewFactory(Modifier).preview( - rendering = "preview", - modifier = Modifier.fillMaxSize() - .drawBorder(size = 1.dp, color = Color.Red) - ) + PreviewStubBindingPreviewTemplate() } } @Preview(widthDp = 200, heightDp = 200) @Composable private fun PreviewStubViewBindingOnBlack() { Box(backgroundColor = Color.Black) { - placeholderViewFactory(Modifier).preview( - rendering = "preview", - modifier = Modifier.fillMaxSize() - .drawBorder(size = 1.dp, color = Color.Red) - ) + PreviewStubBindingPreviewTemplate() } } +@Composable private fun PreviewStubBindingPreviewTemplate() { + placeholderViewFactory(Modifier).preview( + rendering = "preview", + placeholderModifier = Modifier.fillMaxSize() + .drawBorder(size = 1.dp, color = Color.Red) + ) +} + private fun DrawScope.drawCrossHatch( color: Color, strokeWidth: Dp, @@ -109,34 +113,27 @@ private fun DrawScope.drawHatch( spaceWidth: Dp, angle: Float ) { - val strokeWidthPx = strokeWidth.toPx() - .value - val paint = Paint().also { - it.color = color.scaleColors(.5f) - it.strokeWidth = strokeWidthPx - } - - withSave { - val halfWidth = size.width.value / 2 - val halfHeight = size.height.value / 2 - translate(halfWidth, halfHeight) - rotate(angle) - translate(-halfWidth, -halfHeight) + val strokeWidthPx = strokeWidth.toPx().value + val spaceWidthPx = spaceWidth.toPx().value + val strokeColor = color.scaleColors(.5f) + val stroke = Stroke(width = strokeWidthPx) + rotate(angle) { // Draw outside our bounds to fill the space even when rotated. - val left = -size.width.value - val right = size.width.value * 2 - val top = -size.height.value - val bottom = size.height.value * 2 + val left = -size.width + val right = size.width * 2 + val top = -size.height + val bottom = size.height * 2 var y = top + strokeWidthPx * 2f while (y < bottom) { drawLine( + strokeColor, Offset(left, y), Offset(right, y), - paint + stroke = stroke ) - y += spaceWidth.toPx().value * 2 + y += spaceWidthPx * 2 } } } diff --git a/samples/nested-renderings/src/androidTest/java/com/squareup/sample/nestedrenderings/NestedRenderingsTest.kt b/samples/nested-renderings/src/androidTest/java/com/squareup/sample/nestedrenderings/NestedRenderingsTest.kt index bb8514c452..c2014b7449 100644 --- a/samples/nested-renderings/src/androidTest/java/com/squareup/sample/nestedrenderings/NestedRenderingsTest.kt +++ b/samples/nested-renderings/src/androidTest/java/com/squareup/sample/nestedrenderings/NestedRenderingsTest.kt @@ -16,12 +16,15 @@ package com.squareup.sample.nestedrenderings import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.ui.test.SemanticsNodeInteraction +import androidx.ui.test.SemanticsNodeInteractionCollection import androidx.ui.test.android.AndroidComposeTestRule +import androidx.ui.test.assertCountEquals import androidx.ui.test.assertIsDisplayed import androidx.ui.test.doClick import androidx.ui.test.findAllByText import androidx.ui.test.findByText -import com.google.common.truth.Truth.assertThat +import androidx.ui.test.last import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -35,24 +38,26 @@ class NestedRenderingsTest { @Rule @JvmField val composeRule = AndroidComposeTestRule() @Test fun childrenAreAddedAndRemoved() { - val resetButton = findByText("Reset") - findByText(ADD_BUTTON_TEXT) .assertIsDisplayed() .doClick() findAllByText(ADD_BUTTON_TEXT) - .also { addButtons -> - assertThat(addButtons).hasSize(2) - addButtons.forEach { it.doClick() } - } + .assertCountEquals(2) + .forEach { it.doClick() } findAllByText(ADD_BUTTON_TEXT) - .also { addButtons -> - assertThat(addButtons).hasSize(4) - } + .assertCountEquals(4) + + findAllByText("Reset").last() + .doClick() + findAllByText(ADD_BUTTON_TEXT).assertCountEquals(1) + } - resetButton.doClick() - assertThat(findAllByText(ADD_BUTTON_TEXT)).hasSize(1) + private fun SemanticsNodeInteractionCollection.forEach( + block: (SemanticsNodeInteraction) -> Unit + ) { + val count = fetchSemanticsNodes().size + for (i in 0 until count) block(get(i)) } } diff --git a/samples/nested-renderings/src/main/res/values/java/com/squareup/workflow/ui/compose/tooling/ComposeWorkflows.kt b/samples/nested-renderings/src/main/res/values/java/com/squareup/workflow/ui/compose/tooling/ComposeWorkflows.kt deleted file mode 100644 index 85d104451c..0000000000 --- a/samples/nested-renderings/src/main/res/values/java/com/squareup/workflow/ui/compose/tooling/ComposeWorkflows.kt +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2020 Square Inc. - * - * 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.squareup.workflow.ui.compose.tooling - -import androidx.compose.Composable -import androidx.compose.Immutable -import com.squareup.workflow.Sink -import com.squareup.workflow.compose.ComposeWorkflow -import com.squareup.workflow.ui.ViewBinding - -/** - * Draws this [ComposeWorkflow] using a special preview `ViewRegistry`. - * - * The sink passed to [ComposeWorkflow.render] will be a no-op implementation, since previews can't - * process input. - * - * Use inside `@Preview` Composable functions. - */ -//@Composable fun ComposeWorkflow.preview( -// props: PropsT, -// stubBinding: ViewBinding = PreviewStubViewBinding -//) { -// val containerHints = PreviewContainerHints(stubBinding) -// render(props, NoopSink, containerHints) -//} - -//@Immutable -//private object NoopSink : Sink { -// override fun send(value: Any?) = Unit -//} From acfbee02360106aaefcf522582370792b5d3d886 Mon Sep 17 00:00:00 2001 From: Zach Klippenstein Date: Fri, 15 May 2020 13:24:04 -0700 Subject: [PATCH 17/67] Make ViewFactory.showRendering function responsible for applying the ComposeViewFactoryRoot. This ensures that the root will be applied in all code paths: - Entering the Compose world through a `WorkflowViewStub`/`ComposeViewFactory`. - Already in Compose, showing a rendering from a `WorkflowContainer`. This change also ensures that if the `ComposeViewFactoryRoot` is changed above where it's applied, the new wrapper will be applied. Adds tests for this and other logic. Fixes #20. --- .github/workflows/kotlin.yml | 2 +- core-compose/api/core-compose.api | 1 + .../ui/compose/ComposeViewFactoryTest.kt | 78 ++++++++++ .../ui/compose/internal/ComposeSupportTest.kt | 96 ++++++++++++ .../internal/ComposeViewFactoryRootTest.kt | 142 ++++++++++++++++++ .../SafeComposeViewFactoryRootTest.kt} | 17 +-- .../ui/compose/internal/ViewFactoriesTest.kt | 61 ++++++++ .../workflow/ui/compose/ComposeViewFactory.kt | 43 ++---- .../ui/compose/ComposeViewFactoryRoot.kt | 37 +++++ .../ui/compose/internal/ComposeSupport.kt | 3 +- ...onContinuation.kt => ParentComposition.kt} | 45 ++++-- .../ui/compose/internal/ViewFactories.kt | 18 ++- 12 files changed, 480 insertions(+), 63 deletions(-) create mode 100644 core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/ComposeViewFactoryTest.kt create mode 100644 core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/internal/ComposeSupportTest.kt create mode 100644 core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/internal/ComposeViewFactoryRootTest.kt rename core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/{ComposeViewFactoryRootTest.kt => internal/SafeComposeViewFactoryRootTest.kt} (87%) create mode 100644 core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/internal/ViewFactoriesTest.kt rename core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/{CompositionContinuation.kt => ParentComposition.kt} (52%) diff --git a/.github/workflows/kotlin.yml b/.github/workflows/kotlin.yml index 227984829f..6e30bbe393 100644 --- a/.github/workflows/kotlin.yml +++ b/.github/workflows/kotlin.yml @@ -97,7 +97,7 @@ jobs: name: Instrumentation tests needs: assemble runs-on: macos-latest - timeout-minutes: 20 + timeout-minutes: 30 strategy: # Allow tests to continue on other devices if they fail on one device. fail-fast: false diff --git a/core-compose/api/core-compose.api b/core-compose/api/core-compose.api index 2005694d51..9e59342691 100644 --- a/core-compose/api/core-compose.api +++ b/core-compose/api/core-compose.api @@ -28,6 +28,7 @@ public final class com/squareup/workflow/ui/compose/ComposeViewFactoryRoot$Compa } public final class com/squareup/workflow/ui/compose/ComposeViewFactoryRootKt { + public static final fun ()V public static final fun ComposeViewFactoryRoot (Lkotlin/jvm/functions/Function2;)Lcom/squareup/workflow/ui/compose/ComposeViewFactoryRoot; public static final fun withComposeViewFactoryRoot (Lcom/squareup/workflow/ui/ViewEnvironment;Lkotlin/jvm/functions/Function2;)Lcom/squareup/workflow/ui/ViewEnvironment; } diff --git a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/ComposeViewFactoryTest.kt b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/ComposeViewFactoryTest.kt new file mode 100644 index 0000000000..510c4fbfae --- /dev/null +++ b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/ComposeViewFactoryTest.kt @@ -0,0 +1,78 @@ +/* + * Copyright 2020 Square Inc. + * + * 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.squareup.workflow.ui.compose + +import android.content.Context +import android.widget.FrameLayout +import androidx.compose.FrameManager +import androidx.compose.mutableStateOf +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.ui.foundation.Text +import androidx.ui.layout.Column +import androidx.ui.test.assertIsDisplayed +import androidx.ui.test.createComposeRule +import androidx.ui.test.findByText +import com.squareup.workflow.ui.ViewEnvironment +import com.squareup.workflow.ui.ViewRegistry +import com.squareup.workflow.ui.WorkflowViewStub +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class ComposeViewFactoryTest { + + @Rule @JvmField val composeRule = createComposeRule() + + @Test fun wrapsFactoryWithRoot() { + val wrapperText = mutableStateOf("one") + val viewEnvironment = ViewEnvironment(ViewRegistry(TestFactory)) + .withComposeViewFactoryRoot { content -> + Column { + Text(wrapperText.value) + content() + } + } + + composeRule.setContent { + // This is valid Compose code, but the IDE doesn't know that yet so it will show an + // unsuppressable error. + RootView(viewEnvironment = viewEnvironment) + } + + findByText("one\ntwo").assertIsDisplayed() + FrameManager.framed { + wrapperText.value = "ENO" + } + findByText("ENO\ntwo").assertIsDisplayed() + } + + private class RootView(context: Context) : FrameLayout(context) { + private val stub = WorkflowViewStub(context).also(::addView) + + fun setViewEnvironment(viewEnvironment: ViewEnvironment) { + stub.update(TestRendering("two"), viewEnvironment) + } + } + + private data class TestRendering(val text: String) + + private companion object { + val TestFactory = bindCompose { rendering, _ -> + Text(rendering.text) + } + } +} diff --git a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/internal/ComposeSupportTest.kt b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/internal/ComposeSupportTest.kt new file mode 100644 index 0000000000..0c6a10d114 --- /dev/null +++ b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/internal/ComposeSupportTest.kt @@ -0,0 +1,96 @@ +/* + * Copyright 2020 Square Inc. + * + * 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:Suppress("RemoveEmptyParenthesesFromAnnotationEntry") + +package com.squareup.workflow.ui.compose.internal + +import android.content.Context +import android.widget.FrameLayout +import androidx.compose.Composable +import androidx.compose.CompositionReference +import androidx.compose.FrameManager +import androidx.compose.Providers +import androidx.compose.Recomposer +import androidx.compose.ambientOf +import androidx.compose.compositionReference +import androidx.compose.currentComposer +import androidx.compose.mutableStateOf +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.ui.foundation.Text +import androidx.ui.test.assertIsDisplayed +import androidx.ui.test.createComposeRule +import androidx.ui.test.findByText +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class ComposeSupportTest { + + @Rule @JvmField val composeRule = createComposeRule() + + @Test fun ambientsPassThroughSubcomposition() { + composeRule.setContent { + TestComposable("foo") + } + + findByText("foo").assertIsDisplayed() + } + + @Test fun ambientChangesPassThroughSubcomposition() { + val ambientValue = mutableStateOf("foo") + composeRule.setContent { + TestComposable(ambientValue.value) + } + + findByText("foo").assertIsDisplayed() + FrameManager.framed { + ambientValue.value = "bar" + } + findByText("bar").assertIsDisplayed() + } + + @Composable private fun TestComposable(ambientValue: String) { + Providers(TestAmbient provides ambientValue) { + LegacyHostComposable { + Text(TestAmbient.current) + } + } + } + + @Composable private fun LegacyHostComposable(leafContent: @Composable() () -> Unit) { + val wormhole = Wormhole(currentComposer.recomposer, compositionReference(), leafContent) + // This is valid Compose code, but the IDE doesn't know that yet so it will show an + // unsuppressable error. + WormholeView(wormhole = wormhole) + } + + private class Wormhole( + val recomposer: Recomposer, + val parentReference: CompositionReference, + val childContent: @Composable() () -> Unit + ) + + private class WormholeView(context: Context) : FrameLayout(context) { + fun setWormhole(wormhole: Wormhole) { + setContent(wormhole.recomposer, wormhole.parentReference, wormhole.childContent) + } + } + + private companion object { + val TestAmbient = ambientOf { error("Ambient not provided") } + } +} diff --git a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/internal/ComposeViewFactoryRootTest.kt b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/internal/ComposeViewFactoryRootTest.kt new file mode 100644 index 0000000000..453c254571 --- /dev/null +++ b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/internal/ComposeViewFactoryRootTest.kt @@ -0,0 +1,142 @@ +/* + * Copyright 2020 Square Inc. + * + * 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.squareup.workflow.ui.compose.internal + +import androidx.compose.FrameManager +import androidx.compose.mutableStateOf +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.ui.foundation.Text +import androidx.ui.layout.Column +import androidx.ui.test.assertIsDisplayed +import androidx.ui.test.createComposeRule +import androidx.ui.test.findByText +import com.squareup.workflow.ui.ViewEnvironment +import com.squareup.workflow.ui.ViewRegistry +import com.squareup.workflow.ui.compose.withComposeViewFactoryRoot +import com.squareup.workflow.ui.compose.wrapWithRootIfNecessary +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class ComposeViewFactoryRootTest { + + @Rule @JvmField val composeRule = createComposeRule() + + @Test fun wrapWithRootIfNecessary_handlesNoRoot() { + val viewEnvironment = ViewEnvironment(ViewRegistry()) + + composeRule.setContent { + wrapWithRootIfNecessary(viewEnvironment) { + Text("foo") + } + } + + findByText("foo").assertIsDisplayed() + } + + @Test fun wrapWithRootIfNecessary_wrapsWhenNecessary() { + val viewEnvironment = ViewEnvironment(ViewRegistry()) + .withComposeViewFactoryRoot { content -> + Column { + Text("one") + content() + } + } + + composeRule.setContent { + wrapWithRootIfNecessary(viewEnvironment) { + Text("two") + } + } + + findByText("one\ntwo").assertIsDisplayed() + } + + @Test fun wrapWithRootIfNecessary_onlyWrapsOnce() { + val viewEnvironment = ViewEnvironment(ViewRegistry()) + .withComposeViewFactoryRoot { content -> + Column { + Text("one") + content() + } + } + + composeRule.setContent { + wrapWithRootIfNecessary(viewEnvironment) { + Text("two") + wrapWithRootIfNecessary(viewEnvironment) { + Text("three") + } + } + } + + findByText("one\ntwo\nthree").assertIsDisplayed() + } + + @Test fun wrapWithRootIfNecessary_seesUpdatesFromRootWrapper() { + val wrapperText = mutableStateOf("one") + val viewEnvironment = ViewEnvironment(ViewRegistry()) + .withComposeViewFactoryRoot { content -> + Column { + Text(wrapperText.value) + content() + } + } + + composeRule.setContent { + wrapWithRootIfNecessary(viewEnvironment) { + Text("two") + } + } + + findByText("one\ntwo").assertIsDisplayed() + FrameManager.framed { + wrapperText.value = "ENO" + } + findByText("ENO\ntwo").assertIsDisplayed() + } + + @Test fun wrapWithRootIfNecessary_rewrapsWhenDifferentRoot() { + val viewEnvironment1 = ViewEnvironment(ViewRegistry()) + .withComposeViewFactoryRoot { content -> + Column { + Text("one") + content() + } + } + val viewEnvironment2 = ViewEnvironment(ViewRegistry()) + .withComposeViewFactoryRoot { content -> + Column { + Text("ENO") + content() + } + } + val viewEnvironment = mutableStateOf(viewEnvironment1) + + composeRule.setContent { + wrapWithRootIfNecessary(viewEnvironment.value) { + Text("two") + } + } + + findByText("one\ntwo").assertIsDisplayed() + FrameManager.framed { + viewEnvironment.value = viewEnvironment2 + } + findByText("ENO\ntwo").assertIsDisplayed() + } +} diff --git a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/ComposeViewFactoryRootTest.kt b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/internal/SafeComposeViewFactoryRootTest.kt similarity index 87% rename from core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/ComposeViewFactoryRootTest.kt rename to core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/internal/SafeComposeViewFactoryRootTest.kt index e27c64551f..794caad872 100644 --- a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/ComposeViewFactoryRootTest.kt +++ b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/internal/SafeComposeViewFactoryRootTest.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.squareup.workflow.ui.compose +package com.squareup.workflow.ui.compose.internal import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.ui.foundation.Text @@ -23,16 +23,16 @@ import androidx.ui.test.assertIsDisplayed import androidx.ui.test.createComposeRule import androidx.ui.test.findByText import com.google.common.truth.Truth.assertThat -import com.squareup.workflow.ui.compose.internal.SafeComposeViewFactoryRoot +import com.squareup.workflow.ui.compose.ComposeViewFactoryRoot import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import kotlin.test.assertFailsWith @RunWith(AndroidJUnit4::class) -class ComposeViewFactoryRootTest { +class SafeComposeViewFactoryRootTest { - @Rule @JvmField val composeTestRule = createComposeRule() + @Rule @JvmField val composeRule = createComposeRule() @Test fun safeComposeViewFactoryRoot_wraps_content() { val wrapped = ComposeViewFactoryRoot { content -> @@ -43,7 +43,7 @@ class ComposeViewFactoryRootTest { } val safeRoot = SafeComposeViewFactoryRoot(wrapped) - composeTestRule.setContent { + composeRule.setContent { safeRoot.wrap { // Need an explicit semantics container, otherwise both Texts will be merged into a single // Semantics object with the text "Parent\nChild". @@ -53,8 +53,7 @@ class ComposeViewFactoryRootTest { } } - findByText("Parent") - .assertIsDisplayed() + findByText("Parent").assertIsDisplayed() findByText("Child").assertIsDisplayed() } @@ -63,7 +62,7 @@ class ComposeViewFactoryRootTest { val safeRoot = SafeComposeViewFactoryRoot(wrapped) val error = assertFailsWith { - composeTestRule.setContent { + composeRule.setContent { safeRoot.wrap {} } } @@ -82,7 +81,7 @@ class ComposeViewFactoryRootTest { val safeRoot = SafeComposeViewFactoryRoot(wrapped) val error = assertFailsWith { - composeTestRule.setContent { + composeRule.setContent { safeRoot.wrap { Text("Hello") } diff --git a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/internal/ViewFactoriesTest.kt b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/internal/ViewFactoriesTest.kt new file mode 100644 index 0000000000..1d4abc7794 --- /dev/null +++ b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/internal/ViewFactoriesTest.kt @@ -0,0 +1,61 @@ +/* + * Copyright 2020 Square Inc. + * + * 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.squareup.workflow.ui.compose.internal + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.ui.foundation.Text +import androidx.ui.layout.Column +import androidx.ui.test.assertIsDisplayed +import androidx.ui.test.createComposeRule +import androidx.ui.test.findByText +import com.squareup.workflow.ui.ViewEnvironment +import com.squareup.workflow.ui.ViewRegistry +import com.squareup.workflow.ui.compose.bindCompose +import com.squareup.workflow.ui.compose.showRendering +import com.squareup.workflow.ui.compose.withComposeViewFactoryRoot +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class ViewFactoriesTest { + + @Rule @JvmField val composeRule = createComposeRule() + + @Test fun showRendering_wrapsFactoryWithRoot_whenAlreadyInComposition() { + val viewEnvironment = ViewEnvironment(ViewRegistry(TestFactory)) + .withComposeViewFactoryRoot { content -> + Column { + Text("one") + content() + } + } + + composeRule.setContent { + viewEnvironment.showRendering(TestRendering("two")) + } + + findByText("one\ntwo").assertIsDisplayed() + } + + private data class TestRendering(val text: String) + + private companion object { + val TestFactory = bindCompose { rendering, _ -> + Text(rendering.text) + } + } +} diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactory.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactory.kt index 2afb6797d9..9491b7e2e0 100644 --- a/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactory.kt +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactory.kt @@ -24,16 +24,12 @@ import android.view.ViewGroup import android.widget.FrameLayout import androidx.compose.Composable import androidx.compose.FrameManager -import androidx.compose.Recomposer import androidx.compose.StructurallyEqual import androidx.compose.mutableStateOf -import androidx.ui.core.setContent import com.squareup.workflow.ui.ViewEnvironment import com.squareup.workflow.ui.ViewFactory import com.squareup.workflow.ui.bindShowRendering -import com.squareup.workflow.ui.compose.internal.CompositionContinuation -import com.squareup.workflow.ui.compose.internal.SafeComposeViewFactoryRoot -import com.squareup.workflow.ui.compose.internal.setContent +import com.squareup.workflow.ui.compose.internal.setOrContinueContent import kotlin.reflect.KClass /** @@ -93,7 +89,7 @@ inline fun bindCompose( @PublishedApi internal class ComposeViewFactory( override val type: KClass, - internal val showRendering: @Composable() (RenderingT, ViewEnvironment) -> Unit + private val content: @Composable() (RenderingT, ViewEnvironment) -> Unit ) : ViewFactory { override fun buildView( @@ -133,41 +129,22 @@ internal class ComposeViewFactory( // Entry point to the world of Compose. composeContainer.setOrContinueContent(initialViewEnvironment) { val (rendering, environment) = renderState.value!! - showRendering(rendering, environment) + showRenderingWrappedWithRoot(rendering, environment) } return composeContainer } /** - * Starts composing [content] into this [ViewGroup]. - * - * It will either propagate the composition context from any outer [ComposeViewFactory]s, or if - * this is the first [ComposeViewFactory] in the tree, it will initialize it using the - * [ComposeViewFactoryRoot] if present. - * - * This function relies on [ViewFactory.showRendering] adding the [CompositionContinuation] to the - * [ViewEnvironment]. + * Invokes [content]. If this is the highest [ComposeViewFactory] in the tree, wraps with + * the [ComposeViewFactoryRoot] if present in the [ViewEnvironment]. */ - private fun ViewGroup.setOrContinueContent( - initialViewEnvironment: ViewEnvironment, - content: @Composable() () -> Unit + @Composable internal fun showRenderingWrappedWithRoot( + rendering: RenderingT, + viewEnvironment: ViewEnvironment ) { - val (compositionReference, recomposer) = initialViewEnvironment[CompositionContinuation] - if (compositionReference != null && recomposer != null) { - // Somewhere above us in the workflow rendering tree, there's another bindCompose factory. - // We need to link to its composition reference so we inherit its ambients. - setContent(recomposer, compositionReference, content) - } else { - // This is the first bindCompose factory in the rendering tree, so we need to initialize it - // with the ComposableDecorator if present. - val decorator = initialViewEnvironment[ComposeViewFactoryRoot] - val safeDecorator = SafeComposeViewFactoryRoot(decorator) - setContent(Recomposer.current()) { - safeDecorator.wrap { - content() - } - } + wrapWithRootIfNecessary(viewEnvironment) { + content(rendering, viewEnvironment) } } } diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactoryRoot.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactoryRoot.kt index 25ed41e1c4..231636151c 100644 --- a/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactoryRoot.kt +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactoryRoot.kt @@ -19,8 +19,17 @@ package com.squareup.workflow.ui.compose import androidx.compose.Composable import androidx.compose.Direct +import androidx.compose.Providers +import androidx.compose.remember +import androidx.compose.staticAmbientOf import com.squareup.workflow.ui.ViewEnvironment import com.squareup.workflow.ui.ViewEnvironmentKey +import com.squareup.workflow.ui.compose.internal.SafeComposeViewFactoryRoot + +/** + * Used by [wrapWithRootIfNecessary] to ensure the [ComposeViewFactoryRoot] is only applied once. + */ +private val HasViewFactoryRootBeenApplied = staticAmbientOf { false } /** * A `@Composable` function that is stored in a [ViewEnvironment] and will be used to wrap the first @@ -56,6 +65,34 @@ fun ComposeViewFactoryRoot( @Composable override fun wrap(content: @Composable() () -> Unit) = wrapper(content) } +/** + * Adds [content] to the composition, ensuring that any [ComposeViewFactoryRoot] present in the + * [ViewEnvironment] has been applied. Will only apply the root at the highest occurrence of this + * function in the composition subtree. + */ +@Composable internal fun wrapWithRootIfNecessary( + viewEnvironment: ViewEnvironment, + content: @Composable() () -> Unit +) { + if (HasViewFactoryRootBeenApplied.current) { + // The only way this ambient can have the value true is if, somewhere above this point in the + // composition, the else case below was hit and wrapped us in the ambient. Since the root + // wrapper will have already been applied, we can just compose content directly. + content() + } else { + // If the ambient is false, this is the first time this function has appeared in the composition + // so far. We provide a true value for the ambient for everything below us, so any recursive + // calls to this function will hit the if case above and not re-apply the wrapper. + Providers(HasViewFactoryRootBeenApplied provides true) { + val decorator = viewEnvironment[ComposeViewFactoryRoot] + val safeDecorator = remember(decorator) { + SafeComposeViewFactoryRoot(decorator) + } + safeDecorator.wrap(content) + } + } +} + private object NoopComposeViewFactoryRoot : ComposeViewFactoryRoot { @Direct @Composable override fun wrap(content: @Composable() () -> Unit) { content() diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ComposeSupport.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ComposeSupport.kt index 506217277e..72c97918a2 100644 --- a/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ComposeSupport.kt +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ComposeSupport.kt @@ -64,8 +64,7 @@ internal fun ViewGroup.setContent( getChildAt(0).takeIf(ANDROID_OWNER_CLASS::isInstance) } else { removeAllViews(); null - } - ?: createOwner(context).also { addView(androidOwnerView(it), DefaultLayoutParams) } + } ?: createOwner(context).also { addView(androidOwnerView(it), DefaultLayoutParams) } return doSetContent(context, composeView, recomposer, parent, content) } diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/CompositionContinuation.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ParentComposition.kt similarity index 52% rename from core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/CompositionContinuation.kt rename to core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ParentComposition.kt index 4d789b9a53..0eaad52cdc 100644 --- a/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/CompositionContinuation.kt +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ParentComposition.kt @@ -13,13 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +@file:Suppress("RemoveEmptyParenthesesFromAnnotationEntry") + package com.squareup.workflow.ui.compose.internal +import android.view.ViewGroup import androidx.compose.Composable import androidx.compose.CompositionReference import androidx.compose.Recomposer import androidx.compose.compositionReference import androidx.compose.currentComposer +import androidx.ui.core.setContent import com.squareup.workflow.ui.ViewEnvironment import com.squareup.workflow.ui.ViewEnvironmentKey @@ -33,26 +37,47 @@ import com.squareup.workflow.ui.ViewEnvironmentKey * [ComposeViewFactory] will then pull the continuation out of the environment and use it to link * its composition to the outer one. */ -internal data class CompositionContinuation( +internal data class ParentComposition( val reference: CompositionReference? = null, val recomposer: Recomposer? = null ) { - companion object : ViewEnvironmentKey( - CompositionContinuation::class - ) { - override val default: CompositionContinuation - get() = CompositionContinuation() + companion object : ViewEnvironmentKey(ParentComposition::class) { + override val default: ParentComposition get() = ParentComposition() } } /** - * Creates a [CompositionContinuation] from the current point in the composition and adds it to this + * Creates a [ParentComposition] from the current point in the composition and adds it to this * [ViewEnvironment]. */ -@Composable internal fun ViewEnvironment.withCompositionContinuation(): ViewEnvironment { - val compositionReference = CompositionContinuation( +@Composable internal fun ViewEnvironment.withParentComposition(): ViewEnvironment { + val compositionReference = ParentComposition( reference = compositionReference(), recomposer = currentComposer.recomposer ) - return this + (CompositionContinuation to compositionReference) + return this + (ParentComposition to compositionReference) +} + +/** + * Starts composing [content] into this [ViewGroup]. + * + * If there is a [ParentComposition] present in [initialViewEnvironment], it will start the + * composition as a subcomposition of that continuation. + * + * This function corresponds to [withParentComposition]. + */ +internal fun ViewGroup.setOrContinueContent( + initialViewEnvironment: ViewEnvironment, + content: @Composable() () -> Unit +) { + val (compositionReference, recomposer) = initialViewEnvironment[ParentComposition] + if (compositionReference != null && recomposer != null) { + // Somewhere above us in the workflow rendering tree, there's another bindCompose factory. + // We need to link to its composition reference so we inherit its ambients. + setContent(recomposer, compositionReference, content) + } else { + // This is the first bindCompose factory in the rendering tree, so it won't be a child + // composition. + setContent(Recomposer.current(), content) + } } diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ViewFactories.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ViewFactories.kt index edd9290f51..9007cd3e0e 100644 --- a/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ViewFactories.kt +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ViewFactories.kt @@ -36,7 +36,7 @@ import com.squareup.workflow.ui.compose.internal.ComposableViewStubWrapper.Updat * * *Note: [rendering] must be the same type as this [ViewFactory], even though the type system does * not enforce this constraint. This is due to a Compose compiler bug tracked - * [here](https://issuetracker.google.com/issues/156527332).* + * [here](https://issuetracker.google.com/issues/156527332). * * @see ViewEnvironment.showRendering * @see com.squareup.workflow.ui.ViewRegistry.showRendering @@ -47,19 +47,21 @@ import com.squareup.workflow.ui.compose.internal.ComposableViewStubWrapper.Updat viewEnvironment: ViewEnvironment, modifier: Modifier = Modifier ) { + val viewFactory = this Box(modifier = modifier) { // Fast path: If the child binding is also a Composable, we don't need to go through the legacy // view system and can just invoke the binding's composable function directly. - if (this is ComposeViewFactory) { - showRendering(rendering, viewEnvironment) + if (viewFactory is ComposeViewFactory) { + viewFactory.showRenderingWrappedWithRoot(rendering, viewEnvironment) } else { - // Plumb the current composition "context" through the ViewEnvironment so any nested composable - // factories get access to any ambients currently in effect. + // Plumb the current composition "context" through the ViewEnvironment so any nested + // composable factories get access to any ambients currently in effect. // See setOrContinueContent(). - val newEnvironment = viewEnvironment.withCompositionContinuation() + val newEnvironment = viewEnvironment.withParentComposition() - // IntelliJ currently complains very loudly about this function call, but it actually compiles. - // The IDE tooling isn't currently able to recognize that the Compose compiler accepts this code. + // IntelliJ currently complains very loudly about this function call, but it actually + // compiles. The IDE tooling isn't currently able to recognize that the Compose compiler + // accepts this code. ComposableViewStubWrapper(update = Update(rendering, newEnvironment)) } } From 9a6c51ad48130edc7393cba863b17a0d47bd2284 Mon Sep 17 00:00:00 2001 From: Zach Klippenstein Date: Mon, 18 May 2020 13:46:02 -0700 Subject: [PATCH 18/67] Fix gradle cache in CI. Most of the tests in this repo are UI tests, so it would be really nice to not build the entire codebase twice to start running them. I decreased the timeout as well since test shards no longer need to re-build. --- .github/workflows/kotlin.yml | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/.github/workflows/kotlin.yml b/.github/workflows/kotlin.yml index 6e30bbe393..ece141dca9 100644 --- a/.github/workflows/kotlin.yml +++ b/.github/workflows/kotlin.yml @@ -6,10 +6,7 @@ on: pull_request: env: - # Must use $HOME here, NOT a tilde, because of the order in which bash does expansion: - # Tilde happens before variables, so will be used literally, whereas $HOME will be - # recursively expanded. - GRADLE_CACHE_PATH: $HOME/.gradle/caches + GRADLE_HOME: ${{ github.workspace }}/gradle-home jobs: assemble: @@ -29,7 +26,7 @@ jobs: - name: Cache gradle dependencies uses: actions/cache@v1 with: - path: ${{ env.GRADLE_CACHE_PATH }} + path: ${{ env.GRADLE_HOME }}/caches # Include the SHA in the hash so this step always adds a cache entry. If we didn't use the SHA, the artifacts # would only get cached once for each build config hash. # Don't use ${{ runner.os }} in the key so we don't re-assemble for UI tests. @@ -47,13 +44,13 @@ jobs: # and there's no way to modify the cache after the job that created it finishes. - name: Clean gradle build cache to assemble fresh run: | - ls -lhrt $GRADLE_CACHE_PATH || true - rm -rf $GRADLE_CACHE_PATH/build-cache-1 - ls -lhrt $GRADLE_CACHE_PATH || true + ls -lhrt "$GRADLE_HOME/caches" || true + rm -rf "$GRADLE_HOME/caches/build-cache-1" + ls -lhrt "$GRADLE_HOME/caches" || true ## Actual task - name: Assemble with gradle - run: ./gradlew assemble --build-cache --no-daemon --stacktrace + run: ./gradlew assemble --build-cache --no-daemon --stacktrace --gradle-user-home "$GRADLE_HOME" # Runs all check tasks in parallel. check: @@ -84,14 +81,14 @@ jobs: - name: Cache build artifacts uses: actions/cache@v1 with: - path: ${{ env.GRADLE_CACHE_PATH }} + path: ${{ env.GRADLE_HOME }}/caches # Don't set restore-keys so cache is always only valid for the current build config. # Also don't use ${{ runner.os }} in the key so we don't re-assemble for UI tests. key: gradle-${{ hashFiles('**/gradle/wrapper/gradle-wrapper.properties') }}-${{ hashFiles('**/*.gradle*') }}-${{ hashFiles('**/buildSrc/**') }}-${{ github.sha }} ## Actual task - name: Check with Gradle - run: ./gradlew ${{ matrix.gradle-task }} --build-cache --no-daemon --stacktrace + run: ./gradlew ${{ matrix.gradle-task }} --build-cache --no-daemon --stacktrace --gradle-user-home "$GRADLE_HOME" instrumentation-tests: name: Instrumentation tests @@ -119,7 +116,7 @@ jobs: - name: Cache build artifacts uses: actions/cache@v1 with: - path: ${{ env.GRADLE_CACHE_PATH }} + path: ${{ env.GRADLE_HOME }}/caches # Don't set restore-keys so cache is always only valid for the current build config. # Also don't use ${{ runner.os }} in the key so we don't re-assemble for UI tests. key: gradle-${{ hashFiles('**/gradle/wrapper/gradle-wrapper.properties') }}-${{ hashFiles('**/*.gradle*') }}-${{ hashFiles('**/buildSrc/**') }}-${{ github.sha }} @@ -130,4 +127,4 @@ jobs: with: api-level: ${{ matrix.api-level }} arch: x86_64 - script: ./gradlew connectedCheck --build-cache --no-daemon --stacktrace + script: ./gradlew connectedCheck --build-cache --no-daemon --stacktrace --gradle-user-home "$GRADLE_HOME" From f91253ab5a04c62ec846e174a35f3decee84b6b2 Mon Sep 17 00:00:00 2001 From: Zach Klippenstein Date: Mon, 18 May 2020 13:59:54 -0700 Subject: [PATCH 19/67] Upload UI test reports as Github Actions artifacts. --- .github/workflows/kotlin.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/kotlin.yml b/.github/workflows/kotlin.yml index ece141dca9..249f19f8c7 100644 --- a/.github/workflows/kotlin.yml +++ b/.github/workflows/kotlin.yml @@ -128,3 +128,8 @@ jobs: api-level: ${{ matrix.api-level }} arch: x86_64 script: ./gradlew connectedCheck --build-cache --no-daemon --stacktrace --gradle-user-home "$GRADLE_HOME" + - name: Upload results + uses: actions/upload-artifact@v2 + with: + name: instrumentation-test-results + path: ./**/build/reports/androidTests/connected/** From eb8a3d0078201ba3f93670a89de8c758225b6425 Mon Sep 17 00:00:00 2001 From: Zach Klippenstein Date: Mon, 18 May 2020 14:16:15 -0700 Subject: [PATCH 20/67] Releasing v0.29.0. --- CHANGELOG.md | 15 +++++++ RELEASING.md | 104 ++++++++++++++++++++++++++++++++++++++++++++++ gradle.properties | 2 +- 3 files changed, 120 insertions(+), 1 deletion(-) create mode 100644 CHANGELOG.md create mode 100644 RELEASING.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000000..1c1747ebe8 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,15 @@ +Change Log +========== + +Version 0.29.0 +-------------- + +_2020-05-18_ + +* First release from separate repo. +* Update: Compose version to dev11. (#26) +* New: Add the ability to display nested renderings with `bindCompose`. (#7) +* New: Introduce `ComposeWorkflow`, a self-rendering Workflow. (#8) +* New: Introduce tooling module with support for previewing ViewBindings with Compose's Preview. (#15) +* New: Introduce WorkflowContainer for running a workflow inside a Compose app. (#16) +* Breaking: Tidy up the package structure. (#23) diff --git a/RELEASING.md b/RELEASING.md new file mode 100644 index 0000000000..6c0ff1b0e8 --- /dev/null +++ b/RELEASING.md @@ -0,0 +1,104 @@ +Releasing +========= + +Production Releases +------------------- + +1. Merge an update of [the change log](CHANGELOG.md) with the changes since the last release. + +1. Make sure you're on the `master` branch (or fix branch, e.g. `v0.1-fixes`). + +1. Confirm that the kotlin build is green before committing any changes + ```bash + (cd kotlin && ./gradlew build connectedCheck) + ``` + +1. In `kotlin/gradle.properties`, remove the `-SNAPSHOT` prefix from the `VERSION_NAME` property. + E.g. `VERSION_NAME=0.1.0` + +1. Create a commit and tag the commit with the version number: + ```bash + git commit -am "Releasing v0.1.0." + git tag v0.1.0 + ``` + +1. Upload the kotlin artifacts: + ```bash + (cd kotlin && ./gradlew build && ./gradlew uploadArchives --no-parallel) + ``` + +1. Close and release the staging repository at https://oss.sonatype.org. + +1. Update the `VERSION_NAME` property in `kotlin/gradle.properties` to the new + snapshot version, e.g. `VERSION_NAME=0.2.0-SNAPSHOT`. + +1. Commit the new snapshot version: + ``` + git commit -am "Finish releasing v0.1.0." + ``` + +1. Push your commits and tag: + ``` + git push origin master + # or git push origin fix-branch + git push origin v0.1.0 + ``` + +1. Create the release on GitHub: + 1. Go to the [Releases](https://github.com/square/workflow-kotlin-compose/releases) page for the GitHub + project. + 1. Click "Draft a new release". + 1. Enter the tag name you just pushed. + 1. Title the release with the same name as the tag. + 1. Copy & paste the changelog entry for this release into the description. + 1. If this is a pre-release version, check the pre-release box. + 1. Hit "Publish release". + +1. If this was a fix release, merge changes to the master branch: + ```bash + git checkout master + git pull + git merge --no-ff v0.1-fixes + # Resolve conflicts. Accept master's versions of gradle.properties and podspecs. + git push origin master + ``` + +1. Publish the website. See below. + +--- + +## Kotlin Notes + +### Development + +To build and install the current version to your local Maven repository (`~/.m2`), run: + +```bash +./gradlew clean installArchives +``` + +### Deploying + +#### Configuration + +In order to deploy artifacts to a Maven repository, you'll need to set 4 properties in your private +Gradle properties file (`~/.gradle/gradle.properties`): + +``` +RELEASE_REPOSITORY_URL= +SNAPSHOT_REPOSITORY_URL= +SONATYPE_NEXUS_PASSWORD= +``` + +#### Snapshot Releases + +Double-check that `gradle.properties` correctly contains the `-SNAPSHOT` suffix, then upload +snapshot artifacts to Sonatype just like you would for a production release: + +```bash +./gradlew clean build && ./gradlew uploadArchives --no-parallel +``` + +You can verify the artifacts are available by visiting +https://oss.sonatype.org/content/repositories/snapshots/com/squareup/workflow/. diff --git a/gradle.properties b/gradle.properties index f3dd00919c..4f9a71c901 100644 --- a/gradle.properties +++ b/gradle.properties @@ -23,7 +23,7 @@ android.enableJetifier=true systemProp.org.gradle.internal.publish.checksums.insecure=true GROUP=com.squareup.workflow -VERSION_NAME=0.27.0-SNAPSHOT +VERSION_NAME=0.29.0 POM_DESCRIPTION=Reactive workflows From 41cbbf1fe8b101fab33be1a7d3461dcfa511e331 Mon Sep 17 00:00:00 2001 From: Zach Klippenstein Date: Mon, 18 May 2020 15:48:07 -0700 Subject: [PATCH 21/67] Finish releasing v0.29.0. --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 4f9a71c901..3778eecbd2 100644 --- a/gradle.properties +++ b/gradle.properties @@ -23,7 +23,7 @@ android.enableJetifier=true systemProp.org.gradle.internal.publish.checksums.insecure=true GROUP=com.squareup.workflow -VERSION_NAME=0.29.0 +VERSION_NAME=0.30.0-SNAPSHOT POM_DESCRIPTION=Reactive workflows From 4c1c33b073eaedaa5a0e5625701f3e829353a8ce Mon Sep 17 00:00:00 2001 From: Zach Klippenstein Date: Mon, 18 May 2020 16:09:08 -0700 Subject: [PATCH 22/67] Update README with dependency instructions for v0.29.0. --- README.md | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 3d278fdf87..50c5ffc58b 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,8 @@ # workflow-kotlin-compose +[![GitHub license](https://img.shields.io/badge/license-Apache%20License%202.0-blue.svg?style=flat)](https://www.apache.org/licenses/LICENSE-2.0) +[![Maven Central](https://img.shields.io/maven-central/v/com.squareup.workflow/workflow-ui-core-compose.svg?label=Maven%20Central)](https://search.maven.org/search?q=g:com.squareup.workflow%20AND%20a:workflow-ui-core-compose) + This module provides experimental support for [Jetpack Compose UI][1] with workflows. The only integration that is currently supported is the ability to define [ViewFactories][2] that @@ -20,8 +23,24 @@ and to experiment with various ways to integrate Compose with Workflow. ## Usage -To get started, you must be using the latest Android Gradle Plugin 4.x version. Then, you need to -enable Compose support in your `build.gradle`: +### Add the dependency + +Add the dependencies from this project (they're on Maven Central): + +```groovy +dependencies { + // Main dependency + implementation "com.squareup.workflow:workflow-ui-core-compose:${versions.workflow_compose}" + + // For the preview helpers + implementation "com.squareup.workflow:workflow-ui-compose-tooling:${versions.workflow_compose}" +} +``` + +### Enable Compose + +You must be using the latest Android Gradle Plugin 4.x version, and enable Compose support +in your `build.gradle`: ```groovy android { @@ -47,7 +66,7 @@ To create a `ViewFactory`, call `bindCompose`. The lambda passed to `bindCompose function. ```kotlin -val HelloBinding = bindCompose { rendering -> +val HelloBinding = bindCompose { rendering, _ -> MaterialTheme { Clickable(onClick = { rendering.onClick() }) { Text(rendering.message) From da4527c85b33f45207de2f85c881d4a03be09309 Mon Sep 17 00:00:00 2001 From: Zach Klippenstein Date: Sat, 16 May 2020 19:30:11 -0700 Subject: [PATCH 23/67] Inline WorkflowViewStub logic in ViewFactory.showRendering to avoid unnecessary intermediate Views. This is effectively the logic of `WorkflowViewStub`, but translated into Compose idioms. This approach has a few advantages: - Avoids extra custom views required to host `WorkflowViewStub` inside a Composition. Its trick of replacing itself in its parent doesn't play nice with Compose. - Allows us to pass the correct parent view for inflation (the root of the composition). - Avoids `WorkflowViewStub` having to do its own lookup to find the correct [ViewFactory], since we already have the correct one. Like `WorkflowViewStub`, this function uses the `ViewFactory` to create and memoize a `View` to display the rendering, keeps it updated with the latest `RenderingT` and `ViewEnvironment`, and adds it to the composition. --- .buildscript/android-ui-tests.gradle | 3 + .github/workflows/kotlin.yml | 4 +- .../workflow/ui/compose/ComposeViewFactory.kt | 21 ++-- .../ui/compose/internal/ParentComposition.kt | 40 ++++---- .../ui/compose/internal/ViewFactories.kt | 97 ++++++++++++------- .../nestedrenderings/NestedRenderingsTest.kt | 26 ++++- 6 files changed, 121 insertions(+), 70 deletions(-) diff --git a/.buildscript/android-ui-tests.gradle b/.buildscript/android-ui-tests.gradle index 120e3c2e0f..ffa0eae327 100644 --- a/.buildscript/android-ui-tests.gradle +++ b/.buildscript/android-ui-tests.gradle @@ -2,6 +2,9 @@ android { defaultConfig { testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } + testOptions { + animationsDisabled = true + } } dependencies { diff --git a/.github/workflows/kotlin.yml b/.github/workflows/kotlin.yml index 249f19f8c7..73804b4877 100644 --- a/.github/workflows/kotlin.yml +++ b/.github/workflows/kotlin.yml @@ -100,9 +100,7 @@ jobs: fail-fast: false matrix: api-level: - # Tests are failing on APIs <24. - #- 21 - #- 23 + - 21 - 24 - 29 steps: diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactory.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactory.kt index 9491b7e2e0..6bbb6c7b13 100644 --- a/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactory.kt +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactory.kt @@ -29,7 +29,8 @@ import androidx.compose.mutableStateOf import com.squareup.workflow.ui.ViewEnvironment import com.squareup.workflow.ui.ViewFactory import com.squareup.workflow.ui.bindShowRendering -import com.squareup.workflow.ui.compose.internal.setOrContinueContent +import com.squareup.workflow.ui.compose.internal.ParentComposition +import com.squareup.workflow.ui.compose.internal.setOrSubcomposeContent import kotlin.reflect.KClass /** @@ -111,23 +112,25 @@ internal class ComposeViewFactory( areEquivalent = StructurallyEqual ) - // Models will throw if their properties are accessed when there is no frame open. Currently, - // that will be the case if the model is accessed before any other Compose infrastructure has - // ran, i.e. if this view factory is the first compose code to run in the app. - // I believe that eventually there will be a global frame that will make this unnecessary. - FrameManager.ensureStarted() - // Update the state whenever a new rendering is emitted. composeContainer.bindShowRendering( initialRendering, initialViewEnvironment ) { rendering, environment -> // This lambda will be executed synchronously before bindShowRendering returns. - renderState.value = Pair(rendering, environment) + + // Models will throw if their properties are accessed when there is no frame open. Currently, + // that will be the case if the model is accessed before any other Compose infrastructure has + // run, i.e. if this view factory is the first compose code to run in the app. + // I believe that eventually there will be a global frame that will make this unnecessary. + FrameManager.framed { + renderState.value = Pair(rendering, environment) + } } // Entry point to the world of Compose. - composeContainer.setOrContinueContent(initialViewEnvironment) { + val parentComposition = initialViewEnvironment[ParentComposition] + composeContainer.setOrSubcomposeContent(parentComposition.reference) { val (rendering, environment) = renderState.value!! showRenderingWrappedWithRoot(rendering, environment) } diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ParentComposition.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ParentComposition.kt index 0eaad52cdc..633530e8fd 100644 --- a/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ParentComposition.kt +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ParentComposition.kt @@ -22,24 +22,22 @@ import androidx.compose.Composable import androidx.compose.CompositionReference import androidx.compose.Recomposer import androidx.compose.compositionReference -import androidx.compose.currentComposer import androidx.ui.core.setContent import com.squareup.workflow.ui.ViewEnvironment import com.squareup.workflow.ui.ViewEnvironmentKey /** - * Holds a [CompositionReference] and a [Recomposer] that can be used to [setContent] to create a - * composition that is a child of another composition. Child compositions get ambients and other - * compose context from their parent, which allows ambients provided around a [showRendering] call - * to be read by nested [bindCompose] factories. + * Holds a [CompositionReference] and that can be passed to [setOrSubcomposeContent] to create a + * composition that is a child of another composition. Subcompositions get ambients and other + * compose context from their parent, and propagate invalidations, which allows ambients provided + * around a [showRendering] call to be read by nested Compose-based view factories. * * When [showRendering] is called, it will store an instance of this class in the [ViewEnvironment]. - * [ComposeViewFactory] will then pull the continuation out of the environment and use it to link - * its composition to the outer one. + * [ComposeViewFactory] pulls the reference out of the environment and uses it to link its + * composition to the outer one. */ -internal data class ParentComposition( - val reference: CompositionReference? = null, - val recomposer: Recomposer? = null +internal class ParentComposition( + var reference: CompositionReference? = null ) { companion object : ViewEnvironmentKey(ParentComposition::class) { override val default: ParentComposition get() = ParentComposition() @@ -50,31 +48,29 @@ internal data class ParentComposition( * Creates a [ParentComposition] from the current point in the composition and adds it to this * [ViewEnvironment]. */ -@Composable internal fun ViewEnvironment.withParentComposition(): ViewEnvironment { - val compositionReference = ParentComposition( - reference = compositionReference(), - recomposer = currentComposer.recomposer - ) +@Composable internal fun ViewEnvironment.withParentComposition( + reference: CompositionReference = compositionReference() +): ViewEnvironment { + val compositionReference = ParentComposition(reference = reference) return this + (ParentComposition to compositionReference) } /** * Starts composing [content] into this [ViewGroup]. * - * If there is a [ParentComposition] present in [initialViewEnvironment], it will start the - * composition as a subcomposition of that continuation. + * If [parentComposition] is not null, [content] will be installed as a _subcomposition_ of the + * parent composition, meaning that it will propagate ambients and invalidation. * * This function corresponds to [withParentComposition]. */ -internal fun ViewGroup.setOrContinueContent( - initialViewEnvironment: ViewEnvironment, +internal fun ViewGroup.setOrSubcomposeContent( + parentComposition: CompositionReference?, content: @Composable() () -> Unit ) { - val (compositionReference, recomposer) = initialViewEnvironment[ParentComposition] - if (compositionReference != null && recomposer != null) { + if (parentComposition != null) { // Somewhere above us in the workflow rendering tree, there's another bindCompose factory. // We need to link to its composition reference so we inherit its ambients. - setContent(recomposer, compositionReference, content) + setContent(Recomposer.current(), parentComposition, content) } else { // This is the first bindCompose factory in the rendering tree, so it won't be a child // composition. diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ViewFactories.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ViewFactories.kt index 9007cd3e0e..70048c41a0 100644 --- a/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ViewFactories.kt +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ViewFactories.kt @@ -15,17 +15,24 @@ */ package com.squareup.workflow.ui.compose.internal -import android.content.Context -import android.view.ViewGroup.LayoutParams.MATCH_PARENT -import android.widget.FrameLayout +import android.view.View +import android.view.ViewGroup import androidx.compose.Composable +import androidx.compose.compositionReference +import androidx.compose.onPreCommit +import androidx.compose.remember +import androidx.ui.core.AndroidOwner +import androidx.ui.core.ContextAmbient import androidx.ui.core.Modifier +import androidx.ui.core.OwnerAmbient +import androidx.ui.core.Ref import androidx.ui.foundation.Box +import androidx.ui.viewinterop.AndroidView import com.squareup.workflow.ui.ViewEnvironment import com.squareup.workflow.ui.ViewFactory -import com.squareup.workflow.ui.WorkflowViewStub +import com.squareup.workflow.ui.canShowRendering import com.squareup.workflow.ui.compose.ComposeViewFactory -import com.squareup.workflow.ui.compose.internal.ComposableViewStubWrapper.Update +import com.squareup.workflow.ui.showRendering /** * Renders [rendering] into the composition using the `ViewRegistry` from the [ViewEnvironment] to @@ -49,49 +56,73 @@ import com.squareup.workflow.ui.compose.internal.ComposableViewStubWrapper.Updat ) { val viewFactory = this Box(modifier = modifier) { - // Fast path: If the child binding is also a Composable, we don't need to go through the legacy - // view system and can just invoke the binding's composable function directly. + // "Fast" path: If the child binding is also a Composable, we don't need to go through the + // legacy view system and can just invoke the binding's composable function directly. if (viewFactory is ComposeViewFactory) { viewFactory.showRenderingWrappedWithRoot(rendering, viewEnvironment) - } else { - // Plumb the current composition "context" through the ViewEnvironment so any nested - // composable factories get access to any ambients currently in effect. - // See setOrContinueContent(). - val newEnvironment = viewEnvironment.withParentComposition() - - // IntelliJ currently complains very loudly about this function call, but it actually - // compiles. The IDE tooling isn't currently able to recognize that the Compose compiler - // accepts this code. - ComposableViewStubWrapper(update = Update(rendering, newEnvironment)) + return@Box } + + // "Slow" path: Create a legacy Android View to show the rendering, like WorkflowViewStub. + ViewFactoryAndroidView(viewFactory, rendering, viewEnvironment) } } /** - * Wraps a [WorkflowViewStub] with an API that is more Compose-friendly. + * This is effectively the logic of [com.squareup.workflow.ui.WorkflowViewStub], but translated + * into Compose idioms. This approach has a few advantages: + * + * - Avoids extra custom views required to host `WorkflowViewStub` inside a Composition. Its trick + * of replacing itself in its parent doesn't play nice with Compose. + * - Allows us to pass the correct parent view for inflation (the root of the composition). + * - Avoids `WorkflowViewStub` having to do its own lookup to find the correct [ViewFactory], since + * we already have the correct one. * - * In particular, Compose will only generate `Emittable`s for views with a single constructor - * that takes a [Context]. + * Like `WorkflowViewStub`, this function uses the [viewFactory] to create and memoize a [View] to + * display the [rendering], keeps it updated with the latest [rendering] and [viewEnvironment], and + * adds it to the composition. * - * See [this slack message](https://kotlinlang.slack.com/archives/CJLTWPH7S/p1576264533012000?thread_ts=1576262311.008800&cid=CJLTWPH7S). + * This function also passes a [ParentComposition] down through the [ViewEnvironment] so that if the + * child view further nests any `ComposableViewFactory`s, they will be correctly subcomposed. */ -private class ComposableViewStubWrapper(context: Context) : FrameLayout(context) { +@Composable private fun ViewFactoryAndroidView( + viewFactory: ViewFactory, + rendering: R, + viewEnvironment: ViewEnvironment +) { + val childView = remember { Ref() } - data class Update( - val rendering: Any, - val viewEnvironment: ViewEnvironment - ) + // Plumb the current composition through the ViewEnvironment so any nested composable factories + // get access to any ambients currently in effect. See setOrSubcomposeContent(). + val parentComposition = remember { ParentComposition() } + parentComposition.reference = compositionReference() + val wrappedEnvironment = remember(viewEnvironment) { + viewEnvironment + (ParentComposition to parentComposition) + } - private val viewStub = WorkflowViewStub(context) + // A view factory can decide to recreate its view at any time. This also covers the case where + // the value of the viewFactory argument has changed, including to one with a different type. + if (childView.value?.canShowRendering(rendering) != true) { + // If we don't pass the parent Android View, the child will have the wrong LayoutParams. + // OwnerAmbient is deprecated, but the only way to get the root view currently. I've filed + // a feature request to expose this as first-class API, see + // https://issuetracker.google.com/issues/156875705. + @Suppress("DEPRECATION") + val parentView = (OwnerAmbient.current as? AndroidOwner)?.view as? ViewGroup - init { - addView(viewStub) + childView.value = viewFactory.buildView( + initialRendering = rendering, + initialViewEnvironment = wrappedEnvironment, + contextForNewView = ContextAmbient.current, + container = parentView + ) } - // Compose turns this into a parameter when you invoke this class as a Composable. - fun setUpdate(update: Update) { - viewStub.update(update.rendering, update.viewEnvironment) + // Invoke the ViewFactory's update logic whenever the view, the rendering, or the ViewEnvironment + // change. + onPreCommit(childView.value, rendering, wrappedEnvironment) { + childView.value!!.showRendering(rendering, wrappedEnvironment) } - override fun getLayoutParams(): LayoutParams = LayoutParams(MATCH_PARENT, MATCH_PARENT) + AndroidView(childView.value!!) } diff --git a/samples/nested-renderings/src/androidTest/java/com/squareup/sample/nestedrenderings/NestedRenderingsTest.kt b/samples/nested-renderings/src/androidTest/java/com/squareup/sample/nestedrenderings/NestedRenderingsTest.kt index c2014b7449..a4440c479e 100644 --- a/samples/nested-renderings/src/androidTest/java/com/squareup/sample/nestedrenderings/NestedRenderingsTest.kt +++ b/samples/nested-renderings/src/androidTest/java/com/squareup/sample/nestedrenderings/NestedRenderingsTest.kt @@ -24,7 +24,6 @@ import androidx.ui.test.assertIsDisplayed import androidx.ui.test.doClick import androidx.ui.test.findAllByText import androidx.ui.test.findByText -import androidx.ui.test.last import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -49,11 +48,32 @@ class NestedRenderingsTest { findAllByText(ADD_BUTTON_TEXT) .assertCountEquals(4) - findAllByText("Reset").last() - .doClick() + resetAll() findAllByText(ADD_BUTTON_TEXT).assertCountEquals(1) } + /** + * We can't rely on the order of nodes returned by [findAllByText], and the contents of the + * collection will change as we remove nodes, so we have to double-loop over all reset buttons and + * click them all until there is only one left. + */ + private fun resetAll() { + var foundNodes = Int.MAX_VALUE + while (foundNodes > 1) { + foundNodes = 0 + findAllByText("Reset").forEach { + try { + it.assertExists() + } catch (e: AssertionError) { + // No more reset buttons, we're done. + return@forEach + } + foundNodes++ + it.doClick() + } + } + } + private fun SemanticsNodeInteractionCollection.forEach( block: (SemanticsNodeInteraction) -> Unit ) { From 5e620d7a7af8c9a49852575d5d00c55c0af2ed1d Mon Sep 17 00:00:00 2001 From: Zach Klippenstein Date: Tue, 19 May 2020 15:37:12 -0700 Subject: [PATCH 24/67] Make ViewRegistry.showRendering update if ViewRegistry changes. --- .../ui/compose/internal/ViewRegistriesTest.kt | 56 +++++++++++++++++++ .../ui/compose/internal/ViewRegistries.kt | 2 +- 2 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/internal/ViewRegistriesTest.kt diff --git a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/internal/ViewRegistriesTest.kt b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/internal/ViewRegistriesTest.kt new file mode 100644 index 0000000000..f6302b123f --- /dev/null +++ b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/internal/ViewRegistriesTest.kt @@ -0,0 +1,56 @@ +/* + * Copyright 2020 Square Inc. + * + * 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.squareup.workflow.ui.compose.internal + +import androidx.compose.FrameManager +import androidx.compose.mutableStateOf +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.ui.foundation.Text +import androidx.ui.test.assertIsDisplayed +import androidx.ui.test.createComposeRule +import androidx.ui.test.findByText +import com.squareup.workflow.ui.ViewEnvironment +import com.squareup.workflow.ui.ViewRegistry +import com.squareup.workflow.ui.compose.bindCompose +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class ViewRegistriesTest { + + @Rule @JvmField val composeRule = createComposeRule() + + @Test fun showRendering_recomposes_whenFactoryChanged() { + val registry1 = ViewRegistry(bindCompose { rendering, _ -> + Text(rendering) + }) + val registry2 = ViewRegistry(bindCompose { rendering, _ -> + Text(rendering.reversed()) + }) + val registry = mutableStateOf(registry1) + + composeRule.setContent { + registry.value.showRendering("hello", ViewEnvironment(registry.value)) + } + + findByText("hello").assertIsDisplayed() + FrameManager.framed { + registry.value = registry2 + } + findByText("olleh").assertIsDisplayed() + } +} diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ViewRegistries.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ViewRegistries.kt index 80f6f2aa02..cc456c3539 100644 --- a/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ViewRegistries.kt +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ViewRegistries.kt @@ -37,6 +37,6 @@ import com.squareup.workflow.ui.ViewRegistry modifier: Modifier = Modifier ) { val renderingType = rendering::class - val viewFactory = remember(renderingType) { getFactoryFor(renderingType) } + val viewFactory = remember(this, renderingType) { getFactoryFor(renderingType) } viewFactory.showRendering(rendering, hints, modifier) } From 3bb2f1f4e32b58e3c74d7d5876f59c4699f260bc Mon Sep 17 00:00:00 2001 From: Zach Klippenstein Date: Tue, 19 May 2020 16:56:47 -0700 Subject: [PATCH 25/67] Rename bindCompose to composedViewFactory. Fixes #19. --- README.md | 8 ++++---- .../ui/compose/tooling/PreviewViewFactoryTest.kt | 10 +++++----- .../ui/compose/tooling/PlaceholderViewFactory.kt | 4 ++-- .../workflow/ui/compose/ComposeViewFactoryTest.kt | 2 +- .../ui/compose/internal/ViewFactoriesTest.kt | 4 ++-- .../squareup/workflow/ui/compose/ComposeRendering.kt | 2 +- .../workflow/ui/compose/ComposeViewFactory.kt | 12 ++++++------ .../workflow/ui/compose/ComposeViewFactoryRoot.kt | 12 ++++++------ .../squareup/workflow/ui/compose/ComposeWorkflow.kt | 2 +- .../squareup/workflow/ui/compose/ViewEnvironments.kt | 2 +- .../ui/compose/internal/ParentComposition.kt | 4 ++-- .../workflow/ui/compose/internal/ViewFactories.kt | 3 ++- .../workflow/ui/compose/internal/ViewRegistries.kt | 2 +- .../sample/hellocomposebinding/HelloBinding.kt | 4 ++-- .../com/squareup/sample/hellocompose/HelloBinding.kt | 4 ++-- .../sample/nestedrenderings/RecursiveViewFactory.kt | 4 ++-- 16 files changed, 40 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index 50c5ffc58b..521d5b1345 100644 --- a/README.md +++ b/README.md @@ -62,11 +62,11 @@ tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach { } ``` -To create a `ViewFactory`, call `bindCompose`. The lambda passed to `bindCompose` is a `@Composable` -function. +To create a `ViewFactory`, call `composedViewFactory`. The lambda passed to `composedViewFactory` is +a `@Composable` function. ```kotlin -val HelloBinding = bindCompose { rendering, _ -> +val HelloBinding = composedViewFactory { rendering, _ -> MaterialTheme { Clickable(onClick = { rendering.onClick() }) { Text(rendering.message) @@ -75,7 +75,7 @@ val HelloBinding = bindCompose { rendering, _ -> } ``` -The `bindCompose` function returns a regular [`ViewFactory`][2] which can be added to a +The `composedViewFactory` function returns a regular [`ViewFactory`][2] which can be added to a [`ViewRegistry`][3] like any other: ```kotlin diff --git a/compose-tooling/src/androidTest/java/com/squareup/workflow/ui/compose/tooling/PreviewViewFactoryTest.kt b/compose-tooling/src/androidTest/java/com/squareup/workflow/ui/compose/tooling/PreviewViewFactoryTest.kt index cab900a545..b09ff3bdfa 100644 --- a/compose-tooling/src/androidTest/java/com/squareup/workflow/ui/compose/tooling/PreviewViewFactoryTest.kt +++ b/compose-tooling/src/androidTest/java/com/squareup/workflow/ui/compose/tooling/PreviewViewFactoryTest.kt @@ -31,7 +31,7 @@ import androidx.ui.test.findByText import androidx.ui.tooling.preview.Preview import androidx.ui.unit.dp import com.squareup.workflow.ui.ViewEnvironmentKey -import com.squareup.workflow.ui.compose.bindCompose +import com.squareup.workflow.ui.compose.composedViewFactory import com.squareup.workflow.ui.compose.showRendering import org.junit.Rule import org.junit.Test @@ -99,7 +99,7 @@ class PreviewViewFactoryTest { findByText("foo").assertIsDisplayed() } - private val ParentWithOneChild = bindCompose> { rendering, environment -> + private val ParentWithOneChild = composedViewFactory> { rendering, environment -> Column { Text(rendering.first) Semantics(container = true, mergeAllDescendants = true) { @@ -113,7 +113,7 @@ class PreviewViewFactoryTest { } private val ParentWithTwoChildren = - bindCompose> { rendering, environment -> + composedViewFactory> { rendering, environment -> Column { Semantics(container = true) { environment.showRendering(rendering = rendering.first) @@ -134,7 +134,7 @@ class PreviewViewFactoryTest { val child: RecursiveRendering? = null ) - private val ParentRecursive = bindCompose { rendering, environment -> + private val ParentRecursive = composedViewFactory { rendering, environment -> Column { Text(rendering.text) rendering.child?.let { child -> @@ -175,7 +175,7 @@ class PreviewViewFactoryTest { override val default: String get() = error("Not specified") } - private val ParentConsumesCustomKey = bindCompose { _, environment -> + private val ParentConsumesCustomKey = composedViewFactory { _, environment -> Text(environment[TestEnvironmentKey]) } diff --git a/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/PlaceholderViewFactory.kt b/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/PlaceholderViewFactory.kt index fae90a780b..9987b25b81 100644 --- a/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/PlaceholderViewFactory.kt +++ b/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/PlaceholderViewFactory.kt @@ -42,14 +42,14 @@ import androidx.ui.unit.dp import androidx.ui.unit.px import androidx.ui.unit.toRect import com.squareup.workflow.ui.ViewFactory -import com.squareup.workflow.ui.compose.bindCompose +import com.squareup.workflow.ui.compose.composedViewFactory /** * A [ViewFactory] that will be used any time a [PreviewViewRegistry] is asked to show a rendering. * It displays a placeholder graphic and the rendering's `toString()` result. */ internal fun placeholderViewFactory(modifier: Modifier): ViewFactory = - bindCompose { rendering, _ -> + composedViewFactory { rendering, _ -> Text( modifier = modifier .clipToBounds() diff --git a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/ComposeViewFactoryTest.kt b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/ComposeViewFactoryTest.kt index 510c4fbfae..af6a87c871 100644 --- a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/ComposeViewFactoryTest.kt +++ b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/ComposeViewFactoryTest.kt @@ -71,7 +71,7 @@ class ComposeViewFactoryTest { private data class TestRendering(val text: String) private companion object { - val TestFactory = bindCompose { rendering, _ -> + val TestFactory = composedViewFactory { rendering, _ -> Text(rendering.text) } } diff --git a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/internal/ViewFactoriesTest.kt b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/internal/ViewFactoriesTest.kt index 1d4abc7794..110a76b53e 100644 --- a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/internal/ViewFactoriesTest.kt +++ b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/internal/ViewFactoriesTest.kt @@ -23,7 +23,7 @@ import androidx.ui.test.createComposeRule import androidx.ui.test.findByText import com.squareup.workflow.ui.ViewEnvironment import com.squareup.workflow.ui.ViewRegistry -import com.squareup.workflow.ui.compose.bindCompose +import com.squareup.workflow.ui.compose.composedViewFactory import com.squareup.workflow.ui.compose.showRendering import com.squareup.workflow.ui.compose.withComposeViewFactoryRoot import org.junit.Rule @@ -54,7 +54,7 @@ class ViewFactoriesTest { private data class TestRendering(val text: String) private companion object { - val TestFactory = bindCompose { rendering, _ -> + val TestFactory = composedViewFactory { rendering, _ -> Text(rendering.text) } } diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeRendering.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeRendering.kt index 466fcafd51..7e1b97ce89 100644 --- a/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeRendering.kt +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeRendering.kt @@ -38,7 +38,7 @@ class ComposeRendering internal constructor( /** * A [ViewFactory] that renders a [ComposeRendering]. */ - val Factory: ViewFactory = bindCompose { rendering, environment -> + val Factory: ViewFactory = composedViewFactory { rendering, environment -> rendering.render(environment) } diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactory.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactory.kt index 6bbb6c7b13..a67cf1c9f9 100644 --- a/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactory.kt +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactory.kt @@ -43,7 +43,7 @@ import kotlin.reflect.KClass * * ``` * // Function references to @Composable functions aren't supported yet. - * val FooBinding = bindCompose { showFoo(it) } + * val FooBinding = composedViewFactory { showFoo(it) } * * @Composable * private fun showFoo(foo: FooRendering) { @@ -73,14 +73,14 @@ import kotlin.reflect.KClass * * ## Initializing Compose context * - * Often all the [bindCompose] factories in an app need to share some context – for example, certain + * Often all the [composedViewFactory]s in an app need to share some context – for example, certain * ambients need to be provided, such as `MaterialTheme`. To configure this shared context, include * a [ComposeViewFactoryRoot] in your top-level [ViewEnvironment] (e.g. by using - * [withComposeViewFactoryRoot]). The first time a [bindCompose] is used to show a rendering, its - * [showRendering] function will be wrapped with the [ComposeViewFactoryRoot]. See the documentation - * on [ComposeViewFactoryRoot] for more information. + * [withComposeViewFactoryRoot]). The first time a [composedViewFactory] is used to show a + * rendering, its [showRendering] function will be wrapped with the [ComposeViewFactoryRoot]. + * See the documentation on [ComposeViewFactoryRoot] for more information. */ -inline fun bindCompose( +inline fun composedViewFactory( noinline showRendering: @Composable() ( rendering: RenderingT, environment: ViewEnvironment diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactoryRoot.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactoryRoot.kt index 231636151c..34e4667e66 100644 --- a/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactoryRoot.kt +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactoryRoot.kt @@ -33,12 +33,12 @@ private val HasViewFactoryRootBeenApplied = staticAmbientOf { false } /** * A `@Composable` function that is stored in a [ViewEnvironment] and will be used to wrap the first - * [bindCompose] composition. This can be used to setup any ambients that all [bindCompose] - * factories need access to, such as ambients that specify the UI theme. + * [composedViewFactory] composition. This can be used to setup any ambients that all + * [composedViewFactory]s need access to, such as ambients that specify the UI theme. * - * This function will called once, to wrap the _highest-level_ [bindCompose] in the tree. However, - * ambients are propagated down to child [bindCompose] compositions, so any ambients provided here - * will be available in _all_ [bindCompose] compositions. + * This function will called once, to wrap the _highest-level_ [composedViewFactory] in the tree. + * However, ambients are propagated down to child [composedViewFactory] compositions, so any + * ambients provided here will be available in _all_ [composedViewFactory] compositions. */ interface ComposeViewFactoryRoot { @@ -51,7 +51,7 @@ interface ComposeViewFactoryRoot { /** * Adds a [ComposeViewFactoryRoot] to this [ViewEnvironment] that uses [wrapper] to wrap the first - * [bindCompose] composition. See [ComposeViewFactoryRoot] for more information. + * [composedViewFactory] composition. See [ComposeViewFactoryRoot] for more information. */ fun ViewEnvironment.withComposeViewFactoryRoot( wrapper: @Composable() (content: @Composable() () -> Unit) -> Unit diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeWorkflow.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeWorkflow.kt index 1a30bcb6f0..0af19c3f64 100644 --- a/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeWorkflow.kt +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeWorkflow.kt @@ -27,7 +27,7 @@ import com.squareup.workflow.ui.compose.internal.ComposeWorkflowImpl /** * A stateless [Workflow][com.squareup.workflow.Workflow] that [renders][render] itself as * [Composable] function. Effectively defines an inline - * [bindCompose][com.squareup.workflow.ui.compose.bindCompose]. + * [composedViewFactory][com.squareup.workflow.ui.compose.composedViewFactory]. * * This workflow does not have access to a [RenderContext][com.squareup.workflow.RenderContext] * since render contexts are only valid during render passes, and this workflow's [render] method diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/ViewEnvironments.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/ViewEnvironments.kt index cdb618f5f2..aabd9c4fb4 100644 --- a/core-compose/src/main/java/com/squareup/workflow/ui/compose/ViewEnvironments.kt +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/ViewEnvironments.kt @@ -38,7 +38,7 @@ import com.squareup.workflow.ui.compose.internal.showRendering * val child: Any * ) * - * val FramedContainerViewFactory = bindCompose { rendering, environment -> + * val FramedContainerViewFactory = composedViewFactory { rendering, environment -> * Surface(border = Border(rendering.borderColor, 8.dp)) { * environment.showRendering(rendering.child) * } diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ParentComposition.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ParentComposition.kt index 633530e8fd..3b205fbcee 100644 --- a/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ParentComposition.kt +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ParentComposition.kt @@ -68,11 +68,11 @@ internal fun ViewGroup.setOrSubcomposeContent( content: @Composable() () -> Unit ) { if (parentComposition != null) { - // Somewhere above us in the workflow rendering tree, there's another bindCompose factory. + // Somewhere above us in the workflow rendering tree, there's another composedViewFactory. // We need to link to its composition reference so we inherit its ambients. setContent(Recomposer.current(), parentComposition, content) } else { - // This is the first bindCompose factory in the rendering tree, so it won't be a child + // This is the first composedViewFactory in the rendering tree, so it won't be a child // composition. setContent(Recomposer.current(), content) } diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ViewFactories.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ViewFactories.kt index 70048c41a0..db33eb5541 100644 --- a/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ViewFactories.kt +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ViewFactories.kt @@ -38,7 +38,8 @@ import com.squareup.workflow.ui.showRendering * Renders [rendering] into the composition using the `ViewRegistry` from the [ViewEnvironment] to * determine how to draw it. * - * To display a nested rendering from a [Composable view binding][bindCompose], use + * To display a nested rendering from a + * [Composable view binding][com.squareup.workflow.ui.compose.composedViewFactory], use * [ViewEnvironment.showRendering]. * * *Note: [rendering] must be the same type as this [ViewFactory], even though the type system does diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ViewRegistries.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ViewRegistries.kt index 80f6f2aa02..15511c93e6 100644 --- a/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ViewRegistries.kt +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ViewRegistries.kt @@ -25,7 +25,7 @@ import com.squareup.workflow.ui.ViewRegistry /** * Renders [rendering] into the composition using this [ViewRegistry] to determine how to draw it. * - * To display a nested rendering from a [Composable view binding][bindCompose], use + * To display a nested rendering from a [Composable view binding][composedViewFactory], use * [ViewEnvironment.showRendering]. * * @see ViewEnvironment.showRendering diff --git a/samples/hello-compose-binding/src/main/java/com/squareup/sample/hellocomposebinding/HelloBinding.kt b/samples/hello-compose-binding/src/main/java/com/squareup/sample/hellocomposebinding/HelloBinding.kt index 1f06ed116d..f51613abe2 100644 --- a/samples/hello-compose-binding/src/main/java/com/squareup/sample/hellocomposebinding/HelloBinding.kt +++ b/samples/hello-compose-binding/src/main/java/com/squareup/sample/hellocomposebinding/HelloBinding.kt @@ -25,10 +25,10 @@ import androidx.ui.layout.wrapContentSize import androidx.ui.material.ripple.ripple import androidx.ui.tooling.preview.Preview import com.squareup.sample.hellocomposebinding.HelloWorkflow.Rendering -import com.squareup.workflow.ui.compose.bindCompose +import com.squareup.workflow.ui.compose.composedViewFactory import com.squareup.workflow.ui.compose.tooling.preview -val HelloBinding = bindCompose { rendering, _ -> +val HelloBinding = composedViewFactory { rendering, _ -> Clickable( modifier = Modifier.fillMaxSize() .ripple(bounded = true), diff --git a/samples/hello-compose/src/main/java/com/squareup/sample/hellocompose/HelloBinding.kt b/samples/hello-compose/src/main/java/com/squareup/sample/hellocompose/HelloBinding.kt index 277c0590f2..57d19a875c 100644 --- a/samples/hello-compose/src/main/java/com/squareup/sample/hellocompose/HelloBinding.kt +++ b/samples/hello-compose/src/main/java/com/squareup/sample/hellocompose/HelloBinding.kt @@ -23,9 +23,9 @@ import androidx.ui.layout.fillMaxSize import androidx.ui.layout.wrapContentSize import androidx.ui.material.ripple.ripple import com.squareup.sample.hellocompose.HelloWorkflow.Rendering -import com.squareup.workflow.ui.compose.bindCompose +import com.squareup.workflow.ui.compose.composedViewFactory -val HelloBinding = bindCompose { rendering, _ -> +val HelloBinding = composedViewFactory { rendering, _ -> Clickable( onClick = { rendering.onClick() }, modifier = Modifier.ripple(bounded = true) diff --git a/samples/nested-renderings/src/main/java/com/squareup/sample/nestedrenderings/RecursiveViewFactory.kt b/samples/nested-renderings/src/main/java/com/squareup/sample/nestedrenderings/RecursiveViewFactory.kt index 48bbd88db2..54b078a226 100644 --- a/samples/nested-renderings/src/main/java/com/squareup/sample/nestedrenderings/RecursiveViewFactory.kt +++ b/samples/nested-renderings/src/main/java/com/squareup/sample/nestedrenderings/RecursiveViewFactory.kt @@ -39,7 +39,7 @@ import androidx.ui.res.dimensionResource import androidx.ui.tooling.preview.Preview import com.squareup.sample.nestedrenderings.RecursiveWorkflow.Rendering import com.squareup.workflow.ui.ViewEnvironment -import com.squareup.workflow.ui.compose.bindCompose +import com.squareup.workflow.ui.compose.composedViewFactory import com.squareup.workflow.ui.compose.showRendering import com.squareup.workflow.ui.compose.tooling.preview @@ -51,7 +51,7 @@ val BackgroundColorAmbient = ambientOf { error("No background color speci /** * A `ViewFactory` that renders [RecursiveWorkflow.Rendering]s. */ -val RecursiveViewFactory = bindCompose { rendering, viewEnvironment -> +val RecursiveViewFactory = composedViewFactory { rendering, viewEnvironment -> // Every child should be drawn with a slightly-darker background color. val color = BackgroundColorAmbient.current val childColor = remember(color) { From 49228bdba9ffbeb666f63b24f9e3f27c572a4d66 Mon Sep 17 00:00:00 2001 From: Zach Klippenstein Date: Tue, 19 May 2020 17:13:11 -0700 Subject: [PATCH 26/67] Rename showRendering to WorkflowRendering and make it not an extension function. Fixes #21, see that issue for rationale. --- .../tooling/PreviewComposeWorkflowTest.kt | 10 ++--- .../compose/tooling/PreviewViewFactoryTest.kt | 21 +++++----- .../compose/tooling/PreviewViewEnvironment.kt | 2 +- .../ui/compose/tooling/ViewFactories.kt | 4 +- core-compose/api/core-compose.api | 4 +- ...istriesTest.kt => ViewEnvironmentsTest.kt} | 13 +++--- .../ui/compose/internal/ViewFactoriesTest.kt | 6 +-- .../workflow/ui/compose/ComposeViewFactory.kt | 4 +- .../workflow/ui/compose/ViewEnvironments.kt | 15 ++++--- .../ui/compose/internal/ParentComposition.kt | 4 +- .../ui/compose/internal/ViewFactories.kt | 15 +++---- .../ui/compose/internal/ViewRegistries.kt | 42 ------------------- .../com/squareup/sample/hellocompose/App.kt | 5 ++- .../nestedrenderings/RecursiveViewFactory.kt | 5 ++- 14 files changed, 56 insertions(+), 94 deletions(-) rename core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/{internal/ViewRegistriesTest.kt => ViewEnvironmentsTest.kt} (78%) delete mode 100644 core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ViewRegistries.kt diff --git a/compose-tooling/src/androidTest/java/com/squareup/workflow/ui/compose/tooling/PreviewComposeWorkflowTest.kt b/compose-tooling/src/androidTest/java/com/squareup/workflow/ui/compose/tooling/PreviewComposeWorkflowTest.kt index 8b9b542ec7..1c09b99400 100644 --- a/compose-tooling/src/androidTest/java/com/squareup/workflow/ui/compose/tooling/PreviewComposeWorkflowTest.kt +++ b/compose-tooling/src/androidTest/java/com/squareup/workflow/ui/compose/tooling/PreviewComposeWorkflowTest.kt @@ -31,9 +31,9 @@ import androidx.ui.test.findByText import androidx.ui.tooling.preview.Preview import androidx.ui.unit.dp import com.squareup.workflow.Workflow -import com.squareup.workflow.ui.compose.composed import com.squareup.workflow.ui.ViewEnvironmentKey -import com.squareup.workflow.ui.compose.showRendering +import com.squareup.workflow.ui.compose.WorkflowRendering +import com.squareup.workflow.ui.compose.composed import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -98,7 +98,7 @@ class PreviewComposeWorkflowTest { Column { Text(props.first) Semantics(container = true, mergeAllDescendants = true) { - environment.showRendering(rendering = props.second) + WorkflowRendering(props.second, environment) } } } @@ -111,11 +111,11 @@ class PreviewComposeWorkflowTest { Workflow.composed, Nothing> { props, _, environment -> Column { Semantics(container = true) { - environment.showRendering(rendering = props.first) + WorkflowRendering(rendering = props.first, viewEnvironment = environment) } Text(props.second) Semantics(container = true) { - environment.showRendering(rendering = props.third) + WorkflowRendering(rendering = props.third, viewEnvironment = environment) } } } diff --git a/compose-tooling/src/androidTest/java/com/squareup/workflow/ui/compose/tooling/PreviewViewFactoryTest.kt b/compose-tooling/src/androidTest/java/com/squareup/workflow/ui/compose/tooling/PreviewViewFactoryTest.kt index b09ff3bdfa..1d4b44f3d6 100644 --- a/compose-tooling/src/androidTest/java/com/squareup/workflow/ui/compose/tooling/PreviewViewFactoryTest.kt +++ b/compose-tooling/src/androidTest/java/com/squareup/workflow/ui/compose/tooling/PreviewViewFactoryTest.kt @@ -31,8 +31,8 @@ import androidx.ui.test.findByText import androidx.ui.tooling.preview.Preview import androidx.ui.unit.dp import com.squareup.workflow.ui.ViewEnvironmentKey +import com.squareup.workflow.ui.compose.WorkflowRendering import com.squareup.workflow.ui.compose.composedViewFactory -import com.squareup.workflow.ui.compose.showRendering import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -99,14 +99,15 @@ class PreviewViewFactoryTest { findByText("foo").assertIsDisplayed() } - private val ParentWithOneChild = composedViewFactory> { rendering, environment -> - Column { - Text(rendering.first) - Semantics(container = true, mergeAllDescendants = true) { - environment.showRendering(rendering = rendering.second) + private val ParentWithOneChild = + composedViewFactory> { rendering, environment -> + Column { + Text(rendering.first) + Semantics(container = true, mergeAllDescendants = true) { + WorkflowRendering(rendering.second, environment) + } } } - } @Preview @Composable private fun ParentWithOneChildPreview() { ParentWithOneChild.preview(Pair("one", "two")) @@ -116,11 +117,11 @@ class PreviewViewFactoryTest { composedViewFactory> { rendering, environment -> Column { Semantics(container = true) { - environment.showRendering(rendering = rendering.first) + WorkflowRendering(rendering.first, environment) } Text(rendering.second) Semantics(container = true) { - environment.showRendering(rendering = rendering.third) + WorkflowRendering(rendering.third, environment) } } } @@ -139,7 +140,7 @@ class PreviewViewFactoryTest { Text(rendering.text) rendering.child?.let { child -> Semantics(container = true) { - environment.showRendering(rendering = child) + WorkflowRendering(rendering = child, viewEnvironment = environment) } } } diff --git a/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/PreviewViewEnvironment.kt b/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/PreviewViewEnvironment.kt index 05a86cf86b..7693e8b53f 100644 --- a/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/PreviewViewEnvironment.kt +++ b/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/PreviewViewEnvironment.kt @@ -50,7 +50,7 @@ import kotlin.reflect.KClass /** * A [ViewRegistry] that uses [mainFactory] for rendering [RenderingT]s, and [placeholderFactory] - * for all other [showRendering][com.squareup.workflow.ui.compose.showRendering] calls. + * for all other [WorkflowRendering][com.squareup.workflow.ui.compose.WorkflowRendering] calls. */ @Immutable private class PreviewViewRegistry( diff --git a/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/ViewFactories.kt b/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/ViewFactories.kt index 4314486f60..fe9f55a228 100644 --- a/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/ViewFactories.kt +++ b/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/ViewFactories.kt @@ -22,7 +22,7 @@ import androidx.ui.core.Modifier import com.squareup.workflow.ui.ViewEnvironment import com.squareup.workflow.ui.ViewFactory import com.squareup.workflow.ui.ViewRegistry -import com.squareup.workflow.ui.compose.showRendering +import com.squareup.workflow.ui.compose.WorkflowRendering /** * Draws this [ViewFactory] using a special preview [ViewRegistry]. @@ -48,5 +48,5 @@ import com.squareup.workflow.ui.compose.showRendering ) { val previewEnvironment = previewViewEnvironment(placeholderModifier, viewEnvironmentUpdater, mainFactory = this) - previewEnvironment.showRendering(rendering, modifier) + WorkflowRendering(rendering, previewEnvironment, modifier) } diff --git a/core-compose/api/core-compose.api b/core-compose/api/core-compose.api index 9e59342691..a2a16ed711 100644 --- a/core-compose/api/core-compose.api +++ b/core-compose/api/core-compose.api @@ -44,8 +44,8 @@ public final class com/squareup/workflow/ui/compose/ComposeWorkflowKt { } public final class com/squareup/workflow/ui/compose/ViewEnvironmentsKt { - public static final fun showRendering (Lcom/squareup/workflow/ui/ViewEnvironment;Ljava/lang/Object;Landroidx/ui/core/Modifier;Landroidx/compose/Composer;)V - public static synthetic fun showRendering$default (Lcom/squareup/workflow/ui/ViewEnvironment;Ljava/lang/Object;Landroidx/ui/core/Modifier;Landroidx/compose/Composer;ILjava/lang/Object;)V + public static final fun WorkflowRendering (Ljava/lang/Object;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/ui/core/Modifier;Landroidx/compose/Composer;)V + public static synthetic fun WorkflowRendering$default (Ljava/lang/Object;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/ui/core/Modifier;Landroidx/compose/Composer;ILjava/lang/Object;)V } public final class com/squareup/workflow/ui/compose/WorkflowContainerKt { diff --git a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/internal/ViewRegistriesTest.kt b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/ViewEnvironmentsTest.kt similarity index 78% rename from core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/internal/ViewRegistriesTest.kt rename to core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/ViewEnvironmentsTest.kt index f6302b123f..364e36795d 100644 --- a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/internal/ViewRegistriesTest.kt +++ b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/ViewEnvironmentsTest.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.squareup.workflow.ui.compose.internal +package com.squareup.workflow.ui.compose import androidx.compose.FrameManager import androidx.compose.mutableStateOf @@ -24,27 +24,26 @@ import androidx.ui.test.createComposeRule import androidx.ui.test.findByText import com.squareup.workflow.ui.ViewEnvironment import com.squareup.workflow.ui.ViewRegistry -import com.squareup.workflow.ui.compose.bindCompose import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) -class ViewRegistriesTest { +class ViewEnvironmentsTest { @Rule @JvmField val composeRule = createComposeRule() - @Test fun showRendering_recomposes_whenFactoryChanged() { - val registry1 = ViewRegistry(bindCompose { rendering, _ -> + @Test fun workflowRendering_recomposes_whenFactoryChanged() { + val registry1 = ViewRegistry(composedViewFactory { rendering, _ -> Text(rendering) }) - val registry2 = ViewRegistry(bindCompose { rendering, _ -> + val registry2 = ViewRegistry(composedViewFactory { rendering, _ -> Text(rendering.reversed()) }) val registry = mutableStateOf(registry1) composeRule.setContent { - registry.value.showRendering("hello", ViewEnvironment(registry.value)) + WorkflowRendering("hello", ViewEnvironment(registry.value)) } findByText("hello").assertIsDisplayed() diff --git a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/internal/ViewFactoriesTest.kt b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/internal/ViewFactoriesTest.kt index 110a76b53e..998e6d7542 100644 --- a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/internal/ViewFactoriesTest.kt +++ b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/internal/ViewFactoriesTest.kt @@ -24,7 +24,7 @@ import androidx.ui.test.findByText import com.squareup.workflow.ui.ViewEnvironment import com.squareup.workflow.ui.ViewRegistry import com.squareup.workflow.ui.compose.composedViewFactory -import com.squareup.workflow.ui.compose.showRendering +import com.squareup.workflow.ui.compose.WorkflowRendering import com.squareup.workflow.ui.compose.withComposeViewFactoryRoot import org.junit.Rule import org.junit.Test @@ -35,7 +35,7 @@ class ViewFactoriesTest { @Rule @JvmField val composeRule = createComposeRule() - @Test fun showRendering_wrapsFactoryWithRoot_whenAlreadyInComposition() { + @Test fun WorkflowRendering_wrapsFactoryWithRoot_whenAlreadyInComposition() { val viewEnvironment = ViewEnvironment(ViewRegistry(TestFactory)) .withComposeViewFactoryRoot { content -> Column { @@ -45,7 +45,7 @@ class ViewFactoriesTest { } composeRule.setContent { - viewEnvironment.showRendering(TestRendering("two")) + WorkflowRendering(TestRendering("two"), viewEnvironment) } findByText("one\ntwo").assertIsDisplayed() diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactory.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactory.kt index a67cf1c9f9..bc8fc80583 100644 --- a/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactory.kt +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactory.kt @@ -64,8 +64,8 @@ import kotlin.reflect.KClass * renderings using the [ViewRegistry][com.squareup.workflow.ui.ViewRegistry]. * * View factories defined using this function may also show nested renderings. Doing so is as simple - * as calling [ViewEnvironment.showRendering] and passing in the nested rendering. See the kdoc on - * that function for an example. + * as calling [WorkflowRendering] and passing in the nested rendering. See the kdoc on that function + * for an example. * * Nested renderings will have access to any ambients defined in outer composable, even if there are * legacy views in between them, as long as the [ViewEnvironment] is propagated continuously between diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/ViewEnvironments.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/ViewEnvironments.kt index aabd9c4fb4..d9bb433ca8 100644 --- a/core-compose/src/main/java/com/squareup/workflow/ui/compose/ViewEnvironments.kt +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/ViewEnvironments.kt @@ -20,7 +20,7 @@ import androidx.compose.remember import androidx.ui.core.Modifier import com.squareup.workflow.ui.ViewEnvironment import com.squareup.workflow.ui.ViewRegistry -import com.squareup.workflow.ui.compose.internal.showRendering +import com.squareup.workflow.ui.compose.internal.WorkflowRendering /** * Renders [rendering] into the composition using this [ViewEnvironment]'s @@ -40,7 +40,7 @@ import com.squareup.workflow.ui.compose.internal.showRendering * * val FramedContainerViewFactory = composedViewFactory { rendering, environment -> * Surface(border = Border(rendering.borderColor, 8.dp)) { - * environment.showRendering(rendering.child) + * WorkflowRendering(rendering.child, environment) * } * } * ``` @@ -52,10 +52,15 @@ import com.squareup.workflow.ui.compose.internal.showRendering * * @throws IllegalArgumentException if no factory can be found for [rendering]'s type. */ -@Composable fun ViewEnvironment.showRendering( +@Composable fun WorkflowRendering( rendering: Any, + viewEnvironment: ViewEnvironment, modifier: Modifier = Modifier ) { - val viewRegistry = remember(this) { this[ViewRegistry] } - viewRegistry.showRendering(rendering, this, modifier) + val viewRegistry = remember(viewEnvironment) { viewEnvironment[ViewRegistry] } + val renderingType = rendering::class + val viewFactory = remember(viewRegistry, renderingType) { + viewRegistry.getFactoryFor(renderingType) + } + WorkflowRendering(rendering, viewFactory, viewEnvironment, modifier) } diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ParentComposition.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ParentComposition.kt index 3b205fbcee..454365def5 100644 --- a/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ParentComposition.kt +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ParentComposition.kt @@ -30,9 +30,9 @@ import com.squareup.workflow.ui.ViewEnvironmentKey * Holds a [CompositionReference] and that can be passed to [setOrSubcomposeContent] to create a * composition that is a child of another composition. Subcompositions get ambients and other * compose context from their parent, and propagate invalidations, which allows ambients provided - * around a [showRendering] call to be read by nested Compose-based view factories. + * around a [WorkflowRendering] call to be read by nested Compose-based view factories. * - * When [showRendering] is called, it will store an instance of this class in the [ViewEnvironment]. + * When [WorkflowRendering] is called, it will store an instance of this class in the [ViewEnvironment]. * [ComposeViewFactory] pulls the reference out of the environment and uses it to link its * composition to the outer one. */ diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ViewFactories.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ViewFactories.kt index db33eb5541..dab4d8859b 100644 --- a/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ViewFactories.kt +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ViewFactories.kt @@ -35,27 +35,24 @@ import com.squareup.workflow.ui.compose.ComposeViewFactory import com.squareup.workflow.ui.showRendering /** - * Renders [rendering] into the composition using the `ViewRegistry` from the [ViewEnvironment] to - * determine how to draw it. + * Renders [rendering] into the composition using [viewFactory]. * * To display a nested rendering from a - * [Composable view binding][com.squareup.workflow.ui.compose.composedViewFactory], use - * [ViewEnvironment.showRendering]. + * [Composable view binding][com.squareup.workflow.ui.compose.composedViewFactory], use the overload + * without a [ViewFactory] parameter. * * *Note: [rendering] must be the same type as this [ViewFactory], even though the type system does * not enforce this constraint. This is due to a Compose compiler bug tracked * [here](https://issuetracker.google.com/issues/156527332). * - * @see ViewEnvironment.showRendering - * @see com.squareup.workflow.ui.ViewRegistry.showRendering + * @see com.squareup.workflow.ui.compose.WorkflowRendering */ -// TODO(https://issuetracker.google.com/issues/156527332) Should be ViewFactory -@Composable internal fun ViewFactory.showRendering( +@Composable internal fun WorkflowRendering( rendering: RenderingT, + viewFactory: ViewFactory, viewEnvironment: ViewEnvironment, modifier: Modifier = Modifier ) { - val viewFactory = this Box(modifier = modifier) { // "Fast" path: If the child binding is also a Composable, we don't need to go through the // legacy view system and can just invoke the binding's composable function directly. diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ViewRegistries.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ViewRegistries.kt deleted file mode 100644 index 4fbd747d33..0000000000 --- a/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ViewRegistries.kt +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2020 Square Inc. - * - * 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.squareup.workflow.ui.compose.internal - -import androidx.compose.Composable -import androidx.compose.remember -import androidx.ui.core.Modifier -import com.squareup.workflow.ui.ViewEnvironment -import com.squareup.workflow.ui.ViewFactory -import com.squareup.workflow.ui.ViewRegistry - -/** - * Renders [rendering] into the composition using this [ViewRegistry] to determine how to draw it. - * - * To display a nested rendering from a [Composable view binding][composedViewFactory], use - * [ViewEnvironment.showRendering]. - * - * @see ViewEnvironment.showRendering - * @see ViewFactory.showRendering - */ -@Composable internal fun ViewRegistry.showRendering( - rendering: Any, - hints: ViewEnvironment, - modifier: Modifier = Modifier -) { - val renderingType = rendering::class - val viewFactory = remember(this, renderingType) { getFactoryFor(renderingType) } - viewFactory.showRendering(rendering, hints, modifier) -} diff --git a/samples/hello-compose/src/main/java/com/squareup/sample/hellocompose/App.kt b/samples/hello-compose/src/main/java/com/squareup/sample/hellocompose/App.kt index c6676a2430..f778326e47 100644 --- a/samples/hello-compose/src/main/java/com/squareup/sample/hellocompose/App.kt +++ b/samples/hello-compose/src/main/java/com/squareup/sample/hellocompose/App.kt @@ -27,7 +27,7 @@ import com.squareup.workflow.diagnostic.SimpleLoggingDiagnosticListener import com.squareup.workflow.ui.ViewEnvironment import com.squareup.workflow.ui.ViewRegistry import com.squareup.workflow.ui.compose.WorkflowContainer -import com.squareup.workflow.ui.compose.showRendering +import com.squareup.workflow.ui.compose.WorkflowRendering private val viewRegistry = ViewRegistry(HelloBinding) private val viewEnvironment = ViewEnvironment(viewRegistry) @@ -38,8 +38,9 @@ private val viewEnvironment = ViewEnvironment(viewRegistry) diagnosticListener = SimpleLoggingDiagnosticListener() ) { rendering -> MaterialTheme { - viewEnvironment.showRendering( + WorkflowRendering( rendering, + viewEnvironment, modifier = Modifier.drawBorder( shape = RoundedCornerShape(10.dp), size = 10.dp, diff --git a/samples/nested-renderings/src/main/java/com/squareup/sample/nestedrenderings/RecursiveViewFactory.kt b/samples/nested-renderings/src/main/java/com/squareup/sample/nestedrenderings/RecursiveViewFactory.kt index 54b078a226..5bbbd7d6d5 100644 --- a/samples/nested-renderings/src/main/java/com/squareup/sample/nestedrenderings/RecursiveViewFactory.kt +++ b/samples/nested-renderings/src/main/java/com/squareup/sample/nestedrenderings/RecursiveViewFactory.kt @@ -40,7 +40,7 @@ import androidx.ui.tooling.preview.Preview import com.squareup.sample.nestedrenderings.RecursiveWorkflow.Rendering import com.squareup.workflow.ui.ViewEnvironment import com.squareup.workflow.ui.compose.composedViewFactory -import com.squareup.workflow.ui.compose.showRendering +import com.squareup.workflow.ui.compose.WorkflowRendering import com.squareup.workflow.ui.compose.tooling.preview /** @@ -109,10 +109,11 @@ val RecursiveViewFactory = composedViewFactory { rendering, viewEnvir horizontalGravity = CenterHorizontally ) { children.forEach { childRendering -> - viewEnvironment.showRendering( + WorkflowRendering( childRendering, // Pass a weight so all children are partitioned evenly within the total column space. // Without the weight, each child is the full size of the parent. + viewEnvironment, modifier = Modifier.weight(1f, true) .padding(dimensionResource(R.dimen.recursive_padding)) ) From ea57ae0e05556aacdea52034d1f5af38f0ac6394 Mon Sep 17 00:00:00 2001 From: Zach Klippenstein Date: Mon, 18 May 2020 13:32:27 -0700 Subject: [PATCH 27/67] Renames ComposeViewFactoryRoot to CompositionRoot and decouples the implementation. The root is now applied via a `ViewRegistry` wrapper that wraps individual factories to apply the root, instead of having this logic hard-coded inside `ComposeViewFactory`. This approach is more flexible in general (could be used to do other tricks), and decouples the rooting feature from the rest of the code. --- core-compose/api/core-compose.api | 24 +-- .../ui/compose/ComposeViewFactoryTest.kt | 2 +- ...toryRootTest.kt => CompositionRootTest.kt} | 112 ++++++++++++-- .../internal/ComposeViewFactoryRootTest.kt | 142 ------------------ .../ui/compose/internal/ViewFactoriesTest.kt | 4 +- .../workflow/ui/compose/ComposeViewFactory.kt | 28 +--- .../ui/compose/ComposeViewFactoryRoot.kt | 100 ------------ .../workflow/ui/compose/CompositionRoot.kt | 110 ++++++++++++++ .../internal/SafeComposeViewFactoryRoot.kt | 42 ------ .../ui/compose/internal/ViewFactories.kt | 2 +- .../ui/compose/internal/ViewRegistries.kt | 42 ++++++ .../HelloBindingActivity.kt | 4 +- .../NestedRenderingsActivity.kt | 4 +- 13 files changed, 273 insertions(+), 343 deletions(-) rename core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/{internal/SafeComposeViewFactoryRootTest.kt => CompositionRootTest.kt} (50%) delete mode 100644 core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/internal/ComposeViewFactoryRootTest.kt delete mode 100644 core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactoryRoot.kt create mode 100644 core-compose/src/main/java/com/squareup/workflow/ui/compose/CompositionRoot.kt delete mode 100644 core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/SafeComposeViewFactoryRoot.kt create mode 100644 core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ViewRegistries.kt diff --git a/core-compose/api/core-compose.api b/core-compose/api/core-compose.api index a2a16ed711..594f3674ad 100644 --- a/core-compose/api/core-compose.api +++ b/core-compose/api/core-compose.api @@ -15,24 +15,6 @@ public final class com/squareup/workflow/ui/compose/ComposeViewFactory : com/squ public fun getType ()Lkotlin/reflect/KClass; } -public abstract interface class com/squareup/workflow/ui/compose/ComposeViewFactoryRoot { - public static final field Companion Lcom/squareup/workflow/ui/compose/ComposeViewFactoryRoot$Companion; - public static fun ()V - public abstract fun wrap (Lkotlin/jvm/functions/Function1;Landroidx/compose/Composer;)V -} - -public final class com/squareup/workflow/ui/compose/ComposeViewFactoryRoot$Companion : com/squareup/workflow/ui/ViewEnvironmentKey { - public static final fun ()V - public fun getDefault ()Lcom/squareup/workflow/ui/compose/ComposeViewFactoryRoot; - public synthetic fun getDefault ()Ljava/lang/Object; -} - -public final class com/squareup/workflow/ui/compose/ComposeViewFactoryRootKt { - public static final fun ()V - public static final fun ComposeViewFactoryRoot (Lkotlin/jvm/functions/Function2;)Lcom/squareup/workflow/ui/compose/ComposeViewFactoryRoot; - public static final fun withComposeViewFactoryRoot (Lcom/squareup/workflow/ui/ViewEnvironment;Lkotlin/jvm/functions/Function2;)Lcom/squareup/workflow/ui/ViewEnvironment; -} - public abstract class com/squareup/workflow/ui/compose/ComposeWorkflow : com/squareup/workflow/Workflow { public fun ()V public fun asStatefulWorkflow ()Lcom/squareup/workflow/StatefulWorkflow; @@ -43,6 +25,12 @@ public final class com/squareup/workflow/ui/compose/ComposeWorkflowKt { public static final fun composed (Lcom/squareup/workflow/Workflow$Companion;Lkotlin/jvm/functions/Function4;)Lcom/squareup/workflow/ui/compose/ComposeWorkflow; } +public final class com/squareup/workflow/ui/compose/CompositionRootKt { + public static final fun ()V + public static final fun withCompositionRoot (Lcom/squareup/workflow/ui/ViewEnvironment;Lkotlin/jvm/functions/Function2;)Lcom/squareup/workflow/ui/ViewEnvironment; + public static final fun withCompositionRoot (Lcom/squareup/workflow/ui/ViewRegistry;Lkotlin/jvm/functions/Function2;)Lcom/squareup/workflow/ui/ViewRegistry; +} + public final class com/squareup/workflow/ui/compose/ViewEnvironmentsKt { public static final fun WorkflowRendering (Ljava/lang/Object;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/ui/core/Modifier;Landroidx/compose/Composer;)V public static synthetic fun WorkflowRendering$default (Ljava/lang/Object;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/ui/core/Modifier;Landroidx/compose/Composer;ILjava/lang/Object;)V diff --git a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/ComposeViewFactoryTest.kt b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/ComposeViewFactoryTest.kt index af6a87c871..27f84d263f 100644 --- a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/ComposeViewFactoryTest.kt +++ b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/ComposeViewFactoryTest.kt @@ -40,7 +40,7 @@ class ComposeViewFactoryTest { @Test fun wrapsFactoryWithRoot() { val wrapperText = mutableStateOf("one") val viewEnvironment = ViewEnvironment(ViewRegistry(TestFactory)) - .withComposeViewFactoryRoot { content -> + .withCompositionRoot { content -> Column { Text(wrapperText.value) content() diff --git a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/internal/SafeComposeViewFactoryRootTest.kt b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/CompositionRootTest.kt similarity index 50% rename from core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/internal/SafeComposeViewFactoryRootTest.kt rename to core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/CompositionRootTest.kt index 794caad872..a900a1056c 100644 --- a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/internal/SafeComposeViewFactoryRootTest.kt +++ b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/CompositionRootTest.kt @@ -13,8 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.squareup.workflow.ui.compose.internal +package com.squareup.workflow.ui.compose +import androidx.compose.FrameManager +import androidx.compose.mutableStateOf import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.ui.foundation.Text import androidx.ui.layout.Column @@ -23,28 +25,114 @@ import androidx.ui.test.assertIsDisplayed import androidx.ui.test.createComposeRule import androidx.ui.test.findByText import com.google.common.truth.Truth.assertThat -import com.squareup.workflow.ui.compose.ComposeViewFactoryRoot import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import kotlin.test.assertFailsWith @RunWith(AndroidJUnit4::class) -class SafeComposeViewFactoryRootTest { +class CompositionRootTest { @Rule @JvmField val composeRule = createComposeRule() + @Test fun wrapWithRootIfNecessary_wrapsWhenNecessary() { + val root: CompositionRoot = { content -> + Column { + Text("one") + content() + } + } + + composeRule.setContent { + wrapWithRootIfNecessary(root) { + Text("two") + } + } + + findByText("one\ntwo").assertIsDisplayed() + } + + @Test fun wrapWithRootIfNecessary_onlyWrapsOnce() { + val root: CompositionRoot = { content -> + Column { + Text("one") + content() + } + } + + composeRule.setContent { + wrapWithRootIfNecessary(root) { + Text("two") + wrapWithRootIfNecessary(root) { + Text("three") + } + } + } + + findByText("one\ntwo\nthree").assertIsDisplayed() + } + + @Test fun wrapWithRootIfNecessary_seesUpdatesFromRootWrapper() { + val wrapperText = mutableStateOf("one") + val root: CompositionRoot = { content -> + Column { + Text(wrapperText.value) + content() + } + } + + composeRule.setContent { + wrapWithRootIfNecessary(root) { + Text("two") + } + } + + findByText("one\ntwo").assertIsDisplayed() + FrameManager.framed { + wrapperText.value = "ENO" + } + findByText("ENO\ntwo").assertIsDisplayed() + } + + @Test fun wrapWithRootIfNecessary_rewrapsWhenDifferentRoot() { + val root1: CompositionRoot = { content -> + Column { + Text("one") + content() + } + } + val root2: CompositionRoot = { content -> + Column { + Text("ENO") + content() + } + } + val viewEnvironment = mutableStateOf(root1) + + composeRule.setContent { + wrapWithRootIfNecessary(viewEnvironment.value) { + Text("two") + } + } + + findByText("one\ntwo").assertIsDisplayed() + FrameManager.framed { + viewEnvironment.value = root2 + } + findByText("ENO\ntwo").assertIsDisplayed() + } + @Test fun safeComposeViewFactoryRoot_wraps_content() { - val wrapped = ComposeViewFactoryRoot { content -> + val wrapped: CompositionRoot = { content -> Column { Text("Parent") content() } } - val safeRoot = SafeComposeViewFactoryRoot(wrapped) + val safeRoot = safeCompositionRoot(wrapped) composeRule.setContent { - safeRoot.wrap { + safeRoot { // Need an explicit semantics container, otherwise both Texts will be merged into a single // Semantics object with the text "Parent\nChild". Semantics(container = true) { @@ -58,12 +146,12 @@ class SafeComposeViewFactoryRootTest { } @Test fun safeComposeViewFactoryRoot_throws_whenChildrenNotInvoked() { - val wrapped = ComposeViewFactoryRoot { } - val safeRoot = SafeComposeViewFactoryRoot(wrapped) + val wrapped: CompositionRoot = { } + val safeRoot = safeCompositionRoot(wrapped) val error = assertFailsWith { composeRule.setContent { - safeRoot.wrap {} + safeRoot {} } } @@ -74,15 +162,15 @@ class SafeComposeViewFactoryRootTest { } @Test fun safeComposeViewFactoryRoot_throws_whenChildrenInvokedMultipleTimes() { - val wrapped = ComposeViewFactoryRoot { children -> + val wrapped: CompositionRoot = { children -> children() children() } - val safeRoot = SafeComposeViewFactoryRoot(wrapped) + val safeRoot = safeCompositionRoot(wrapped) val error = assertFailsWith { composeRule.setContent { - safeRoot.wrap { + safeRoot { Text("Hello") } } diff --git a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/internal/ComposeViewFactoryRootTest.kt b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/internal/ComposeViewFactoryRootTest.kt deleted file mode 100644 index 453c254571..0000000000 --- a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/internal/ComposeViewFactoryRootTest.kt +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright 2020 Square Inc. - * - * 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.squareup.workflow.ui.compose.internal - -import androidx.compose.FrameManager -import androidx.compose.mutableStateOf -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.ui.foundation.Text -import androidx.ui.layout.Column -import androidx.ui.test.assertIsDisplayed -import androidx.ui.test.createComposeRule -import androidx.ui.test.findByText -import com.squareup.workflow.ui.ViewEnvironment -import com.squareup.workflow.ui.ViewRegistry -import com.squareup.workflow.ui.compose.withComposeViewFactoryRoot -import com.squareup.workflow.ui.compose.wrapWithRootIfNecessary -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith - -@RunWith(AndroidJUnit4::class) -class ComposeViewFactoryRootTest { - - @Rule @JvmField val composeRule = createComposeRule() - - @Test fun wrapWithRootIfNecessary_handlesNoRoot() { - val viewEnvironment = ViewEnvironment(ViewRegistry()) - - composeRule.setContent { - wrapWithRootIfNecessary(viewEnvironment) { - Text("foo") - } - } - - findByText("foo").assertIsDisplayed() - } - - @Test fun wrapWithRootIfNecessary_wrapsWhenNecessary() { - val viewEnvironment = ViewEnvironment(ViewRegistry()) - .withComposeViewFactoryRoot { content -> - Column { - Text("one") - content() - } - } - - composeRule.setContent { - wrapWithRootIfNecessary(viewEnvironment) { - Text("two") - } - } - - findByText("one\ntwo").assertIsDisplayed() - } - - @Test fun wrapWithRootIfNecessary_onlyWrapsOnce() { - val viewEnvironment = ViewEnvironment(ViewRegistry()) - .withComposeViewFactoryRoot { content -> - Column { - Text("one") - content() - } - } - - composeRule.setContent { - wrapWithRootIfNecessary(viewEnvironment) { - Text("two") - wrapWithRootIfNecessary(viewEnvironment) { - Text("three") - } - } - } - - findByText("one\ntwo\nthree").assertIsDisplayed() - } - - @Test fun wrapWithRootIfNecessary_seesUpdatesFromRootWrapper() { - val wrapperText = mutableStateOf("one") - val viewEnvironment = ViewEnvironment(ViewRegistry()) - .withComposeViewFactoryRoot { content -> - Column { - Text(wrapperText.value) - content() - } - } - - composeRule.setContent { - wrapWithRootIfNecessary(viewEnvironment) { - Text("two") - } - } - - findByText("one\ntwo").assertIsDisplayed() - FrameManager.framed { - wrapperText.value = "ENO" - } - findByText("ENO\ntwo").assertIsDisplayed() - } - - @Test fun wrapWithRootIfNecessary_rewrapsWhenDifferentRoot() { - val viewEnvironment1 = ViewEnvironment(ViewRegistry()) - .withComposeViewFactoryRoot { content -> - Column { - Text("one") - content() - } - } - val viewEnvironment2 = ViewEnvironment(ViewRegistry()) - .withComposeViewFactoryRoot { content -> - Column { - Text("ENO") - content() - } - } - val viewEnvironment = mutableStateOf(viewEnvironment1) - - composeRule.setContent { - wrapWithRootIfNecessary(viewEnvironment.value) { - Text("two") - } - } - - findByText("one\ntwo").assertIsDisplayed() - FrameManager.framed { - viewEnvironment.value = viewEnvironment2 - } - findByText("ENO\ntwo").assertIsDisplayed() - } -} diff --git a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/internal/ViewFactoriesTest.kt b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/internal/ViewFactoriesTest.kt index 998e6d7542..f88fe99c7a 100644 --- a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/internal/ViewFactoriesTest.kt +++ b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/internal/ViewFactoriesTest.kt @@ -25,7 +25,7 @@ import com.squareup.workflow.ui.ViewEnvironment import com.squareup.workflow.ui.ViewRegistry import com.squareup.workflow.ui.compose.composedViewFactory import com.squareup.workflow.ui.compose.WorkflowRendering -import com.squareup.workflow.ui.compose.withComposeViewFactoryRoot +import com.squareup.workflow.ui.compose.withCompositionRoot import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -37,7 +37,7 @@ class ViewFactoriesTest { @Test fun WorkflowRendering_wrapsFactoryWithRoot_whenAlreadyInComposition() { val viewEnvironment = ViewEnvironment(ViewRegistry(TestFactory)) - .withComposeViewFactoryRoot { content -> + .withCompositionRoot { content -> Column { Text("one") content() diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactory.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactory.kt index bc8fc80583..648ab3f806 100644 --- a/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactory.kt +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactory.kt @@ -73,12 +73,11 @@ import kotlin.reflect.KClass * * ## Initializing Compose context * - * Often all the [composedViewFactory]s in an app need to share some context – for example, certain - * ambients need to be provided, such as `MaterialTheme`. To configure this shared context, include - * a [ComposeViewFactoryRoot] in your top-level [ViewEnvironment] (e.g. by using - * [withComposeViewFactoryRoot]). The first time a [composedViewFactory] is used to show a - * rendering, its [showRendering] function will be wrapped with the [ComposeViewFactoryRoot]. - * See the documentation on [ComposeViewFactoryRoot] for more information. + * Often all the [composedViewFactory] factories in an app need to share some context – for example, + * certain ambients need to be provided, such as `MaterialTheme`. To configure this shared context, + * call [withCompositionRoot] on your top-level [ViewEnvironment]. The first time a + * [composedViewFactory] is used to show a rendering, its [showRendering] function will be wrapped + * with the [CompositionRoot]. See the documentation on [CompositionRoot] for more information. */ inline fun composedViewFactory( noinline showRendering: @Composable() ( @@ -90,7 +89,7 @@ inline fun composedViewFactory( @PublishedApi internal class ComposeViewFactory( override val type: KClass, - private val content: @Composable() (RenderingT, ViewEnvironment) -> Unit + internal val content: @Composable() (RenderingT, ViewEnvironment) -> Unit ) : ViewFactory { override fun buildView( @@ -132,22 +131,9 @@ internal class ComposeViewFactory( val parentComposition = initialViewEnvironment[ParentComposition] composeContainer.setOrSubcomposeContent(parentComposition.reference) { val (rendering, environment) = renderState.value!! - showRenderingWrappedWithRoot(rendering, environment) + content(rendering, environment) } return composeContainer } - - /** - * Invokes [content]. If this is the highest [ComposeViewFactory] in the tree, wraps with - * the [ComposeViewFactoryRoot] if present in the [ViewEnvironment]. - */ - @Composable internal fun showRenderingWrappedWithRoot( - rendering: RenderingT, - viewEnvironment: ViewEnvironment - ) { - wrapWithRootIfNecessary(viewEnvironment) { - content(rendering, viewEnvironment) - } - } } diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactoryRoot.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactoryRoot.kt deleted file mode 100644 index 34e4667e66..0000000000 --- a/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactoryRoot.kt +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright 2020 Square Inc. - * - * 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:Suppress("RemoveEmptyParenthesesFromAnnotationEntry") - -package com.squareup.workflow.ui.compose - -import androidx.compose.Composable -import androidx.compose.Direct -import androidx.compose.Providers -import androidx.compose.remember -import androidx.compose.staticAmbientOf -import com.squareup.workflow.ui.ViewEnvironment -import com.squareup.workflow.ui.ViewEnvironmentKey -import com.squareup.workflow.ui.compose.internal.SafeComposeViewFactoryRoot - -/** - * Used by [wrapWithRootIfNecessary] to ensure the [ComposeViewFactoryRoot] is only applied once. - */ -private val HasViewFactoryRootBeenApplied = staticAmbientOf { false } - -/** - * A `@Composable` function that is stored in a [ViewEnvironment] and will be used to wrap the first - * [composedViewFactory] composition. This can be used to setup any ambients that all - * [composedViewFactory]s need access to, such as ambients that specify the UI theme. - * - * This function will called once, to wrap the _highest-level_ [composedViewFactory] in the tree. - * However, ambients are propagated down to child [composedViewFactory] compositions, so any - * ambients provided here will be available in _all_ [composedViewFactory] compositions. - */ -interface ComposeViewFactoryRoot { - - @Composable fun wrap(content: @Composable() () -> Unit) - - companion object : ViewEnvironmentKey(ComposeViewFactoryRoot::class) { - override val default: ComposeViewFactoryRoot get() = NoopComposeViewFactoryRoot - } -} - -/** - * Adds a [ComposeViewFactoryRoot] to this [ViewEnvironment] that uses [wrapper] to wrap the first - * [composedViewFactory] composition. See [ComposeViewFactoryRoot] for more information. - */ -fun ViewEnvironment.withComposeViewFactoryRoot( - wrapper: @Composable() (content: @Composable() () -> Unit) -> Unit -): ViewEnvironment = this + (ComposeViewFactoryRoot to ComposeViewFactoryRoot(wrapper)) - -// This could be inline, but that makes the Compose compiler puke. -@Suppress("FunctionName") -fun ComposeViewFactoryRoot( - wrapper: @Composable() (content: @Composable() () -> Unit) -> Unit -): ComposeViewFactoryRoot = object : ComposeViewFactoryRoot { - @Composable override fun wrap(content: @Composable() () -> Unit) = wrapper(content) -} - -/** - * Adds [content] to the composition, ensuring that any [ComposeViewFactoryRoot] present in the - * [ViewEnvironment] has been applied. Will only apply the root at the highest occurrence of this - * function in the composition subtree. - */ -@Composable internal fun wrapWithRootIfNecessary( - viewEnvironment: ViewEnvironment, - content: @Composable() () -> Unit -) { - if (HasViewFactoryRootBeenApplied.current) { - // The only way this ambient can have the value true is if, somewhere above this point in the - // composition, the else case below was hit and wrapped us in the ambient. Since the root - // wrapper will have already been applied, we can just compose content directly. - content() - } else { - // If the ambient is false, this is the first time this function has appeared in the composition - // so far. We provide a true value for the ambient for everything below us, so any recursive - // calls to this function will hit the if case above and not re-apply the wrapper. - Providers(HasViewFactoryRootBeenApplied provides true) { - val decorator = viewEnvironment[ComposeViewFactoryRoot] - val safeDecorator = remember(decorator) { - SafeComposeViewFactoryRoot(decorator) - } - safeDecorator.wrap(content) - } - } -} - -private object NoopComposeViewFactoryRoot : ComposeViewFactoryRoot { - @Direct @Composable override fun wrap(content: @Composable() () -> Unit) { - content() - } -} diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/CompositionRoot.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/CompositionRoot.kt new file mode 100644 index 0000000000..b01dc6fa04 --- /dev/null +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/CompositionRoot.kt @@ -0,0 +1,110 @@ +/* + * Copyright 2020 Square Inc. + * + * 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:Suppress("RemoveEmptyParenthesesFromAnnotationEntry") + +package com.squareup.workflow.ui.compose + +import androidx.annotation.VisibleForTesting +import androidx.annotation.VisibleForTesting.PRIVATE +import androidx.compose.Composable +import androidx.compose.Providers +import androidx.compose.remember +import androidx.compose.staticAmbientOf +import com.squareup.workflow.ui.ViewEnvironment +import com.squareup.workflow.ui.ViewRegistry +import com.squareup.workflow.ui.compose.internal.mapFactories +import kotlin.reflect.KClass + +/** + * Used by [wrapWithRootIfNecessary] to ensure the [CompositionRoot] is only applied once. + */ +private val HasViewFactoryRootBeenApplied = staticAmbientOf { false } + +/** + * A `@Composable` function that will be used to wrap the first (highest-level) + * [composedViewFactory] view factory in a composition. This can be used to setup any ambients that + * all [composedViewFactory] factories need access to, such as e.g. UI themes. + * + * This function will called once, to wrap the _highest-level_ [composedViewFactory] in the tree. + * However, ambients are propagated down to child [composedViewFactory] compositions, so any + * ambients provided here will be available in _all_ [composedViewFactory] compositions. + */ +typealias CompositionRoot = @Composable() (content: @Composable() () -> Unit) -> Unit + +/** + * Convenience function for applying a [CompositionRoot] to this [ViewEnvironment]'s [ViewRegistry]. + * See [ViewRegistry.withCompositionRoot]. + */ +fun ViewEnvironment.withCompositionRoot(root: CompositionRoot): ViewEnvironment = + this + (ViewRegistry to this[ViewRegistry].withCompositionRoot(root)) + +/** + * Returns a [ViewRegistry] that ensures that any [composedViewFactory] factories registered in this + * registry will be wrapped exactly once with a [CompositionRoot] wrapper. + * See [CompositionRoot] for more information. + */ +fun ViewRegistry.withCompositionRoot(root: CompositionRoot): ViewRegistry = + mapFactories { factory -> + if (factory !is ComposeViewFactory) return@mapFactories factory + + @Suppress("UNCHECKED_CAST") + ComposeViewFactory(factory.type as KClass) { rendering, environment -> + wrapWithRootIfNecessary(root) { + (factory as ComposeViewFactory).content(rendering, environment) + } + } + } + +/** + * Adds [content] to the composition, ensuring that [CompositionRoot] has been applied. Will only + * wrap the content at the highest occurrence of this function in the composition subtree. + */ +@VisibleForTesting(otherwise = PRIVATE) +@Composable internal fun wrapWithRootIfNecessary( + root: CompositionRoot, + content: @Composable() () -> Unit +) { + if (HasViewFactoryRootBeenApplied.current) { + // The only way this ambient can have the value true is if, somewhere above this point in the + // composition, the else case below was hit and wrapped us in the ambient. Since the root + // wrapper will have already been applied, we can just compose content directly. + content() + } else { + // If the ambient is false, this is the first time this function has appeared in the composition + // so far. We provide a true value for the ambient for everything below us, so any recursive + // calls to this function will hit the if case above and not re-apply the wrapper. + Providers(HasViewFactoryRootBeenApplied provides true) { + val safeRoot: CompositionRoot = remember(root) { safeCompositionRoot(root) } + safeRoot(content) + } + } +} + +/** + * [CompositionRoot] that asserts that the content method invokes its children parameter + * exactly once, and throws an [IllegalStateException] if not. + */ +internal fun safeCompositionRoot(delegate: CompositionRoot): CompositionRoot = { content -> + var childrenCalledCount = 0 + delegate { + childrenCalledCount++ + content() + } + check(childrenCalledCount == 1) { + "Expected ComposableDecorator to invoke children exactly once, " + + "but was invoked $childrenCalledCount times." + } +} diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/SafeComposeViewFactoryRoot.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/SafeComposeViewFactoryRoot.kt deleted file mode 100644 index c2460be208..0000000000 --- a/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/SafeComposeViewFactoryRoot.kt +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2020 Square Inc. - * - * 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:Suppress("RemoveEmptyParenthesesFromAnnotationEntry") - -package com.squareup.workflow.ui.compose.internal - -import androidx.compose.Composable -import com.squareup.workflow.ui.compose.ComposeViewFactoryRoot - -/** - * [ComposeViewFactoryRoot] that asserts that the [wrap] method invokes its children parameter - * exactly once, and throws an [IllegalStateException] if not. - */ -internal class SafeComposeViewFactoryRoot( - private val delegate: ComposeViewFactoryRoot -) : ComposeViewFactoryRoot { - - @Composable override fun wrap(content: @Composable() () -> Unit) { - var childrenCalledCount = 0 - delegate.wrap { - childrenCalledCount++ - content() - } - check(childrenCalledCount == 1) { - "Expected ComposableDecorator to invoke children exactly once, " + - "but was invoked $childrenCalledCount times." - } - } -} diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ViewFactories.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ViewFactories.kt index dab4d8859b..4e1a7feec8 100644 --- a/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ViewFactories.kt +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ViewFactories.kt @@ -57,7 +57,7 @@ import com.squareup.workflow.ui.showRendering // "Fast" path: If the child binding is also a Composable, we don't need to go through the // legacy view system and can just invoke the binding's composable function directly. if (viewFactory is ComposeViewFactory) { - viewFactory.showRenderingWrappedWithRoot(rendering, viewEnvironment) + viewFactory.content(rendering, viewEnvironment) return@Box } diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ViewRegistries.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ViewRegistries.kt new file mode 100644 index 0000000000..8bec77e08f --- /dev/null +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ViewRegistries.kt @@ -0,0 +1,42 @@ +/* + * Copyright 2020 Square Inc. + * + * 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.squareup.workflow.ui.compose.internal + +import com.squareup.workflow.ui.ViewFactory +import com.squareup.workflow.ui.ViewRegistry +import kotlin.reflect.KClass + +/** + * Applies [transform] to each [ViewFactory] in this registry. Transformations are applied lazily, + * at the time of lookup via [ViewRegistry.getFactoryFor]. + */ +internal fun ViewRegistry.mapFactories( + transform: (ViewFactory<*>) -> ViewFactory<*> +): ViewRegistry = object : ViewRegistry { + override val keys: Set> get() = this@mapFactories.keys + + override fun getFactoryFor( + renderingType: KClass + ): ViewFactory { + val transformedFactory = transform(this@mapFactories.getFactoryFor(renderingType)) + check(transformedFactory.type == renderingType) { + "Expected transform to return a ViewFactory that is compatible with $renderingType, " + + "but got one with type ${transformedFactory.type}" + } + @Suppress("UNCHECKED_CAST") + return transformedFactory as ViewFactory + } +} diff --git a/samples/hello-compose-binding/src/main/java/com/squareup/sample/hellocomposebinding/HelloBindingActivity.kt b/samples/hello-compose-binding/src/main/java/com/squareup/sample/hellocomposebinding/HelloBindingActivity.kt index 47971e359c..adcb5db490 100644 --- a/samples/hello-compose-binding/src/main/java/com/squareup/sample/hellocomposebinding/HelloBindingActivity.kt +++ b/samples/hello-compose-binding/src/main/java/com/squareup/sample/hellocomposebinding/HelloBindingActivity.kt @@ -22,11 +22,11 @@ import com.squareup.workflow.diagnostic.SimpleLoggingDiagnosticListener import com.squareup.workflow.ui.ViewEnvironment import com.squareup.workflow.ui.ViewRegistry import com.squareup.workflow.ui.WorkflowRunner -import com.squareup.workflow.ui.compose.withComposeViewFactoryRoot +import com.squareup.workflow.ui.compose.withCompositionRoot import com.squareup.workflow.ui.setContentWorkflow private val viewRegistry = ViewRegistry(HelloBinding) -private val containerHints = ViewEnvironment(viewRegistry).withComposeViewFactoryRoot { content -> +private val containerHints = ViewEnvironment(viewRegistry).withCompositionRoot { content -> MaterialTheme(content = content) } diff --git a/samples/nested-renderings/src/main/java/com/squareup/sample/nestedrenderings/NestedRenderingsActivity.kt b/samples/nested-renderings/src/main/java/com/squareup/sample/nestedrenderings/NestedRenderingsActivity.kt index bd4d021dd0..210d9df2e7 100644 --- a/samples/nested-renderings/src/main/java/com/squareup/sample/nestedrenderings/NestedRenderingsActivity.kt +++ b/samples/nested-renderings/src/main/java/com/squareup/sample/nestedrenderings/NestedRenderingsActivity.kt @@ -23,7 +23,7 @@ import com.squareup.workflow.diagnostic.SimpleLoggingDiagnosticListener import com.squareup.workflow.ui.ViewEnvironment import com.squareup.workflow.ui.ViewRegistry import com.squareup.workflow.ui.WorkflowRunner -import com.squareup.workflow.ui.compose.withComposeViewFactoryRoot +import com.squareup.workflow.ui.compose.withCompositionRoot import com.squareup.workflow.ui.setContentWorkflow private val viewRegistry = ViewRegistry( @@ -31,7 +31,7 @@ private val viewRegistry = ViewRegistry( LegacyRunner ) -private val viewEnvironment = ViewEnvironment(viewRegistry).withComposeViewFactoryRoot { content -> +private val viewEnvironment = ViewEnvironment(viewRegistry).withCompositionRoot { content -> Providers(BackgroundColorAmbient provides Color.Green, children = content) } From 4a5373a60882bb3a7164d0cadaeae92ece232fe0 Mon Sep 17 00:00:00 2001 From: Zach Klippenstein Date: Tue, 19 May 2020 15:35:07 -0700 Subject: [PATCH 28/67] Make renderAsState public, make WorkflowContainer take a ViewEnvironment. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This narrows the scope of `WorkflowContainer` to be only for showing a workflow's renderings using a `ViewEnvironment`. This elimnates the need to have separate overloads for workflows with rendering type `ComposeRendering`, and is more idiomatic – the old `WorkflowContainer` wasn't a "container" in any Compose-y sense of the word, and it was really weird that it took a content function. Now this composable is actually a container for all workflow-related composables, and so I think the name is actually appropriate (closes #22). To render a workflow without a `ViewEnvironment`, `renderAsState` is now public. This is also much more idiomatic, as it resembles APIs like `Flow.collectAsState` and `Observable.subscribeAsState`. --- core-compose/api/core-compose.api | 31 +- .../workflow/ui/compose/RenderAsStateTest.kt | 234 ++++++++++++++ .../ui/compose/WorkflowContainerTest.kt | 169 +--------- .../workflow/ui/compose/RenderAsState.kt | 244 +++++++++++++++ .../workflow/ui/compose/WorkflowContainer.kt | 289 ++++-------------- .../com/squareup/sample/hellocompose/App.kt | 26 +- 6 files changed, 570 insertions(+), 423 deletions(-) create mode 100644 core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/RenderAsStateTest.kt create mode 100644 core-compose/src/main/java/com/squareup/workflow/ui/compose/RenderAsState.kt diff --git a/core-compose/api/core-compose.api b/core-compose/api/core-compose.api index 594f3674ad..902736760e 100644 --- a/core-compose/api/core-compose.api +++ b/core-compose/api/core-compose.api @@ -31,28 +31,31 @@ public final class com/squareup/workflow/ui/compose/CompositionRootKt { public static final fun withCompositionRoot (Lcom/squareup/workflow/ui/ViewRegistry;Lkotlin/jvm/functions/Function2;)Lcom/squareup/workflow/ui/ViewRegistry; } +public final class com/squareup/workflow/ui/compose/RenderAsStateKt { + public static final fun renderAsState (Lcom/squareup/workflow/Workflow;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;)Landroidx/compose/State; + public static final fun renderAsState (Lcom/squareup/workflow/Workflow;Ljava/lang/Object;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;)Landroidx/compose/State; + public static final fun renderAsState (Lcom/squareup/workflow/Workflow;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;)Landroidx/compose/State; + public static final fun renderAsState (Lcom/squareup/workflow/Workflow;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;)Landroidx/compose/State; + public static synthetic fun renderAsState$default (Lcom/squareup/workflow/Workflow;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;ILjava/lang/Object;)Landroidx/compose/State; + public static synthetic fun renderAsState$default (Lcom/squareup/workflow/Workflow;Ljava/lang/Object;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;ILjava/lang/Object;)Landroidx/compose/State; + public static synthetic fun renderAsState$default (Lcom/squareup/workflow/Workflow;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;ILjava/lang/Object;)Landroidx/compose/State; + public static synthetic fun renderAsState$default (Lcom/squareup/workflow/Workflow;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;ILjava/lang/Object;)Landroidx/compose/State; +} + public final class com/squareup/workflow/ui/compose/ViewEnvironmentsKt { public static final fun WorkflowRendering (Ljava/lang/Object;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/ui/core/Modifier;Landroidx/compose/Composer;)V public static synthetic fun WorkflowRendering$default (Ljava/lang/Object;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/ui/core/Modifier;Landroidx/compose/Composer;ILjava/lang/Object;)V } public final class com/squareup/workflow/ui/compose/WorkflowContainerKt { - public static final fun WorkflowContainer (Lcom/squareup/workflow/Workflow;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Lkotlin/jvm/functions/Function2;Landroidx/compose/Composer;)V public static final fun WorkflowContainer (Lcom/squareup/workflow/Workflow;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;)V - public static final fun WorkflowContainer (Lcom/squareup/workflow/Workflow;Lcom/squareup/workflow/ui/ViewEnvironment;Ljava/lang/Object;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;)V - public static final fun WorkflowContainer (Lcom/squareup/workflow/Workflow;Lcom/squareup/workflow/ui/ViewEnvironment;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;)V - public static final fun WorkflowContainer (Lcom/squareup/workflow/Workflow;Lcom/squareup/workflow/ui/ViewEnvironment;Lkotlin/jvm/functions/Function1;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;)V - public static final fun WorkflowContainer (Lcom/squareup/workflow/Workflow;Ljava/lang/Object;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Lkotlin/jvm/functions/Function2;Landroidx/compose/Composer;)V - public static final fun WorkflowContainer (Lcom/squareup/workflow/Workflow;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Lkotlin/jvm/functions/Function2;Landroidx/compose/Composer;)V - public static final fun WorkflowContainer (Lcom/squareup/workflow/Workflow;Lkotlin/jvm/functions/Function1;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Lkotlin/jvm/functions/Function2;Landroidx/compose/Composer;)V - public static synthetic fun WorkflowContainer$default (Lcom/squareup/workflow/Workflow;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Lkotlin/jvm/functions/Function2;Landroidx/compose/Composer;ILjava/lang/Object;)V + public static final fun WorkflowContainer (Lcom/squareup/workflow/Workflow;Ljava/lang/Object;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;)V + public static final fun WorkflowContainer (Lcom/squareup/workflow/Workflow;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;)V + public static final fun WorkflowContainer (Lcom/squareup/workflow/Workflow;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;)V public static synthetic fun WorkflowContainer$default (Lcom/squareup/workflow/Workflow;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;ILjava/lang/Object;)V - public static synthetic fun WorkflowContainer$default (Lcom/squareup/workflow/Workflow;Lcom/squareup/workflow/ui/ViewEnvironment;Ljava/lang/Object;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;ILjava/lang/Object;)V - public static synthetic fun WorkflowContainer$default (Lcom/squareup/workflow/Workflow;Lcom/squareup/workflow/ui/ViewEnvironment;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;ILjava/lang/Object;)V - public static synthetic fun WorkflowContainer$default (Lcom/squareup/workflow/Workflow;Lcom/squareup/workflow/ui/ViewEnvironment;Lkotlin/jvm/functions/Function1;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;ILjava/lang/Object;)V - public static synthetic fun WorkflowContainer$default (Lcom/squareup/workflow/Workflow;Ljava/lang/Object;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Lkotlin/jvm/functions/Function2;Landroidx/compose/Composer;ILjava/lang/Object;)V - public static synthetic fun WorkflowContainer$default (Lcom/squareup/workflow/Workflow;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Lkotlin/jvm/functions/Function2;Landroidx/compose/Composer;ILjava/lang/Object;)V - public static synthetic fun WorkflowContainer$default (Lcom/squareup/workflow/Workflow;Lkotlin/jvm/functions/Function1;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Lkotlin/jvm/functions/Function2;Landroidx/compose/Composer;ILjava/lang/Object;)V + public static synthetic fun WorkflowContainer$default (Lcom/squareup/workflow/Workflow;Ljava/lang/Object;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;ILjava/lang/Object;)V + public static synthetic fun WorkflowContainer$default (Lcom/squareup/workflow/Workflow;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;ILjava/lang/Object;)V + public static synthetic fun WorkflowContainer$default (Lcom/squareup/workflow/Workflow;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;ILjava/lang/Object;)V } public final class com/squareup/workflow/ui/compose/internal/ComposeSupportKt { diff --git a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/RenderAsStateTest.kt b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/RenderAsStateTest.kt new file mode 100644 index 0000000000..868c6fe7ab --- /dev/null +++ b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/RenderAsStateTest.kt @@ -0,0 +1,234 @@ +/* + * Copyright 2020 Square Inc. + * + * 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:Suppress("RemoveEmptyParenthesesFromAnnotationEntry") + +package com.squareup.workflow.ui.compose + +import androidx.compose.FrameManager +import androidx.compose.Providers +import androidx.compose.mutableStateOf +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.ui.savedinstancestate.UiSavedStateRegistry +import androidx.ui.savedinstancestate.UiSavedStateRegistryAmbient +import androidx.ui.test.createComposeRule +import androidx.ui.test.runOnIdleCompose +import androidx.ui.test.waitForIdle +import com.google.common.truth.Truth.assertThat +import com.squareup.workflow.RenderContext +import com.squareup.workflow.Snapshot +import com.squareup.workflow.StatefulWorkflow +import com.squareup.workflow.Workflow +import com.squareup.workflow.action +import com.squareup.workflow.parse +import com.squareup.workflow.readUtf8WithLength +import com.squareup.workflow.stateless +import com.squareup.workflow.ui.compose.RenderAsStateTest.SnapshottingWorkflow.SnapshottedRendering +import com.squareup.workflow.writeUtf8WithLength +import okio.ByteString +import okio.ByteString.Companion.decodeBase64 +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class RenderAsStateTest { + + @Rule @JvmField val composeRule = createComposeRule() + + @Test fun passesPropsThrough() { + val workflow = Workflow.stateless { it } + lateinit var initialRendering: String + + composeRule.setContent { + initialRendering = workflow.renderAsState("foo").value + } + + runOnIdleCompose { + assertThat(initialRendering).isEqualTo("foo") + } + } + + @Test fun seesPropsAndRenderingUpdates() { + val workflow = Workflow.stateless { it } + val props = mutableStateOf("foo") + lateinit var rendering: String + + composeRule.setContent { + rendering = workflow.renderAsState(props.value).value + } + + waitForIdle() + assertThat(rendering).isEqualTo("foo") + FrameManager.framed { + props.value = "bar" + } + waitForIdle() + assertThat(rendering).isEqualTo("bar") + } + + @Test fun invokesOutputCallback() { + val workflow = Workflow.stateless Unit> { + { string -> actionSink.send(action { setOutput(string) }) } + } + val receivedOutputs = mutableListOf() + lateinit var rendering: (String) -> Unit + + composeRule.setContent { + rendering = workflow.renderAsState(onOutput = { receivedOutputs += it }).value + } + + waitForIdle() + assertThat(receivedOutputs).isEmpty() + rendering("one") + + waitForIdle() + assertThat(receivedOutputs).isEqualTo(listOf("one")) + rendering("two") + + waitForIdle() + assertThat(receivedOutputs).isEqualTo(listOf("one", "two")) + } + + @Test fun savesSnapshot() { + val workflow = SnapshottingWorkflow() + val savedStateRegistry = UiSavedStateRegistry(emptyMap()) { true } + lateinit var rendering: SnapshottedRendering + + composeRule.setContent { + Providers(UiSavedStateRegistryAmbient provides savedStateRegistry) { + rendering = renderAsStateImpl( + workflow, + props = Unit, + onOutput = {}, + diagnosticListener = null, + snapshotKey = SNAPSHOT_KEY + ).value + } + } + + waitForIdle() + assertThat(rendering.string).isEmpty() + rendering.updateString("foo") + + waitForIdle() + val savedValues = FrameManager.framed { + savedStateRegistry.performSave() + } + println("saved keys: ${savedValues.keys}") + // Relying on the int key across all runtimes is brittle, so use an explicit key. + val snapshot = ByteString.of(*(savedValues.getValue(SNAPSHOT_KEY) as ByteArray)) + println("snapshot: ${snapshot.base64()}") + assertThat(snapshot).isEqualTo(EXPECTED_SNAPSHOT) + } + + @Test fun restoresSnapshot() { + val workflow = SnapshottingWorkflow() + val restoreValues = mapOf(SNAPSHOT_KEY to EXPECTED_SNAPSHOT.toByteArray()) + val savedStateRegistry = UiSavedStateRegistry(restoreValues) { true } + lateinit var rendering: SnapshottedRendering + + composeRule.setContent { + Providers(UiSavedStateRegistryAmbient provides savedStateRegistry) { + rendering = renderAsStateImpl( + workflow, + props = Unit, + onOutput = {}, + diagnosticListener = null, + snapshotKey = "workflow-snapshot" + ).value + } + } + + waitForIdle() + assertThat(rendering.string).isEqualTo("foo") + } + + @Test fun restoresFromSnapshotWhenWorkflowChanged() { + val workflow1 = SnapshottingWorkflow() + val workflow2 = SnapshottingWorkflow() + val currentWorkflow = mutableStateOf(workflow1) + lateinit var rendering: SnapshottedRendering + + var compositionCount = 0 + var lastCompositionCount = 0 + fun assertWasRecomposed() { + assertThat(compositionCount).isGreaterThan(lastCompositionCount) + lastCompositionCount = compositionCount + } + + composeRule.setContent { + compositionCount++ + rendering = currentWorkflow.value.renderAsState().value + } + + // Initialize the first workflow. + waitForIdle() + assertThat(rendering.string).isEmpty() + assertWasRecomposed() + rendering.updateString("one") + waitForIdle() + assertWasRecomposed() + assertThat(rendering.string).isEqualTo("one") + + // Change the workflow instance being rendered. This should restart the runtime, but restore + // it from the snapshot. + FrameManager.framed { + currentWorkflow.value = workflow2 + } + + waitForIdle() + assertWasRecomposed() + assertThat(rendering.string).isEqualTo("one") + } + + private companion object { + const val SNAPSHOT_KEY = "workflow-snapshot" + + /** Golden value from [savesSnapshot]. */ + val EXPECTED_SNAPSHOT = "AAAABwAAAANmb28AAAAA".decodeBase64()!! + } + + // Seems to be a problem accessing Workflow.stateful. + private class SnapshottingWorkflow : + StatefulWorkflow() { + + data class SnapshottedRendering( + val string: String, + val updateString: (String) -> Unit + ) + + override fun initialState( + props: Unit, + snapshot: Snapshot? + ): String = snapshot?.bytes?.parse { it.readUtf8WithLength() } ?: "" + + override fun render( + props: Unit, + state: String, + context: RenderContext + ) = SnapshottedRendering( + string = state, + updateString = { newString -> context.actionSink.send(updateString(newString)) } + ) + + override fun snapshotState(state: String): Snapshot = + Snapshot.write { it.writeUtf8WithLength(state) } + + private fun updateString(newString: String) = action { + nextState = newString + } + } +} diff --git a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/WorkflowContainerTest.kt b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/WorkflowContainerTest.kt index 716b937591..8ae3f540f8 100644 --- a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/WorkflowContainerTest.kt +++ b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/WorkflowContainerTest.kt @@ -17,33 +17,15 @@ package com.squareup.workflow.ui.compose -import androidx.compose.FrameManager -import androidx.compose.Providers -import androidx.compose.mutableStateOf -import androidx.compose.onActive import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.ui.foundation.Clickable import androidx.ui.foundation.Text -import androidx.ui.layout.Column -import androidx.ui.savedinstancestate.UiSavedStateRegistry -import androidx.ui.savedinstancestate.UiSavedStateRegistryAmbient +import androidx.ui.test.assertIsDisplayed import androidx.ui.test.createComposeRule -import androidx.ui.test.doClick import androidx.ui.test.findByText -import androidx.ui.test.waitForIdle -import com.google.common.truth.Truth.assertThat -import com.squareup.workflow.RenderContext -import com.squareup.workflow.Snapshot -import com.squareup.workflow.StatefulWorkflow import com.squareup.workflow.Workflow -import com.squareup.workflow.action -import com.squareup.workflow.parse -import com.squareup.workflow.readUtf8WithLength import com.squareup.workflow.stateless -import com.squareup.workflow.ui.compose.WorkflowContainerTest.SnapshottingWorkflow.SnapshottedRendering -import com.squareup.workflow.writeUtf8WithLength -import okio.ByteString -import okio.ByteString.Companion.decodeBase64 +import com.squareup.workflow.ui.ViewEnvironment +import com.squareup.workflow.ui.ViewRegistry import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -53,150 +35,27 @@ class WorkflowContainerTest { @Rule @JvmField val composeRule = createComposeRule() - @Test fun passesPropsThrough() { - val workflow = Workflow.stateless { it } + @Test fun rendersFromViewRegistry() { + val workflow = Workflow.stateless { "hello" } + val registry = ViewRegistry(composedViewFactory { rendering, _ -> Text(rendering) }) composeRule.setContent { - WorkflowContainer(workflow, "foo") { - assertThat(it).isEqualTo("foo") - } - } - } - - @Test fun seesPropsAndRenderingUpdates() { - val workflow = Workflow.stateless { it } - val props = mutableStateOf("foo") - - composeRule.setContent { - WorkflowContainer(workflow, props.value) { - Text(it) - } + WorkflowContainer(workflow, ViewEnvironment(registry)) } - findByText("foo").assertExists() - FrameManager.framed { - props.value = "bar" - } - findByText("bar").assertExists() + findByText("hello").assertIsDisplayed() } - @Test fun invokesOutputCallback() { - val workflow = Workflow.stateless Unit> { - { string -> actionSink.send(action { setOutput(string) }) } - } - - val receivedOutputs = mutableListOf() - composeRule.setContent { - WorkflowContainer(workflow, onOutput = { receivedOutputs += it }) { sendOutput -> - Column { - Clickable(onClick = { sendOutput("one") }) { - Text("send one") - } - Clickable(onClick = { sendOutput("two") }) { - Text("send two") - } - } - } + @Test fun automaticallyAddsComposeRenderingFactory() { + val workflow = Workflow.composed { _, _, _ -> + Text("it worked") } - - waitForIdle() - assertThat(receivedOutputs).isEmpty() - findByText("send one").doClick() - - waitForIdle() - assertThat(receivedOutputs).isEqualTo(listOf("one")) - findByText("send two").doClick() - - waitForIdle() - assertThat(receivedOutputs).isEqualTo(listOf("one", "two")) - } - - @Test fun savesSnapshot() { - val savedStateRegistry = UiSavedStateRegistry(emptyMap()) { true } + val registry = ViewRegistry() composeRule.setContent { - Providers(UiSavedStateRegistryAmbient provides savedStateRegistry) { - WorkflowContainerImpl( - SnapshottingWorkflow, - props = Unit, - onOutput = {}, - snapshotKey = SNAPSHOT_KEY - ) { (string, updateString) -> - onActive { - assertThat(string).isEmpty() - updateString("foo") - } - } - } + WorkflowContainer(workflow, ViewEnvironment(registry)) } - waitForIdle() - val savedValues = FrameManager.framed { - savedStateRegistry.performSave() - } - println("saved keys: ${savedValues.keys}") - // Relying on the int key across all runtimes might be flaky, might need to pass explicit key. - val snapshot = ByteString.of(*(savedValues.getValue(SNAPSHOT_KEY) as ByteArray)) - println("snapshot: ${snapshot.base64()}") - assertThat(snapshot).isEqualTo(EXPECTED_SNAPSHOT) - } - - @Test fun restoresSnapshot() { - val restoreValues = mapOf(SNAPSHOT_KEY to EXPECTED_SNAPSHOT.toByteArray()) - val savedStateRegistry = UiSavedStateRegistry(restoreValues) { true } - - composeRule.setContent { - Providers(UiSavedStateRegistryAmbient provides savedStateRegistry) { - WorkflowContainerImpl( - SnapshottingWorkflow, - props = Unit, - onOutput = {}, - snapshotKey = "workflow-snapshot" - ) { (string) -> - onActive { - assertThat(string).isEqualTo("foo") - } - Text(string) - } - } - } - - findByText("foo").assertExists() - } - - private companion object { - const val SNAPSHOT_KEY = "workflow-snapshot" - val EXPECTED_SNAPSHOT = "AAAABwAAAANmb28AAAAA".decodeBase64()!! - } - - // Seems to be a problem accessing Workflow.stateful. - private object SnapshottingWorkflow : - StatefulWorkflow() { - - data class SnapshottedRendering( - val string: String, - val updateString: (String) -> Unit - ) - - override fun initialState( - props: Unit, - snapshot: Snapshot? - ): String = snapshot?.bytes?.parse { it.readUtf8WithLength() } ?: "" - - override fun render( - props: Unit, - state: String, - context: RenderContext - ) = SnapshottedRendering( - string = state, - updateString = { newString -> context.actionSink.send(updateString(newString)) } - ) - - override fun snapshotState(state: String): Snapshot = - Snapshot.write { it.writeUtf8WithLength(state) } - - private fun updateString(newString: String) = action { - nextState = newString - } + findByText("it worked").assertIsDisplayed() } } diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/RenderAsState.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/RenderAsState.kt new file mode 100644 index 0000000000..3307a3aa4b --- /dev/null +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/RenderAsState.kt @@ -0,0 +1,244 @@ +/* + * Copyright 2020 Square Inc. + * + * 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:Suppress("NOTHING_TO_INLINE") + +package com.squareup.workflow.ui.compose + +import androidx.annotation.VisibleForTesting +import androidx.compose.Composable +import androidx.compose.CompositionLifecycleObserver +import androidx.compose.FrameManager +import androidx.compose.MutableState +import androidx.compose.State +import androidx.compose.mutableStateOf +import androidx.compose.remember +import androidx.ui.core.CoroutineContextAmbient +import androidx.ui.core.Ref +import androidx.ui.savedinstancestate.Saver +import androidx.ui.savedinstancestate.SaverScope +import androidx.ui.savedinstancestate.UiSavedStateRegistryAmbient +import androidx.ui.savedinstancestate.savedInstanceState +import com.squareup.workflow.Snapshot +import com.squareup.workflow.Workflow +import com.squareup.workflow.diagnostic.WorkflowDiagnosticListener +import com.squareup.workflow.launchWorkflowIn +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.cancel +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.flow.consumeAsFlow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import okio.ByteString +import kotlin.coroutines.CoroutineContext + +/** + * Runs this [Workflow] as long as this composable is part of the composition, and returns a + * [State] object that will be updated whenever the runtime emits a new [RenderingT]. + * + * The workflow runtime will be started when this function is first added to the composition, and + * cancelled when it is removed. The first rendering will be available immediately as soon as this + * function returns, as [State.value]. Composables that read this value will automatically recompose + * whenever the runtime emits a new rendering. + * + * [Snapshot]s from the runtime will automatically be saved to the current + * [UiSavedStateRegistry][androidx.ui.savedinstancestate.UiSavedStateRegistry]. When the runtime is + * started, if a snapshot exists in the registry, it will be used to restore the workflows. + * + * @receiver The [Workflow] to run. If the value of the receiver changes to a different [Workflow] + * while this function is in the composition, the runtime will be restarted with the new workflow. + * @param props The [PropsT] for the root [Workflow]. Changes to this value across different + * compositions will cause the root workflow to re-render with the new props. + * @param onOutput A function that will be executed whenever the root [Workflow] emits an output. + * @param diagnosticListener An optional [WorkflowDiagnosticListener] to start the runtime with. If + * this value changes while this function is in the composition, the runtime will be restarted. + */ +@Composable +fun Workflow.renderAsState( + props: PropsT, + onOutput: (OutputT) -> Unit, + diagnosticListener: WorkflowDiagnosticListener? = null +): State = renderAsStateImpl(this, props, onOutput, diagnosticListener) + +/** + * Runs this [Workflow] as long as this composable is part of the composition, and returns a + * [State] object that will be updated whenever the runtime emits a new [RenderingT]. + * + * The workflow runtime will be started when this function is first added to the composition, and + * cancelled when it is removed. The first rendering will be available immediately as soon as this + * function returns, as [State.value]. Composables that read this value will automatically recompose + * whenever the runtime emits a new rendering. + * + * [Snapshot]s from the runtime will automatically be saved to the current + * [UiSavedStateRegistry][androidx.ui.savedinstancestate.UiSavedStateRegistry]. When the runtime is + * started, if a snapshot exists in the registry, it will be used to restore the workflows. + * + * @receiver The [Workflow] to run. If the value of the receiver changes to a different [Workflow] + * while this function is in the composition, the runtime will be restarted with the new workflow. + * @param onOutput A function that will be executed whenever the root [Workflow] emits an output. + * @param diagnosticListener An optional [WorkflowDiagnosticListener] to start the runtime with. If + * this value changes while this function is in the composition, the runtime will be restarted. + */ +@Composable +inline fun Workflow.renderAsState( + noinline onOutput: (OutputT) -> Unit, + diagnosticListener: WorkflowDiagnosticListener? = null +): State = renderAsState(Unit, onOutput, diagnosticListener) + +/** + * Runs this [Workflow] as long as this composable is part of the composition, and returns a + * [State] object that will be updated whenever the runtime emits a new [RenderingT]. + * + * The workflow runtime will be started when this function is first added to the composition, and + * cancelled when it is removed. The first rendering will be available immediately as soon as this + * function returns, as [State.value]. Composables that read this value will automatically recompose + * whenever the runtime emits a new rendering. + * + * [Snapshot]s from the runtime will automatically be saved to the current + * [UiSavedStateRegistry][androidx.ui.savedinstancestate.UiSavedStateRegistry]. When the runtime is + * started, if a snapshot exists in the registry, it will be used to restore the workflows. + * + * @receiver The [Workflow] to run. If the value of the receiver changes to a different [Workflow] + * while this function is in the composition, the runtime will be restarted with the new workflow. + * @param props The [PropsT] for the root [Workflow]. Changes to this value across different + * compositions will cause the root workflow to re-render with the new props. + * @param diagnosticListener An optional [WorkflowDiagnosticListener] to start the runtime with. If + * this value changes while this function is in the composition, the runtime will be restarted. + */ +@Composable +inline fun Workflow.renderAsState( + props: PropsT, + diagnosticListener: WorkflowDiagnosticListener? = null +): State = renderAsState(props, {}, diagnosticListener) + +/** + * Runs this [Workflow] as long as this composable is part of the composition, and returns a + * [State] object that will be updated whenever the runtime emits a new [RenderingT]. + * + * The workflow runtime will be started when this function is first added to the composition, and + * cancelled when it is removed. The first rendering will be available immediately as soon as this + * function returns, as [State.value]. Composables that read this value will automatically recompose + * whenever the runtime emits a new rendering. + * + * [Snapshot]s from the runtime will automatically be saved to the current + * [UiSavedStateRegistry][androidx.ui.savedinstancestate.UiSavedStateRegistry]. When the runtime is + * started, if a snapshot exists in the registry, it will be used to restore the workflows. + * + * @receiver The [Workflow] to run. If the value of the receiver changes to a different [Workflow] + * while this function is in the composition, the runtime will be restarted with the new workflow. + * @param diagnosticListener An optional [WorkflowDiagnosticListener] to start the runtime with. If + * this value changes while this function is in the composition, the runtime will be restarted. + */ +@Composable +inline fun Workflow.renderAsState( + diagnosticListener: WorkflowDiagnosticListener? = null +): State = renderAsState(Unit, {}, diagnosticListener) + +/** + * @param snapshotKey Allows tests to pass in a custom key to use to save/restore the snapshot from + * the [UiSavedStateRegistryAmbient]. If null, will use the default key based on source location. + */ +@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) +@Composable internal fun renderAsStateImpl( + workflow: Workflow, + props: PropsT, + onOutput: (OutputT) -> Unit, + diagnosticListener: WorkflowDiagnosticListener?, + snapshotKey: String? = null +): State { + @Suppress("DEPRECATION") + val coroutineContext = CoroutineContextAmbient.current + Dispatchers.Main.immediate + val snapshotState = savedInstanceState(key = snapshotKey, saver = SnapshotSaver) { null } + + val outputRef = remember { Ref<(OutputT) -> Unit>() } + outputRef.value = onOutput + + // We can't use onActive/on(Pre)Commit because they won't run their callback until after this + // function returns, and we need to run this immediately so we get the rendering synchronously. + val state = remember(coroutineContext, workflow, diagnosticListener) { + WorkflowState(coroutineContext, workflow, props, outputRef, snapshotState, diagnosticListener) + } + state.setProps(props) + + return state.rendering +} + +@Suppress("EXPERIMENTAL_API_USAGE") +private class WorkflowState( + coroutineContext: CoroutineContext, + workflow: Workflow, + initialProps: PropsT, + private val outputRef: Ref<(OutputT) -> Unit>, + private val snapshotState: MutableState, + private val diagnosticListener: WorkflowDiagnosticListener? +) : CompositionLifecycleObserver { + + private val workflowScope = CoroutineScope(coroutineContext) + private val renderingState = mutableStateOf(null) + + // This can be a StateFlow once coroutines is upgraded to 1.3.6. + private val propsChannel = Channel(capacity = Channel.CONFLATED) + .apply { offer(initialProps) } + val propsFlow = propsChannel.consumeAsFlow() + .distinctUntilChanged() + + // The value is guaranteed to be set before returning, so this cast is fine. + @Suppress("UNCHECKED_CAST") + val rendering: State + get() = renderingState as State + + init { + launchWorkflowIn(workflowScope, workflow, propsFlow, snapshotState.value) { session -> + session.diagnosticListener = diagnosticListener + + session.outputs.onEach { outputRef.value!!.invoke(it) } + .launchIn(this) + + session.renderingsAndSnapshots + .onEach { (rendering, snapshot) -> + FrameManager.framed { + renderingState.value = rendering + snapshotState.value = snapshot + } + } + .launchIn(this) + } + } + + fun setProps(props: PropsT) { + propsChannel.offer(props) + } + + override fun onEnter() {} + + override fun onLeave() { + workflowScope.cancel() + } +} + +private object SnapshotSaver : Saver { + override fun SaverScope.save(value: Snapshot?): ByteArray { + return value?.bytes?.toByteArray() ?: ByteArray(0) + } + + override fun restore(value: ByteArray): Snapshot? { + return value.takeUnless { it.isEmpty() } + ?.let { bytes -> Snapshot.of(ByteString.of(*bytes)) } + } +} + +private class OutputCallback(var onOutput: (OutputT) -> Unit) diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/WorkflowContainer.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/WorkflowContainer.kt index 643af54f20..908864a6f8 100644 --- a/core-compose/src/main/java/com/squareup/workflow/ui/compose/WorkflowContainer.kt +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/WorkflowContainer.kt @@ -14,46 +14,22 @@ * limitations under the License. */ @file:Suppress( - "EXPERIMENTAL_API_USAGE", "FunctionNaming", - "NOTHING_TO_INLINE", - "RemoveEmptyParenthesesFromAnnotationEntry" + "NOTHING_TO_INLINE" ) package com.squareup.workflow.ui.compose -import androidx.annotation.VisibleForTesting -import androidx.annotation.VisibleForTesting.PRIVATE import androidx.compose.Composable -import androidx.compose.Direct -import androidx.compose.Pivotal -import androidx.compose.State -import androidx.compose.onDispose import androidx.compose.remember -import androidx.compose.state -import androidx.ui.core.CoroutineContextAmbient import androidx.ui.core.Modifier -import androidx.ui.foundation.Box -import androidx.ui.savedinstancestate.Saver -import androidx.ui.savedinstancestate.SaverScope -import androidx.ui.savedinstancestate.UiSavedStateRegistryAmbient -import androidx.ui.savedinstancestate.savedInstanceState import com.squareup.workflow.Snapshot import com.squareup.workflow.Workflow import com.squareup.workflow.diagnostic.WorkflowDiagnosticListener -import com.squareup.workflow.launchWorkflowIn import com.squareup.workflow.ui.ViewEnvironment -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.cancel -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.channels.Channel.Factory.CONFLATED -import kotlinx.coroutines.flow.consumeAsFlow -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach -import okio.ByteString -import kotlin.coroutines.CoroutineContext +import com.squareup.workflow.ui.ViewFactory +import com.squareup.workflow.ui.ViewRegistry +import com.squareup.workflow.ui.plus /** * Render a [Workflow]'s renderings. @@ -62,121 +38,33 @@ import kotlin.coroutines.CoroutineContext * any time [workflow], [diagnosticListener], or the `CoroutineContext` * changes. The runtime will be cancelled when this function stops composing. * + * [Snapshot]s from the runtime will automatically be saved to the current + * [UiSavedStateRegistry][androidx.ui.savedinstancestate.UiSavedStateRegistry]. When the runtime is + * started, if a snapshot exists in the registry, it will be used to restore the workflows. + * * @param workflow The [Workflow] to render. * @param props The props to render the root workflow with. If this value changes between calls, * the workflow runtime will re-render with the new props. * @param onOutput A function that will be invoked any time the root workflow emits an output. + * @param viewEnvironment The [ViewEnvironment] used to display renderings. + * @param modifier The [Modifier] to apply to the root [ViewFactory]. * @param diagnosticListener A [WorkflowDiagnosticListener] to configure on the runtime. - * @param content A [Composable] function that gets executed every time the root workflow spits - * out a new rendering. */ -@Direct -@Composable fun WorkflowContainer( +@Composable fun WorkflowContainer( workflow: Workflow, props: PropsT, onOutput: (OutputT) -> Unit, - modifier: Modifier = Modifier, - diagnosticListener: WorkflowDiagnosticListener? = null, - content: @Composable() (rendering: RenderingT) -> Unit -) { - WorkflowContainerImpl(workflow, props, onOutput, modifier, diagnosticListener, content = content) -} - -/** - * Render a [Workflow]'s renderings. - * - * When this function is first composed it will start a new runtime. This runtime will be restarted - * any time [workflow], [diagnosticListener], or the `CoroutineContext` - * changes. The runtime will be cancelled when this function stops composing. - * - * @param workflow The [Workflow] to render. - * @param onOutput A function that will be invoked any time the root workflow emits an output. - * @param diagnosticListener A [WorkflowDiagnosticListener] to configure on the runtime. - * @param content A [Composable] function that gets executed every time the root workflow spits - * out a new rendering. - */ -@Composable inline fun WorkflowContainer( - workflow: Workflow, - noinline onOutput: (OutputT) -> Unit, - modifier: Modifier = Modifier, - diagnosticListener: WorkflowDiagnosticListener? = null, - noinline content: @Composable() (rendering: RenderingT) -> Unit -) { - WorkflowContainer(workflow, Unit, onOutput, modifier, diagnosticListener, content) -} - -/** - * Render a [Workflow]'s renderings. - * - * When this function is first composed it will start a new runtime. This runtime will be restarted - * any time [workflow], [diagnosticListener], or the `CoroutineContext` - * changes. The runtime will be cancelled when this function stops composing. - * - * @param workflow The [Workflow] to render. - * @param props The props to render the root workflow with. If this value changes between calls, - * the workflow runtime will re-render with the new props. - * @param diagnosticListener A [WorkflowDiagnosticListener] to configure on the runtime. - * @param content A [Composable] function that gets executed every time the root workflow spits - * out a new rendering. - */ -@Composable inline fun WorkflowContainer( - workflow: Workflow, - props: PropsT, - modifier: Modifier = Modifier, - diagnosticListener: WorkflowDiagnosticListener? = null, - noinline content: @Composable() (rendering: RenderingT) -> Unit -) { - WorkflowContainer(workflow, props, {}, modifier, diagnosticListener, content) -} - -/** - * Render a [Workflow]'s renderings. - * - * When this function is first composed it will start a new runtime. This runtime will be restarted - * any time [workflow], [diagnosticListener], or the `CoroutineContext` - * changes. The runtime will be cancelled when this function stops composing. - * - * @param workflow The [Workflow] to render. - * @param diagnosticListener A [WorkflowDiagnosticListener] to configure on the runtime. - * @param content A [Composable] function that gets executed every time the root workflow spits - * out a new rendering. - */ -@Composable inline fun WorkflowContainer( - workflow: Workflow, - modifier: Modifier = Modifier, - diagnosticListener: WorkflowDiagnosticListener? = null, - noinline content: @Composable() (rendering: RenderingT) -> Unit -) { - WorkflowContainer(workflow, Unit, {}, modifier, diagnosticListener, content) -} - -/** - * Render a [Workflow]'s renderings. - * - * When this function is first composed it will start a new runtime. This runtime will be restarted - * any time [workflow], [diagnosticListener], or the `CoroutineContext` - * changes. The runtime will be cancelled when this function stops composing. - * - * @param workflow The [Workflow] to render. - * @param viewEnvironment The [ViewEnvironment] used to show the [ComposeRendering]s emitted by - * the workflow. - * @param props The props to render the root workflow with. If this value changes between calls, - * the workflow runtime will re-render with the new props. - * @param onOutput A function that will be invoked any time the root workflow emits an output. - * @param diagnosticListener A [WorkflowDiagnosticListener] to configure on the runtime. - */ -@Direct -@Composable fun WorkflowContainer( - workflow: Workflow, viewEnvironment: ViewEnvironment, - props: PropsT, - onOutput: (OutputT) -> Unit, modifier: Modifier = Modifier, diagnosticListener: WorkflowDiagnosticListener? = null ) { - WorkflowContainer(workflow, props, onOutput, modifier, diagnosticListener) { rendering -> - rendering.render(viewEnvironment) + // Ensure ComposeRendering is in the ViewRegistry. + val realEnvironment = remember(viewEnvironment) { + viewEnvironment.withFactory(ComposeRendering.Factory) } + + val rendering = workflow.renderAsState(props, onOutput, diagnosticListener) + WorkflowRendering(rendering.value, realEnvironment, modifier) } /** @@ -186,20 +74,24 @@ import kotlin.coroutines.CoroutineContext * any time [workflow], [diagnosticListener], or the `CoroutineContext` * changes. The runtime will be cancelled when this function stops composing. * + * [Snapshot]s from the runtime will automatically be saved to the current + * [UiSavedStateRegistry][androidx.ui.savedinstancestate.UiSavedStateRegistry]. When the runtime is + * started, if a snapshot exists in the registry, it will be used to restore the workflows. + * * @param workflow The [Workflow] to render. - * @param viewEnvironment The [ViewEnvironment] used to show the [ComposeRendering]s emitted by - * the workflow. * @param onOutput A function that will be invoked any time the root workflow emits an output. + * @param viewEnvironment The [ViewEnvironment] used to display renderings. + * @param modifier The [Modifier] to apply to the root [ViewFactory]. * @param diagnosticListener A [WorkflowDiagnosticListener] to configure on the runtime. */ -@Composable inline fun WorkflowContainer( - workflow: Workflow, - viewEnvironment: ViewEnvironment, +@Composable inline fun WorkflowContainer( + workflow: Workflow, noinline onOutput: (OutputT) -> Unit, + viewEnvironment: ViewEnvironment, modifier: Modifier = Modifier, diagnosticListener: WorkflowDiagnosticListener? = null ) { - WorkflowContainer(workflow, viewEnvironment, Unit, onOutput, modifier, diagnosticListener) + WorkflowContainer(workflow, Unit, onOutput, viewEnvironment, modifier, diagnosticListener) } /** @@ -209,21 +101,25 @@ import kotlin.coroutines.CoroutineContext * any time [workflow], [diagnosticListener], or the `CoroutineContext` * changes. The runtime will be cancelled when this function stops composing. * + * [Snapshot]s from the runtime will automatically be saved to the current + * [UiSavedStateRegistry][androidx.ui.savedinstancestate.UiSavedStateRegistry]. When the runtime is + * started, if a snapshot exists in the registry, it will be used to restore the workflows. + * * @param workflow The [Workflow] to render. - * @param viewEnvironment The [ViewEnvironment] used to show the [ComposeRendering]s emitted by - * the workflow. * @param props The props to render the root workflow with. If this value changes between calls, * the workflow runtime will re-render with the new props. + * @param viewEnvironment The [ViewEnvironment] used to display renderings. + * @param modifier The [Modifier] to apply to the root [ViewFactory]. * @param diagnosticListener A [WorkflowDiagnosticListener] to configure on the runtime. */ -@Composable inline fun WorkflowContainer( - workflow: Workflow, - viewEnvironment: ViewEnvironment, +@Composable inline fun WorkflowContainer( + workflow: Workflow, props: PropsT, + viewEnvironment: ViewEnvironment, modifier: Modifier = Modifier, diagnosticListener: WorkflowDiagnosticListener? = null ) { - WorkflowContainer(workflow, viewEnvironment, props, {}, modifier, diagnosticListener) + WorkflowContainer(workflow, props, {}, viewEnvironment, modifier, diagnosticListener) } /** @@ -233,111 +129,28 @@ import kotlin.coroutines.CoroutineContext * any time [workflow], [diagnosticListener], or the `CoroutineContext` * changes. The runtime will be cancelled when this function stops composing. * + * [Snapshot]s from the runtime will automatically be saved to the current + * [UiSavedStateRegistry][androidx.ui.savedinstancestate.UiSavedStateRegistry]. When the runtime is + * started, if a snapshot exists in the registry, it will be used to restore the workflows. + * * @param workflow The [Workflow] to render. - * @param viewEnvironment The [ViewEnvironment] used to show the [ComposeRendering]s emitted by - * the workflow. + * @param viewEnvironment The [ViewEnvironment] used to display renderings. + * @param modifier The [Modifier] to apply to the root [ViewFactory]. * @param diagnosticListener A [WorkflowDiagnosticListener] to configure on the runtime. */ -@Composable inline fun WorkflowContainer( - workflow: Workflow, +@Composable inline fun WorkflowContainer( + workflow: Workflow, viewEnvironment: ViewEnvironment, modifier: Modifier = Modifier, diagnosticListener: WorkflowDiagnosticListener? = null ) { - WorkflowContainer(workflow, viewEnvironment, Unit, {}, modifier, diagnosticListener) -} - -/** - * Internal version of [WorkflowContainer] that accepts extra parameters for testing. - */ -@VisibleForTesting(otherwise = PRIVATE) -@Composable internal fun WorkflowContainerImpl( - workflow: Workflow, - props: PropsT, - onOutput: (OutputT) -> Unit, - modifier: Modifier = Modifier, - diagnosticListener: WorkflowDiagnosticListener? = null, - snapshotKey: String? = null, - content: @Composable() (rendering: RenderingT) -> Unit -) { - @Suppress("DEPRECATION") - val rendering = renderAsState( - workflow, props, onOutput, CoroutineContextAmbient.current, diagnosticListener, snapshotKey - ) - - Box(modifier = modifier) { - content(rendering.value) - } -} - -/** - * @param snapshotKey Allows tests to pass in a custom key to use to save/restore the snapshot from - * the [UiSavedStateRegistryAmbient]. If null, will use the default key based on source location. - */ -@Composable private fun renderAsState( - @Pivotal workflow: Workflow, - props: PropsT, - onOutput: (OutputT) -> Unit, - @Pivotal coroutineContext: CoroutineContext, - @Pivotal diagnosticListener: WorkflowDiagnosticListener?, - snapshotKey: String? -): State { - // This can be a StateFlow once coroutines is upgraded to 1.3.6. - val propsChannel = remember { Channel(capacity = CONFLATED) } - propsChannel.offer(props) - - // Need a mutable holder for onOutput so the outputs subscriber created in the onActive block - // will always be able to see the latest value. - val outputCallback = remember { OutputCallback(onOutput) } - outputCallback.onOutput = onOutput - - val renderingState = state { null } - val snapshotState = savedInstanceState(key = snapshotKey, saver = SnapshotSaver) { null } - - // We can't use onActive/on(Pre)Commit because they won't run their callback until after this - // function returns, and we need to run this immediately so we get the rendering synchronously. - val workflowScope = remember { - val coroutineScope = CoroutineScope(coroutineContext + Dispatchers.Main.immediate) - val propsFlow = propsChannel.consumeAsFlow() - .distinctUntilChanged() - - launchWorkflowIn(coroutineScope, workflow, propsFlow, snapshotState.value) { session -> - session.diagnosticListener = diagnosticListener - - // Don't call onOutput directly, since out captured reference won't be changed if the - // if a different argument is passed to observeWorkflow. - session.outputs.onEach { outputCallback.onOutput(it) } - .launchIn(this) - - session.renderingsAndSnapshots - .onEach { (rendering, snapshot) -> - renderingState.value = rendering - snapshotState.value = snapshot - } - .launchIn(this) - } - - return@remember coroutineScope - } - - onDispose { - workflowScope.cancel() - } - - // The value is guaranteed to be set before returning, so this cast is fine. - @Suppress("UNCHECKED_CAST") - return renderingState as State + WorkflowContainer(workflow, Unit, {}, viewEnvironment, modifier, diagnosticListener) } -private object SnapshotSaver : Saver { - override fun SaverScope.save(value: Snapshot?): ByteArray { - return value?.bytes?.toByteArray() ?: ByteArray(0) - } - - override fun restore(value: ByteArray): Snapshot? { - return value.takeUnless { it.isEmpty() } - ?.let { bytes -> Snapshot.of(ByteString.of(*bytes)) } +private fun ViewEnvironment.withFactory(viewFactory: ViewFactory<*>): ViewEnvironment { + return this[ViewRegistry].let { registry -> + if (viewFactory.type !in registry.keys) { + this + (ViewRegistry to registry + viewFactory) + } else this } } - -private class OutputCallback(var onOutput: (OutputT) -> Unit) diff --git a/samples/hello-compose/src/main/java/com/squareup/sample/hellocompose/App.kt b/samples/hello-compose/src/main/java/com/squareup/sample/hellocompose/App.kt index f778326e47..da73f86146 100644 --- a/samples/hello-compose/src/main/java/com/squareup/sample/hellocompose/App.kt +++ b/samples/hello-compose/src/main/java/com/squareup/sample/hellocompose/App.kt @@ -27,27 +27,21 @@ import com.squareup.workflow.diagnostic.SimpleLoggingDiagnosticListener import com.squareup.workflow.ui.ViewEnvironment import com.squareup.workflow.ui.ViewRegistry import com.squareup.workflow.ui.compose.WorkflowContainer -import com.squareup.workflow.ui.compose.WorkflowRendering private val viewRegistry = ViewRegistry(HelloBinding) private val viewEnvironment = ViewEnvironment(viewRegistry) @Composable fun App() { - WorkflowContainer( - workflow = HelloWorkflow, - diagnosticListener = SimpleLoggingDiagnosticListener() - ) { rendering -> - MaterialTheme { - WorkflowRendering( - rendering, - viewEnvironment, - modifier = Modifier.drawBorder( - shape = RoundedCornerShape(10.dp), - size = 10.dp, - color = Color.Magenta - ) - ) - } + MaterialTheme { + WorkflowContainer( + HelloWorkflow, viewEnvironment, + modifier = Modifier.drawBorder( + shape = RoundedCornerShape(10.dp), + size = 10.dp, + color = Color.Magenta + ), + diagnosticListener = SimpleLoggingDiagnosticListener() + ) } } From f54fa51093ffad32b9dff5471fc75fe286f49065 Mon Sep 17 00:00:00 2001 From: Zach Klippenstein Date: Wed, 27 May 2020 20:38:52 -0700 Subject: [PATCH 29/67] Update Compose to dev12. --- buildSrc/src/main/java/Dependencies.kt | 2 +- compose-tooling/api/compose-tooling.api | 6 +-- .../compose/tooling/PlaceholderViewFactory.kt | 13 +++--- core-compose/api/core-compose.api | 39 +++++++----------- .../ui/compose/ComposeViewFactoryTest.kt | 7 ++-- .../ui/compose/internal/ComposeSupportTest.kt | 12 ++++-- .../ui/compose/internal/ComposeSupport.kt | 40 +++++-------------- .../hellocomposebinding/HelloBinding.kt | 14 +++---- .../HelloRenderingWorkflow.kt | 19 +++++---- .../sample/hellocompose/HelloBinding.kt | 17 ++++---- settings.gradle.kts | 4 +- 11 files changed, 73 insertions(+), 100 deletions(-) diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt index 9ac3f8f256..42b941cd9d 100644 --- a/buildSrc/src/main/java/Dependencies.kt +++ b/buildSrc/src/main/java/Dependencies.kt @@ -19,7 +19,7 @@ import java.util.Locale.US import kotlin.reflect.full.declaredMembers object Versions { - const val compose = "0.1.0-dev11" + const val compose = "0.1.0-dev12" const val kotlin = "1.3.71" const val targetSdk = 29 const val workflow = "0.28.0" diff --git a/compose-tooling/api/compose-tooling.api b/compose-tooling/api/compose-tooling.api index c283e9b945..6af39e7136 100644 --- a/compose-tooling/api/compose-tooling.api +++ b/compose-tooling/api/compose-tooling.api @@ -6,12 +6,10 @@ public final class com/squareup/workflow/ui/compose/tooling/BuildConfig { } public final class com/squareup/workflow/ui/compose/tooling/ComposeWorkflowsKt { - public static final fun preview (Lcom/squareup/workflow/ui/compose/ComposeWorkflow;Ljava/lang/Object;Landroidx/ui/core/Modifier;Landroidx/ui/core/Modifier;Lkotlin/jvm/functions/Function1;Landroidx/compose/Composer;)V - public static synthetic fun preview$default (Lcom/squareup/workflow/ui/compose/ComposeWorkflow;Ljava/lang/Object;Landroidx/ui/core/Modifier;Landroidx/ui/core/Modifier;Lkotlin/jvm/functions/Function1;Landroidx/compose/Composer;ILjava/lang/Object;)V + public static final fun preview (Lcom/squareup/workflow/ui/compose/ComposeWorkflow;Ljava/lang/Object;Landroidx/ui/core/Modifier;Landroidx/ui/core/Modifier;Lkotlin/jvm/functions/Function1;Landroidx/compose/Composer;III)V } public final class com/squareup/workflow/ui/compose/tooling/ViewFactoriesKt { - public static final fun preview (Lcom/squareup/workflow/ui/ViewFactory;Ljava/lang/Object;Landroidx/ui/core/Modifier;Landroidx/ui/core/Modifier;Lkotlin/jvm/functions/Function1;Landroidx/compose/Composer;)V - public static synthetic fun preview$default (Lcom/squareup/workflow/ui/ViewFactory;Ljava/lang/Object;Landroidx/ui/core/Modifier;Landroidx/ui/core/Modifier;Lkotlin/jvm/functions/Function1;Landroidx/compose/Composer;ILjava/lang/Object;)V + public static final fun preview (Lcom/squareup/workflow/ui/ViewFactory;Ljava/lang/Object;Landroidx/ui/core/Modifier;Landroidx/ui/core/Modifier;Lkotlin/jvm/functions/Function1;Landroidx/compose/Composer;III)V } diff --git a/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/PlaceholderViewFactory.kt b/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/PlaceholderViewFactory.kt index 9987b25b81..5c66c40a93 100644 --- a/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/PlaceholderViewFactory.kt +++ b/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/PlaceholderViewFactory.kt @@ -18,7 +18,6 @@ package com.squareup.workflow.ui.compose.tooling import androidx.compose.Composable -import androidx.ui.core.DrawScope import androidx.ui.core.Modifier import androidx.ui.core.clipToBounds import androidx.ui.core.drawBehind @@ -26,12 +25,14 @@ import androidx.ui.foundation.Box import androidx.ui.foundation.Text import androidx.ui.foundation.drawBorder import androidx.ui.geometry.Offset +import androidx.ui.geometry.toRect import androidx.ui.graphics.Color import androidx.ui.graphics.Paint import androidx.ui.graphics.Shadow -import androidx.ui.graphics.painter.Stroke -import androidx.ui.graphics.painter.drawCanvas -import androidx.ui.graphics.painter.rotate +import androidx.ui.graphics.drawscope.DrawScope +import androidx.ui.graphics.drawscope.Stroke +import androidx.ui.graphics.drawscope.drawCanvas +import androidx.ui.graphics.drawscope.rotate import androidx.ui.graphics.withSaveLayer import androidx.ui.layout.fillMaxSize import androidx.ui.text.TextStyle @@ -39,8 +40,6 @@ import androidx.ui.text.style.TextAlign import androidx.ui.tooling.preview.Preview import androidx.ui.unit.Dp import androidx.ui.unit.dp -import androidx.ui.unit.px -import androidx.ui.unit.toRect import com.squareup.workflow.ui.ViewFactory import com.squareup.workflow.ui.compose.composedViewFactory @@ -70,7 +69,7 @@ internal fun placeholderViewFactory(modifier: Modifier): ViewFactory = style = TextStyle( textAlign = TextAlign.Center, color = Color.White, - shadow = Shadow(blurRadius = 5.px, color = Color.Black) + shadow = Shadow(blurRadius = 5f, color = Color.Black) ) ) } diff --git a/core-compose/api/core-compose.api b/core-compose/api/core-compose.api index 902736760e..86dd0c515a 100644 --- a/core-compose/api/core-compose.api +++ b/core-compose/api/core-compose.api @@ -1,7 +1,7 @@ public final class com/squareup/workflow/ui/compose/ComposeRendering { public static final field Companion Lcom/squareup/workflow/ui/compose/ComposeRendering$Companion; public static final fun ()V - public fun (Lkotlin/jvm/functions/Function2;)V + public fun (Lkotlin/jvm/functions/Function4;)V } public final class com/squareup/workflow/ui/compose/ComposeRendering$Companion { @@ -10,7 +10,7 @@ public final class com/squareup/workflow/ui/compose/ComposeRendering$Companion { } public final class com/squareup/workflow/ui/compose/ComposeViewFactory : com/squareup/workflow/ui/ViewFactory { - public fun (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function3;)V + public fun (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function5;)V public fun buildView (Ljava/lang/Object;Lcom/squareup/workflow/ui/ViewEnvironment;Landroid/content/Context;Landroid/view/ViewGroup;)Landroid/view/View; public fun getType ()Lkotlin/reflect/KClass; } @@ -18,44 +18,35 @@ public final class com/squareup/workflow/ui/compose/ComposeViewFactory : com/squ public abstract class com/squareup/workflow/ui/compose/ComposeWorkflow : com/squareup/workflow/Workflow { public fun ()V public fun asStatefulWorkflow ()Lcom/squareup/workflow/StatefulWorkflow; - public abstract fun render (Ljava/lang/Object;Lcom/squareup/workflow/Sink;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/compose/Composer;)V + public abstract fun render (Ljava/lang/Object;Lcom/squareup/workflow/Sink;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/compose/Composer;II)V } public final class com/squareup/workflow/ui/compose/ComposeWorkflowKt { - public static final fun composed (Lcom/squareup/workflow/Workflow$Companion;Lkotlin/jvm/functions/Function4;)Lcom/squareup/workflow/ui/compose/ComposeWorkflow; + public static final fun composed (Lcom/squareup/workflow/Workflow$Companion;Lkotlin/jvm/functions/Function6;)Lcom/squareup/workflow/ui/compose/ComposeWorkflow; } public final class com/squareup/workflow/ui/compose/CompositionRootKt { public static final fun ()V - public static final fun withCompositionRoot (Lcom/squareup/workflow/ui/ViewEnvironment;Lkotlin/jvm/functions/Function2;)Lcom/squareup/workflow/ui/ViewEnvironment; - public static final fun withCompositionRoot (Lcom/squareup/workflow/ui/ViewRegistry;Lkotlin/jvm/functions/Function2;)Lcom/squareup/workflow/ui/ViewRegistry; + public static final fun withCompositionRoot (Lcom/squareup/workflow/ui/ViewEnvironment;Lkotlin/jvm/functions/Function4;)Lcom/squareup/workflow/ui/ViewEnvironment; + public static final fun withCompositionRoot (Lcom/squareup/workflow/ui/ViewRegistry;Lkotlin/jvm/functions/Function4;)Lcom/squareup/workflow/ui/ViewRegistry; } public final class com/squareup/workflow/ui/compose/RenderAsStateKt { - public static final fun renderAsState (Lcom/squareup/workflow/Workflow;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;)Landroidx/compose/State; - public static final fun renderAsState (Lcom/squareup/workflow/Workflow;Ljava/lang/Object;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;)Landroidx/compose/State; - public static final fun renderAsState (Lcom/squareup/workflow/Workflow;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;)Landroidx/compose/State; - public static final fun renderAsState (Lcom/squareup/workflow/Workflow;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;)Landroidx/compose/State; - public static synthetic fun renderAsState$default (Lcom/squareup/workflow/Workflow;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;ILjava/lang/Object;)Landroidx/compose/State; - public static synthetic fun renderAsState$default (Lcom/squareup/workflow/Workflow;Ljava/lang/Object;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;ILjava/lang/Object;)Landroidx/compose/State; - public static synthetic fun renderAsState$default (Lcom/squareup/workflow/Workflow;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;ILjava/lang/Object;)Landroidx/compose/State; - public static synthetic fun renderAsState$default (Lcom/squareup/workflow/Workflow;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;ILjava/lang/Object;)Landroidx/compose/State; + public static final fun renderAsState (Lcom/squareup/workflow/Workflow;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;III)Landroidx/compose/State; + public static final fun renderAsState (Lcom/squareup/workflow/Workflow;Ljava/lang/Object;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;III)Landroidx/compose/State; + public static final fun renderAsState (Lcom/squareup/workflow/Workflow;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;III)Landroidx/compose/State; + public static final fun renderAsState (Lcom/squareup/workflow/Workflow;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;III)Landroidx/compose/State; } public final class com/squareup/workflow/ui/compose/ViewEnvironmentsKt { - public static final fun WorkflowRendering (Ljava/lang/Object;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/ui/core/Modifier;Landroidx/compose/Composer;)V - public static synthetic fun WorkflowRendering$default (Ljava/lang/Object;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/ui/core/Modifier;Landroidx/compose/Composer;ILjava/lang/Object;)V + public static final fun WorkflowRendering (Ljava/lang/Object;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/ui/core/Modifier;Landroidx/compose/Composer;III)V } public final class com/squareup/workflow/ui/compose/WorkflowContainerKt { - public static final fun WorkflowContainer (Lcom/squareup/workflow/Workflow;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;)V - public static final fun WorkflowContainer (Lcom/squareup/workflow/Workflow;Ljava/lang/Object;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;)V - public static final fun WorkflowContainer (Lcom/squareup/workflow/Workflow;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;)V - public static final fun WorkflowContainer (Lcom/squareup/workflow/Workflow;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;)V - public static synthetic fun WorkflowContainer$default (Lcom/squareup/workflow/Workflow;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;ILjava/lang/Object;)V - public static synthetic fun WorkflowContainer$default (Lcom/squareup/workflow/Workflow;Ljava/lang/Object;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;ILjava/lang/Object;)V - public static synthetic fun WorkflowContainer$default (Lcom/squareup/workflow/Workflow;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;ILjava/lang/Object;)V - public static synthetic fun WorkflowContainer$default (Lcom/squareup/workflow/Workflow;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;ILjava/lang/Object;)V + public static final fun WorkflowContainer (Lcom/squareup/workflow/Workflow;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;III)V + public static final fun WorkflowContainer (Lcom/squareup/workflow/Workflow;Ljava/lang/Object;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;III)V + public static final fun WorkflowContainer (Lcom/squareup/workflow/Workflow;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;III)V + public static final fun WorkflowContainer (Lcom/squareup/workflow/Workflow;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;III)V } public final class com/squareup/workflow/ui/compose/internal/ComposeSupportKt { diff --git a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/ComposeViewFactoryTest.kt b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/ComposeViewFactoryTest.kt index 27f84d263f..2bd3c2557e 100644 --- a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/ComposeViewFactoryTest.kt +++ b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/ComposeViewFactoryTest.kt @@ -22,7 +22,6 @@ import androidx.compose.mutableStateOf import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.ui.foundation.Text import androidx.ui.layout.Column -import androidx.ui.test.assertIsDisplayed import androidx.ui.test.createComposeRule import androidx.ui.test.findByText import com.squareup.workflow.ui.ViewEnvironment @@ -53,11 +52,13 @@ class ComposeViewFactoryTest { RootView(viewEnvironment = viewEnvironment) } - findByText("one\ntwo").assertIsDisplayed() + // Compose bug doesn't let us use assertIsDisplayed on older devices. + // See https://issuetracker.google.com/issues/157728188. + findByText("one\ntwo").assertExists() FrameManager.framed { wrapperText.value = "ENO" } - findByText("ENO\ntwo").assertIsDisplayed() + findByText("ENO\ntwo").assertExists() } private class RootView(context: Context) : FrameLayout(context) { diff --git a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/internal/ComposeSupportTest.kt b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/internal/ComposeSupportTest.kt index 0c6a10d114..1a23845d3b 100644 --- a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/internal/ComposeSupportTest.kt +++ b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/internal/ComposeSupportTest.kt @@ -30,8 +30,8 @@ import androidx.compose.currentComposer import androidx.compose.mutableStateOf import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.ui.foundation.Text -import androidx.ui.test.assertIsDisplayed import androidx.ui.test.createComposeRule +import androidx.ui.test.findBySubstring import androidx.ui.test.findByText import org.junit.Rule import org.junit.Test @@ -47,7 +47,9 @@ class ComposeSupportTest { TestComposable("foo") } - findByText("foo").assertIsDisplayed() + // Compose bug doesn't let us use assertIsDisplayed on older devices. + // See https://issuetracker.google.com/issues/157728188. + findByText("foo").assertExists() } @Test fun ambientChangesPassThroughSubcomposition() { @@ -56,11 +58,13 @@ class ComposeSupportTest { TestComposable(ambientValue.value) } - findByText("foo").assertIsDisplayed() + // Compose bug doesn't let us use assertIsDisplayed on older devices. + // See https://issuetracker.google.com/issues/157728188. + findBySubstring("foo").assertExists() FrameManager.framed { ambientValue.value = "bar" } - findByText("bar").assertIsDisplayed() + findByText("bar").assertExists() } @Composable private fun TestComposable(ambientValue: String) { diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ComposeSupport.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ComposeSupport.kt index 72c97918a2..f5a5b9b7ff 100644 --- a/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ComposeSupport.kt +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ComposeSupport.kt @@ -18,25 +18,19 @@ package com.squareup.workflow.ui.compose.internal import android.content.Context -import android.view.View import android.view.ViewGroup import androidx.compose.Composable import androidx.compose.Composition import androidx.compose.CompositionReference +import androidx.compose.FrameManager import androidx.compose.Recomposer import androidx.compose.compositionFor -import androidx.lifecycle.LifecycleOwner +import androidx.ui.core.AndroidOwner import androidx.ui.node.UiComposer -import com.squareup.workflow.ui.compose.internal.ReflectionSupport.ANDROID_OWNER_CLASS -import com.squareup.workflow.ui.compose.internal.ReflectionSupport.androidOwnerView -import com.squareup.workflow.ui.compose.internal.ReflectionSupport.createOwner import com.squareup.workflow.ui.compose.internal.ReflectionSupport.createWrappedContent -import com.squareup.workflow.ui.compose.internal.ReflectionSupport.ownerRoot import com.squareup.workflow.ui.core.compose.R -private typealias AndroidOwner = Any private typealias WrappedComposition = Composition -private typealias LayoutNode = Any private val DefaultLayoutParams = ViewGroup.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, @@ -59,12 +53,13 @@ internal fun ViewGroup.setContent( parent: CompositionReference, content: @Composable() () -> Unit ): Composition { + FrameManager.ensureStarted() val composeView: AndroidOwner = if (childCount > 0) { - getChildAt(0).takeIf(ANDROID_OWNER_CLASS::isInstance) + getChildAt(0) as? AndroidOwner } else { removeAllViews(); null - } ?: createOwner(context).also { addView(androidOwnerView(it), DefaultLayoutParams) } + } ?: AndroidOwner(context).also { addView(it.view, DefaultLayoutParams) } return doSetContent(context, composeView, recomposer, parent, content) } @@ -80,21 +75,20 @@ private fun doSetContent( content: @Composable() () -> Unit ): Composition { // val original = compositionFor(context, owner.root, recomposer) - val container = ownerRoot(owner) val original = compositionFor( - container = container, + container = owner.root, recomposer = recomposer, parent = parent, composerFactory = { slotTable, factoryRecomposer -> - UiComposer(context, container, slotTable, factoryRecomposer) + UiComposer(context, owner.root, slotTable, factoryRecomposer) } ) - val wrapped = androidOwnerView(owner).getTag(R.id.wrapped_composition_tag) + val wrapped = owner.view.getTag(R.id.wrapped_composition_tag) as? WrappedComposition // ?: WrappedComposition(owner, original).also { ?: createWrappedContent(owner, original).also { - androidOwnerView(owner).setTag(R.id.wrapped_composition_tag, it) + owner.view.setTag(R.id.wrapped_composition_tag, it) } wrapped.setContent(content) return wrapped @@ -102,31 +96,17 @@ private fun doSetContent( private object ReflectionSupport { - val ANDROID_OWNER_CLASS = Class.forName("androidx.ui.core.AndroidOwner") private val WRAPPED_COMPOSITION_CLASS = Class.forName("androidx.ui.core.WrappedComposition") - private val ANDROID_OWNER_KT_CLASS = Class.forName("androidx.ui.core.AndroidOwnerKt") private val WRAPPED_COMPOSITION_CTOR = - WRAPPED_COMPOSITION_CLASS.getConstructor(ANDROID_OWNER_CLASS, Composition::class.java) - - private val CREATE_OWNER_FUN = - ANDROID_OWNER_KT_CLASS.getMethod("createOwner", Context::class.java, LifecycleOwner::class.java) - private val ANDROID_OWNER_ROOT_GETTER = ANDROID_OWNER_CLASS.getMethod("getRoot") + WRAPPED_COMPOSITION_CLASS.getConstructor(AndroidOwner::class.java, Composition::class.java) init { WRAPPED_COMPOSITION_CTOR.isAccessible = true } - fun createOwner(context: Context): AndroidOwner = - CREATE_OWNER_FUN.invoke(null, context, null) as AndroidOwner - - fun ownerRoot(owner: AndroidOwner): LayoutNode = - ANDROID_OWNER_ROOT_GETTER.invoke(owner) as LayoutNode - fun createWrappedContent( owner: AndroidOwner, original: Composition ): WrappedComposition = WRAPPED_COMPOSITION_CTOR.newInstance(owner, original) as Composition - - fun androidOwnerView(owner: AndroidOwner): View = owner as View } diff --git a/samples/hello-compose-binding/src/main/java/com/squareup/sample/hellocomposebinding/HelloBinding.kt b/samples/hello-compose-binding/src/main/java/com/squareup/sample/hellocomposebinding/HelloBinding.kt index f51613abe2..a91020fad4 100644 --- a/samples/hello-compose-binding/src/main/java/com/squareup/sample/hellocomposebinding/HelloBinding.kt +++ b/samples/hello-compose-binding/src/main/java/com/squareup/sample/hellocomposebinding/HelloBinding.kt @@ -18,8 +18,8 @@ package com.squareup.sample.hellocomposebinding import androidx.compose.Composable import androidx.ui.core.Alignment import androidx.ui.core.Modifier -import androidx.ui.foundation.Clickable import androidx.ui.foundation.Text +import androidx.ui.foundation.clickable import androidx.ui.layout.fillMaxSize import androidx.ui.layout.wrapContentSize import androidx.ui.material.ripple.ripple @@ -29,13 +29,13 @@ import com.squareup.workflow.ui.compose.composedViewFactory import com.squareup.workflow.ui.compose.tooling.preview val HelloBinding = composedViewFactory { rendering, _ -> - Clickable( + Text( + rendering.message, modifier = Modifier.fillMaxSize() - .ripple(bounded = true), - onClick = { rendering.onClick() } - ) { - Text(rendering.message, modifier = Modifier.wrapContentSize(Alignment.Center)) - } + .ripple() + .clickable(onClick = rendering.onClick) + .wrapContentSize(Alignment.Center) + ) } @Preview(heightDp = 150, showBackground = true) diff --git a/samples/hello-compose-rendering/src/main/java/com/squareup/sample/hellocomposerendering/HelloRenderingWorkflow.kt b/samples/hello-compose-rendering/src/main/java/com/squareup/sample/hellocomposerendering/HelloRenderingWorkflow.kt index 1cd1906cc2..7faf8e71ed 100644 --- a/samples/hello-compose-rendering/src/main/java/com/squareup/sample/hellocomposerendering/HelloRenderingWorkflow.kt +++ b/samples/hello-compose-rendering/src/main/java/com/squareup/sample/hellocomposerendering/HelloRenderingWorkflow.kt @@ -18,17 +18,16 @@ package com.squareup.sample.hellocomposerendering import androidx.compose.Composable import androidx.ui.core.Alignment import androidx.ui.core.Modifier -import androidx.ui.foundation.Clickable import androidx.ui.foundation.Text -import androidx.ui.layout.fillMaxSize +import androidx.ui.foundation.clickable import androidx.ui.layout.wrapContentSize import androidx.ui.material.MaterialTheme import androidx.ui.material.ripple.ripple import androidx.ui.tooling.preview.Preview import com.squareup.sample.hellocomposerendering.HelloRenderingWorkflow.Toggle import com.squareup.workflow.Sink -import com.squareup.workflow.ui.compose.ComposeWorkflow import com.squareup.workflow.ui.ViewEnvironment +import com.squareup.workflow.ui.compose.ComposeWorkflow import com.squareup.workflow.ui.compose.tooling.preview /** @@ -46,13 +45,13 @@ object HelloRenderingWorkflow : ComposeWorkflow() { viewEnvironment: ViewEnvironment ) { MaterialTheme { - Clickable( - onClick = { outputSink.send(Toggle) }, - modifier = Modifier.ripple(bounded = true) - .fillMaxSize() - ) { - Text(props, modifier = Modifier.wrapContentSize(Alignment.Center)) - } + Text( + props, + modifier = Modifier + .ripple() + .clickable(onClick = { outputSink.send(Toggle) }) + .wrapContentSize(Alignment.Center) + ) } } } diff --git a/samples/hello-compose/src/main/java/com/squareup/sample/hellocompose/HelloBinding.kt b/samples/hello-compose/src/main/java/com/squareup/sample/hellocompose/HelloBinding.kt index 57d19a875c..5b39a449ab 100644 --- a/samples/hello-compose/src/main/java/com/squareup/sample/hellocompose/HelloBinding.kt +++ b/samples/hello-compose/src/main/java/com/squareup/sample/hellocompose/HelloBinding.kt @@ -17,20 +17,19 @@ package com.squareup.sample.hellocompose import androidx.ui.core.Alignment import androidx.ui.core.Modifier -import androidx.ui.foundation.Clickable import androidx.ui.foundation.Text -import androidx.ui.layout.fillMaxSize +import androidx.ui.foundation.clickable import androidx.ui.layout.wrapContentSize import androidx.ui.material.ripple.ripple import com.squareup.sample.hellocompose.HelloWorkflow.Rendering import com.squareup.workflow.ui.compose.composedViewFactory val HelloBinding = composedViewFactory { rendering, _ -> - Clickable( - onClick = { rendering.onClick() }, - modifier = Modifier.ripple(bounded = true) - .fillMaxSize() - ) { - Text(rendering.message, modifier = Modifier.wrapContentSize(Alignment.Center)) - } + Text( + rendering.message, + modifier = Modifier + .ripple() + .clickable(onClick = rendering.onClick) + .wrapContentSize(Alignment.Center) + ) } diff --git a/settings.gradle.kts b/settings.gradle.kts index a273408d87..7b1a1e0a84 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -20,6 +20,8 @@ include( ":core-compose", ":samples:hello-compose", ":samples:hello-compose-binding", - ":samples:hello-compose-rendering", + // This module is crashing the compiler with dev12. Just disable it for now. + // See https://github.com/square/workflow-kotlin-compose/issues/42. + // ":samples:hello-compose-rendering", ":samples:nested-renderings" ) From 4158c4964f0eac2bb100e0dd463dee4242b5d32c Mon Sep 17 00:00:00 2001 From: Zach Klippenstein Date: Wed, 20 May 2020 17:08:04 -0700 Subject: [PATCH 30/67] Fix subcomposition of ComposableViewFactory and WorkflowRendering. Fixes to how subcomposition works after discussing with Leland. Currently, there are two cases for calling `ViewGroup.setContent`: 1. **Root composition (no parent `CompositionReference`).** In this case, compose will throw if called multiple times, so we only call it once and then use a `MutableState` to feed rendering updates to it. 2. **Subcomposition.** In this case, we _must_ call `setContent` again (i.e. manually recompose the child composition) every time the composition that the `CompositionReference` belongs to is recomposed. This is not documented anywhere, but is apparently part of the contract. In this case, we call `setContent` every time we get a rendering update. This also pulls the `bindView` call back into a custom view. I'm not entirely sure why, but there seems to be a timing issue with initializing all the composables and views when the logic is all done inside compose. Using a custom view also means we don't need to access `OwnerAmbient`. **Known issues:** - #43: This issue did not occur with dev11, so I think it's a Compose bug. I haven't officially filed yet since distilling a reproducer is probably going to be a lot of work. --- .../ui/compose/internal/ComposeSupportTest.kt | 7 +- .../workflow/ui/compose/ComposeViewFactory.kt | 59 +++++------ .../workflow/ui/compose/CompositionRoot.kt | 8 +- .../ui/compose/internal/ComposeSupport.kt | 12 +-- .../ui/compose/internal/ParentComposition.kt | 53 ++-------- .../ui/compose/internal/ViewFactories.kt | 98 +++++++++++++------ 6 files changed, 116 insertions(+), 121 deletions(-) diff --git a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/internal/ComposeSupportTest.kt b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/internal/ComposeSupportTest.kt index 1a23845d3b..9318c83d08 100644 --- a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/internal/ComposeSupportTest.kt +++ b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/internal/ComposeSupportTest.kt @@ -23,10 +23,8 @@ import androidx.compose.Composable import androidx.compose.CompositionReference import androidx.compose.FrameManager import androidx.compose.Providers -import androidx.compose.Recomposer import androidx.compose.ambientOf import androidx.compose.compositionReference -import androidx.compose.currentComposer import androidx.compose.mutableStateOf import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.ui.foundation.Text @@ -76,21 +74,20 @@ class ComposeSupportTest { } @Composable private fun LegacyHostComposable(leafContent: @Composable() () -> Unit) { - val wormhole = Wormhole(currentComposer.recomposer, compositionReference(), leafContent) + val wormhole = Wormhole(compositionReference(), leafContent) // This is valid Compose code, but the IDE doesn't know that yet so it will show an // unsuppressable error. WormholeView(wormhole = wormhole) } private class Wormhole( - val recomposer: Recomposer, val parentReference: CompositionReference, val childContent: @Composable() () -> Unit ) private class WormholeView(context: Context) : FrameLayout(context) { fun setWormhole(wormhole: Wormhole) { - setContent(wormhole.recomposer, wormhole.parentReference, wormhole.childContent) + setContent(wormhole.parentReference, wormhole.childContent) } } diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactory.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactory.kt index 648ab3f806..772234668b 100644 --- a/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactory.kt +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactory.kt @@ -24,13 +24,12 @@ import android.view.ViewGroup import android.widget.FrameLayout import androidx.compose.Composable import androidx.compose.FrameManager -import androidx.compose.StructurallyEqual import androidx.compose.mutableStateOf import com.squareup.workflow.ui.ViewEnvironment import com.squareup.workflow.ui.ViewFactory import com.squareup.workflow.ui.bindShowRendering import com.squareup.workflow.ui.compose.internal.ParentComposition -import com.squareup.workflow.ui.compose.internal.setOrSubcomposeContent +import com.squareup.workflow.ui.compose.internal.setContent import kotlin.reflect.KClass /** @@ -100,38 +99,42 @@ internal class ComposeViewFactory( ): View { // There is currently no way to automatically generate an Android View directly from a // Composable function, so we need to use ViewGroup.setContent. + val parentComposition = initialViewEnvironment[ParentComposition].reference val composeContainer = FrameLayout(contextForNewView) - // Create a single MutableState to feed state updates into the composition. - // We could also have two separate MutableStates, but using a Pair both makes it clear and - // enforces that both values are always updated together. - val renderState = mutableStateOf?>( - // This will be updated immediately by bindShowRendering below. - value = null, - areEquivalent = StructurallyEqual - ) + if (parentComposition == null) { + // This composition will be the "root" – it must not be recomposed. - // Update the state whenever a new rendering is emitted. - composeContainer.bindShowRendering( - initialRendering, - initialViewEnvironment - ) { rendering, environment -> - // This lambda will be executed synchronously before bindShowRendering returns. + val state = mutableStateOf(Pair(initialRendering, initialViewEnvironment)) + composeContainer.bindShowRendering( + initialRendering, + initialViewEnvironment + ) { rendering, environment -> + FrameManager.framed { + state.value = Pair(rendering, environment) + } + } - // Models will throw if their properties are accessed when there is no frame open. Currently, - // that will be the case if the model is accessed before any other Compose infrastructure has - // run, i.e. if this view factory is the first compose code to run in the app. - // I believe that eventually there will be a global frame that will make this unnecessary. - FrameManager.framed { - renderState.value = Pair(rendering, environment) + composeContainer.setContent(parent = null) { + val (rendering, environment) = state.value + content(rendering, environment) } - } + } else { + // This composition will be a subcomposition of another composition, we must recompose it + // manually every time something changes. This is not documented anywhere, but according to + // Compose devs it is part of the contract of subcomposition. - // Entry point to the world of Compose. - val parentComposition = initialViewEnvironment[ParentComposition] - composeContainer.setOrSubcomposeContent(parentComposition.reference) { - val (rendering, environment) = renderState.value!! - content(rendering, environment) + // Update the state whenever a new rendering is emitted. + // This lambda will be executed synchronously before bindShowRendering returns. + composeContainer.bindShowRendering( + initialRendering, + initialViewEnvironment + ) { rendering, environment -> + // Entry point to the world of Compose. + composeContainer.setContent(parentComposition) { + content(rendering, environment) + } + } } return composeContainer diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/CompositionRoot.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/CompositionRoot.kt index b01dc6fa04..fb9ca861dd 100644 --- a/core-compose/src/main/java/com/squareup/workflow/ui/compose/CompositionRoot.kt +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/CompositionRoot.kt @@ -26,7 +26,6 @@ import androidx.compose.staticAmbientOf import com.squareup.workflow.ui.ViewEnvironment import com.squareup.workflow.ui.ViewRegistry import com.squareup.workflow.ui.compose.internal.mapFactories -import kotlin.reflect.KClass /** * Used by [wrapWithRootIfNecessary] to ensure the [CompositionRoot] is only applied once. @@ -58,12 +57,13 @@ fun ViewEnvironment.withCompositionRoot(root: CompositionRoot): ViewEnvironment */ fun ViewRegistry.withCompositionRoot(root: CompositionRoot): ViewRegistry = mapFactories { factory -> - if (factory !is ComposeViewFactory) return@mapFactories factory + @Suppress("UNCHECKED_CAST", "SafeCastWithReturn") + factory as? ComposeViewFactory ?: return@mapFactories factory @Suppress("UNCHECKED_CAST") - ComposeViewFactory(factory.type as KClass) { rendering, environment -> + ComposeViewFactory(factory.type) { rendering, environment -> wrapWithRootIfNecessary(root) { - (factory as ComposeViewFactory).content(rendering, environment) + factory.content(rendering, environment) } } } diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ComposeSupport.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ComposeSupport.kt index f5a5b9b7ff..2b716d4a25 100644 --- a/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ComposeSupport.kt +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ComposeSupport.kt @@ -49,8 +49,7 @@ private val DefaultLayoutParams = ViewGroup.LayoutParams( * [here](https://issuetracker.google.com/issues/156527486). */ internal fun ViewGroup.setContent( - recomposer: Recomposer, - parent: CompositionReference, + parent: CompositionReference?, content: @Composable() () -> Unit ): Composition { FrameManager.ensureStarted() @@ -60,7 +59,7 @@ internal fun ViewGroup.setContent( } else { removeAllViews(); null } ?: AndroidOwner(context).also { addView(it.view, DefaultLayoutParams) } - return doSetContent(context, composeView, recomposer, parent, content) + return doSetContent(context, composeView, Recomposer.current(), parent, content) } /** @@ -71,7 +70,7 @@ private fun doSetContent( context: Context, owner: AndroidOwner, recomposer: Recomposer, - parent: CompositionReference, + parent: CompositionReference?, content: @Composable() () -> Unit ): Composition { // val original = compositionFor(context, owner.root, recomposer) @@ -100,10 +99,7 @@ private object ReflectionSupport { private val WRAPPED_COMPOSITION_CTOR = WRAPPED_COMPOSITION_CLASS.getConstructor(AndroidOwner::class.java, Composition::class.java) - - init { - WRAPPED_COMPOSITION_CTOR.isAccessible = true - } + .apply { isAccessible = true } fun createWrappedContent( owner: AndroidOwner, diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ParentComposition.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ParentComposition.kt index 454365def5..3725d65bad 100644 --- a/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ParentComposition.kt +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ParentComposition.kt @@ -17,24 +17,19 @@ package com.squareup.workflow.ui.compose.internal -import android.view.ViewGroup -import androidx.compose.Composable import androidx.compose.CompositionReference -import androidx.compose.Recomposer -import androidx.compose.compositionReference -import androidx.ui.core.setContent import com.squareup.workflow.ui.ViewEnvironment import com.squareup.workflow.ui.ViewEnvironmentKey /** - * Holds a [CompositionReference] and that can be passed to [setOrSubcomposeContent] to create a - * composition that is a child of another composition. Subcompositions get ambients and other - * compose context from their parent, and propagate invalidations, which allows ambients provided - * around a [WorkflowRendering] call to be read by nested Compose-based view factories. + * Holds a [CompositionReference] and that can be passed to [setContent] to create a composition + * that is a child of another composition. Subcompositions get ambients and other compose context + * from their parent, and propagate invalidations, which allows ambients provided around a + * [WorkflowRendering] call to be read by nested Compose-based view factories. * - * When [WorkflowRendering] is called, it will store an instance of this class in the [ViewEnvironment]. - * [ComposeViewFactory] pulls the reference out of the environment and uses it to link its - * composition to the outer one. + * When [WorkflowRendering] is called, it will store an instance of this class in the + * [ViewEnvironment]. `ComposeViewFactory` pulls the reference out of the environment and uses it to + * link its composition to the outer one. */ internal class ParentComposition( var reference: CompositionReference? = null @@ -43,37 +38,3 @@ internal class ParentComposition( override val default: ParentComposition get() = ParentComposition() } } - -/** - * Creates a [ParentComposition] from the current point in the composition and adds it to this - * [ViewEnvironment]. - */ -@Composable internal fun ViewEnvironment.withParentComposition( - reference: CompositionReference = compositionReference() -): ViewEnvironment { - val compositionReference = ParentComposition(reference = reference) - return this + (ParentComposition to compositionReference) -} - -/** - * Starts composing [content] into this [ViewGroup]. - * - * If [parentComposition] is not null, [content] will be installed as a _subcomposition_ of the - * parent composition, meaning that it will propagate ambients and invalidation. - * - * This function corresponds to [withParentComposition]. - */ -internal fun ViewGroup.setOrSubcomposeContent( - parentComposition: CompositionReference?, - content: @Composable() () -> Unit -) { - if (parentComposition != null) { - // Somewhere above us in the workflow rendering tree, there's another composedViewFactory. - // We need to link to its composition reference so we inherit its ambients. - setContent(Recomposer.current(), parentComposition, content) - } else { - // This is the first composedViewFactory in the rendering tree, so it won't be a child - // composition. - setContent(Recomposer.current(), content) - } -} diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ViewFactories.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ViewFactories.kt index 4e1a7feec8..8f2302185f 100644 --- a/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ViewFactories.kt +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ViewFactories.kt @@ -15,24 +15,22 @@ */ package com.squareup.workflow.ui.compose.internal +import android.content.Context import android.view.View -import android.view.ViewGroup +import android.view.ViewGroup.LayoutParams.MATCH_PARENT +import android.widget.FrameLayout import androidx.compose.Composable import androidx.compose.compositionReference -import androidx.compose.onPreCommit import androidx.compose.remember -import androidx.ui.core.AndroidOwner -import androidx.ui.core.ContextAmbient import androidx.ui.core.Modifier -import androidx.ui.core.OwnerAmbient -import androidx.ui.core.Ref import androidx.ui.foundation.Box -import androidx.ui.viewinterop.AndroidView import com.squareup.workflow.ui.ViewEnvironment import com.squareup.workflow.ui.ViewFactory import com.squareup.workflow.ui.canShowRendering import com.squareup.workflow.ui.compose.ComposeViewFactory +import com.squareup.workflow.ui.getRendering import com.squareup.workflow.ui.showRendering +import kotlin.properties.Delegates.observable /** * Renders [rendering] into the composition using [viewFactory]. @@ -88,39 +86,79 @@ import com.squareup.workflow.ui.showRendering rendering: R, viewEnvironment: ViewEnvironment ) { - val childView = remember { Ref() } - // Plumb the current composition through the ViewEnvironment so any nested composable factories - // get access to any ambients currently in effect. See setOrSubcomposeContent(). + // get access to any ambients currently in effect. val parentComposition = remember { ParentComposition() } parentComposition.reference = compositionReference() val wrappedEnvironment = remember(viewEnvironment) { viewEnvironment + (ParentComposition to parentComposition) } - // A view factory can decide to recreate its view at any time. This also covers the case where - // the value of the viewFactory argument has changed, including to one with a different type. - if (childView.value?.canShowRendering(rendering) != true) { - // If we don't pass the parent Android View, the child will have the wrong LayoutParams. - // OwnerAmbient is deprecated, but the only way to get the root view currently. I've filed - // a feature request to expose this as first-class API, see - // https://issuetracker.google.com/issues/156875705. - @Suppress("DEPRECATION") - val parentView = (OwnerAmbient.current as? AndroidOwner)?.view as? ViewGroup + HostView(viewFactory = viewFactory, update = Pair(rendering, wrappedEnvironment)) +} - childView.value = viewFactory.buildView( - initialRendering = rendering, - initialViewEnvironment = wrappedEnvironment, - contextForNewView = ContextAmbient.current, - container = parentView - ) +/** + * This is basically a clone of WorkflowViewStub, but it takes an explicit [ViewFactory] instead + * of looking one up itself, and doesn't do the replace-in-parent trick. + * + * It doesn't seem possible to create the view inside a Composable directly and use + * [androidx.ui.viewinterop.AndroidView]. I can't figure out exactly why it doesn't work, but I + * think it has something to do with getting into an incorrect state if a non-Composable view + * factory synchronously builds and binds a ComposableViewFactory in buildView. In that case, the + * second and subsequent compose passes will lose ambients from the parent composition. I've spent + * a bunch of time trying to debug compose internals and trying different approaches to figure out + * why that is, but nothing makes sense. All I know is that using a custom view like this seems to + * fix it. + * + * …Except in the case where the highest-level ComposableViewFactory isn't a subcomposition (i.e. + * the workflow is being ran with setContentWorkflow instead of WorkflowContainer). Or maybe it's + * only if the top-level ViewFactory is such a ComposableViewFactory, I haven't tested other legacy + * view factories between the root and the top-level CVF. In that case, there seems to be a race + * condition with measuring and second compose pass will throw an exception about an unmeasured + * node. + */ +private class HostView(context: Context) : FrameLayout(context) { + + private var rerender = true + private var view: View? = null + + var viewFactory by observable?>(null) { _, old, new -> + if (old != new) { + update() + } } - // Invoke the ViewFactory's update logic whenever the view, the rendering, or the ViewEnvironment - // change. - onPreCommit(childView.value, rendering, wrappedEnvironment) { - childView.value!!.showRendering(rendering, wrappedEnvironment) + var update by observable?>(null) { _, old, new -> + if (old != new) { + rerender = true + update() + } + } + + init { + layoutParams = LayoutParams(MATCH_PARENT, MATCH_PARENT) } - AndroidView(childView.value!!) + private fun update() { + if (viewFactory == null) return + val (rendering, viewEnvironment) = update ?: return + + if (view?.canShowRendering(rendering) != true) { + // BuildView must call bindShowRendering, which will call showRendering. + @Suppress("UNCHECKED_CAST") + view = (viewFactory as ViewFactory) + .buildView(rendering, viewEnvironment, context, this) + + check(view!!.getRendering() != null) { + "View.bindShowRendering should have been called for $this, typically by the " + + "${ViewFactory::class.java.name} that created it." + } + removeAllViews() + addView(view) + } else if (rerender) { + view!!.showRendering(rendering, viewEnvironment) + } + + rerender = false + } } From bd78a1fbd2aae4bbcadc25df8350d63895b7a28f Mon Sep 17 00:00:00 2001 From: Zach Klippenstein Date: Sun, 31 May 2020 13:41:28 -0700 Subject: [PATCH 31/67] Releasing v0.30.0. --- CHANGELOG.md | 13 +++++++++++++ gradle.properties | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c1747ebe8..2e0df90542 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,19 @@ Change Log ========== +Version 0.30.0 +-------------- + +_2020-05-31_ + +* Update Compose to dev12. (#41) +* New/Breaking: Make `renderAsState` public, make `WorkflowContainer` take a ViewEnvironment. (#32) +* Breaking: Rename `bindCompose` to `composedViewFactory`. (#35) +* Breaking: Rename `showRendering` to `WorkflowRendering` and make it not an extension function. (#36) +* Breaking: Rename `ComposeViewFactoryRoot` to `CompositionRoot` and decouple the implementation. (#34) +* Fix: Make `ViewRegistry.showRendering` update if `ViewRegistry` changes. (#33) +* Fix: Fix subcomposition of ComposableViewFactory and WorkflowRendering. (#37) + Version 0.29.0 -------------- diff --git a/gradle.properties b/gradle.properties index 3778eecbd2..f542bb84d7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -23,7 +23,7 @@ android.enableJetifier=true systemProp.org.gradle.internal.publish.checksums.insecure=true GROUP=com.squareup.workflow -VERSION_NAME=0.30.0-SNAPSHOT +VERSION_NAME=0.30.0 POM_DESCRIPTION=Reactive workflows From 15e354cb14659fb9e71bf90f896262269540f98f Mon Sep 17 00:00:00 2001 From: Zach Klippenstein Date: Sun, 31 May 2020 13:45:04 -0700 Subject: [PATCH 32/67] Finish releasing v0.30.0. --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index f542bb84d7..7b10a35508 100644 --- a/gradle.properties +++ b/gradle.properties @@ -23,7 +23,7 @@ android.enableJetifier=true systemProp.org.gradle.internal.publish.checksums.insecure=true GROUP=com.squareup.workflow -VERSION_NAME=0.30.0 +VERSION_NAME=0.31.0-SNAPSHOT POM_DESCRIPTION=Reactive workflows From 19109c97b4e82fedeba09ef40e7c88f6dabfa2ae Mon Sep 17 00:00:00 2001 From: Zach Klippenstein Date: Wed, 20 May 2020 20:34:11 -0700 Subject: [PATCH 33/67] Merge all samples into one gradle module and app. --- .buildscript/binary-validation.gradle | 2 +- build.gradle.kts | 7 +++ .../ui/compose/internal/ParentComposition.kt | 8 +-- detekt.yml | 20 ++++++ .../build.gradle.kts | 8 ++- .../hello-compose-binding/build.gradle.kts | 43 ------------- .../src/main/AndroidManifest.xml | 38 ------------ .../src/main/res/values/strings.xml | 18 ------ .../src/main/res/values/styles.xml | 23 ------- .../HelloRenderingWorkflow.kt | 62 ------------------- .../src/main/res/values/strings.xml | 18 ------ samples/hello-compose/build.gradle.kts | 48 -------------- .../src/main/AndroidManifest.xml | 38 ------------ .../src/main/res/values/styles.xml | 23 ------- samples/nested-renderings/build.gradle.kts | 44 ------------- .../src/main/AndroidManifest.xml | 38 ------------ .../src/main/res/values/strings.xml | 18 ------ .../src/main/res/values/styles.xml | 23 ------- .../hellocomposebinding/HelloBindingTest.kt | 0 .../HelloComposeRenderingTest.kt | 35 +++++++++++ .../sample/launcher/SampleLauncherTest.kt} | 33 ++++++---- .../nestedrenderings/NestedRenderingsTest.kt | 0 .../src/main/AndroidManifest.xml | 11 ++-- .../com/squareup/sample/hellocompose/App.kt | 0 .../sample/hellocompose/HelloBinding.kt | 0 .../hellocompose/HelloComposeActivity.kt | 0 .../sample/hellocompose/HelloWorkflow.kt | 0 .../hellocomposebinding/HelloBinding.kt | 0 .../HelloBindingActivity.kt | 0 .../hellocomposebinding/HelloWorkflow.kt | 0 .../HelloComposeRenderingActivity.kt | 0 .../HelloRenderingWorkflow.kt | 49 +++++++++++++++ .../hellocomposerendering/HelloWorkflow.kt | 6 +- .../sample/launcher/SampleLauncherActivity.kt | 29 +++++++++ .../sample/launcher/SampleLauncherApp.kt | 62 +++++++++++++++++++ .../com/squareup/sample/launcher/Samples.kt | 48 ++++++++++++++ .../sample/nestedrenderings/LegacyRunner.kt | 2 +- .../NestedRenderingsActivity.kt | 0 .../nestedrenderings/RecursiveViewFactory.kt | 1 + .../nestedrenderings/RecursiveWorkflow.kt | 0 .../src/main/res/layout/legacy_view.xml | 0 .../src/main/res/values/dimens.xml | 0 .../src/main/res/values/strings.xml | 2 +- .../src/main/res/values/styles.xml | 0 settings.gradle.kts | 7 +-- 45 files changed, 296 insertions(+), 468 deletions(-) create mode 100644 detekt.yml rename samples/{hello-compose-rendering => }/build.gradle.kts (90%) delete mode 100644 samples/hello-compose-binding/build.gradle.kts delete mode 100644 samples/hello-compose-binding/src/main/AndroidManifest.xml delete mode 100644 samples/hello-compose-binding/src/main/res/values/strings.xml delete mode 100644 samples/hello-compose-binding/src/main/res/values/styles.xml delete mode 100644 samples/hello-compose-rendering/src/main/java/com/squareup/sample/hellocomposerendering/HelloRenderingWorkflow.kt delete mode 100644 samples/hello-compose-rendering/src/main/res/values/strings.xml delete mode 100644 samples/hello-compose/build.gradle.kts delete mode 100644 samples/hello-compose/src/main/AndroidManifest.xml delete mode 100644 samples/hello-compose/src/main/res/values/styles.xml delete mode 100644 samples/nested-renderings/build.gradle.kts delete mode 100644 samples/nested-renderings/src/main/AndroidManifest.xml delete mode 100644 samples/nested-renderings/src/main/res/values/strings.xml delete mode 100644 samples/nested-renderings/src/main/res/values/styles.xml rename samples/{hello-compose-binding => }/src/androidTest/java/com/squareup/sample/hellocomposebinding/HelloBindingTest.kt (100%) create mode 100644 samples/src/androidTest/java/com/squareup/sample/hellocomposerendering/HelloComposeRenderingTest.kt rename samples/{hello-compose-rendering/src/androidTest/java/com/squareup/sample/hellocomposerendering/HelloComposeRenderingTest.kt => src/androidTest/java/com/squareup/sample/launcher/SampleLauncherTest.kt} (55%) rename samples/{nested-renderings => }/src/androidTest/java/com/squareup/sample/nestedrenderings/NestedRenderingsTest.kt (100%) rename samples/{hello-compose-rendering => }/src/main/AndroidManifest.xml (74%) rename samples/{hello-compose => }/src/main/java/com/squareup/sample/hellocompose/App.kt (100%) rename samples/{hello-compose => }/src/main/java/com/squareup/sample/hellocompose/HelloBinding.kt (100%) rename samples/{hello-compose => }/src/main/java/com/squareup/sample/hellocompose/HelloComposeActivity.kt (100%) rename samples/{hello-compose => }/src/main/java/com/squareup/sample/hellocompose/HelloWorkflow.kt (100%) rename samples/{hello-compose-binding => }/src/main/java/com/squareup/sample/hellocomposebinding/HelloBinding.kt (100%) rename samples/{hello-compose-binding => }/src/main/java/com/squareup/sample/hellocomposebinding/HelloBindingActivity.kt (100%) rename samples/{hello-compose-binding => }/src/main/java/com/squareup/sample/hellocomposebinding/HelloWorkflow.kt (100%) rename samples/{hello-compose-rendering => }/src/main/java/com/squareup/sample/hellocomposerendering/HelloComposeRenderingActivity.kt (100%) create mode 100644 samples/src/main/java/com/squareup/sample/hellocomposerendering/HelloRenderingWorkflow.kt rename samples/{hello-compose-rendering => }/src/main/java/com/squareup/sample/hellocomposerendering/HelloWorkflow.kt (90%) create mode 100644 samples/src/main/java/com/squareup/sample/launcher/SampleLauncherActivity.kt create mode 100644 samples/src/main/java/com/squareup/sample/launcher/SampleLauncherApp.kt create mode 100644 samples/src/main/java/com/squareup/sample/launcher/Samples.kt rename samples/{nested-renderings => }/src/main/java/com/squareup/sample/nestedrenderings/LegacyRunner.kt (96%) rename samples/{nested-renderings => }/src/main/java/com/squareup/sample/nestedrenderings/NestedRenderingsActivity.kt (100%) rename samples/{nested-renderings => }/src/main/java/com/squareup/sample/nestedrenderings/RecursiveViewFactory.kt (99%) rename samples/{nested-renderings => }/src/main/java/com/squareup/sample/nestedrenderings/RecursiveWorkflow.kt (100%) rename samples/{nested-renderings => }/src/main/res/layout/legacy_view.xml (100%) rename samples/{nested-renderings => }/src/main/res/values/dimens.xml (100%) rename samples/{hello-compose => }/src/main/res/values/strings.xml (91%) rename samples/{hello-compose-rendering => }/src/main/res/values/styles.xml (100%) diff --git a/.buildscript/binary-validation.gradle b/.buildscript/binary-validation.gradle index 898a381a8b..978899ca76 100644 --- a/.buildscript/binary-validation.gradle +++ b/.buildscript/binary-validation.gradle @@ -28,5 +28,5 @@ apiValidation { // Ignore all sample projects, since they're not part of our API. // Only leaf project name is valid configuration, and every project must be individually ignored. // See https://github.com/Kotlin/binary-compatibility-validator/issues/3 - ignoredProjects += project('samples').subprojects.collect { it.name } + ignoredProjects += project('samples').name } diff --git a/build.gradle.kts b/build.gradle.kts index 0275a16906..7c1acfab7c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import io.gitlab.arturbosch.detekt.extensions.DetektExtension import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import org.jlleitschuh.gradle.ktlint.KtlintExtension import org.jlleitschuh.gradle.ktlint.reporter.ReporterType @@ -105,6 +106,12 @@ subprojects { ) ) } + + configure { + config = files("${rootDir}/detekt.yml") + // Treat config file as an override for the default config. + buildUponDefaultConfig = true + } } apply(from = rootProject.file(".buildscript/binary-validation.gradle")) diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ParentComposition.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ParentComposition.kt index 3725d65bad..edec23b4db 100644 --- a/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ParentComposition.kt +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ParentComposition.kt @@ -22,10 +22,10 @@ import com.squareup.workflow.ui.ViewEnvironment import com.squareup.workflow.ui.ViewEnvironmentKey /** - * Holds a [CompositionReference] and that can be passed to [setContent] to create a composition - * that is a child of another composition. Subcompositions get ambients and other compose context - * from their parent, and propagate invalidations, which allows ambients provided around a - * [WorkflowRendering] call to be read by nested Compose-based view factories. + * Holds a [CompositionReference] that can be passed to [setContent] to create a composition that is + * a child of another composition. Subcompositions get ambients and other compose context from their + * parent, and propagate invalidations, which allows ambients provided around a [WorkflowRendering] + * call to be read by nested Compose-based view factories. * * When [WorkflowRendering] is called, it will store an instance of this class in the * [ViewEnvironment]. `ComposeViewFactory` pulls the reference out of the environment and uses it to diff --git a/detekt.yml b/detekt.yml new file mode 100644 index 0000000000..51dc868162 --- /dev/null +++ b/detekt.yml @@ -0,0 +1,20 @@ +# +# Copyright 2020 Square Inc. +# +# 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. +# +naming: + MatchingDeclarationName: + active: false + FunctionNaming: + active: false diff --git a/samples/hello-compose-rendering/build.gradle.kts b/samples/build.gradle.kts similarity index 90% rename from samples/hello-compose-rendering/build.gradle.kts rename to samples/build.gradle.kts index 88103a2c4b..07668fdab9 100644 --- a/samples/hello-compose-rendering/build.gradle.kts +++ b/samples/build.gradle.kts @@ -15,6 +15,7 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile * See the License for the specific language governing permissions and * limitations under the License. */ + plugins { id("com.android.application") kotlin("android") @@ -26,7 +27,7 @@ apply(from = rootProject.file(".buildscript/android-ui-tests.gradle")) android { defaultConfig { - applicationId = "com.squareup.sample.hellocomposerendering" + applicationId = "com.squareup.sample" } } @@ -38,9 +39,12 @@ tasks.withType { dependencies { implementation(project(":core-compose")) implementation(Dependencies.AndroidX.appcompat) - implementation(Dependencies.Compose.foundation) implementation(Dependencies.Compose.layout) implementation(Dependencies.Compose.material) + implementation(Dependencies.Compose.foundation) implementation(Dependencies.Workflow.core) implementation(Dependencies.Workflow.runtime) + implementation(Dependencies.Workflow.UI.coreAndroid) + + debugImplementation(project(":compose-tooling")) } diff --git a/samples/hello-compose-binding/build.gradle.kts b/samples/hello-compose-binding/build.gradle.kts deleted file mode 100644 index 04cb81c10e..0000000000 --- a/samples/hello-compose-binding/build.gradle.kts +++ /dev/null @@ -1,43 +0,0 @@ -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile - -/* - * Copyright 2019 Square Inc. - * - * 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. - */ -plugins { - id("com.android.application") - kotlin("android") -} - -apply(from = rootProject.file(".buildscript/android-sample-app.gradle")) -apply(from = rootProject.file(".buildscript/android-ui-tests.gradle")) - -android { - defaultConfig { - applicationId = "com.squareup.sample.hellocomposebinding" - } -} - -apply(from = rootProject.file(".buildscript/configure-compose.gradle")) -tasks.withType { - kotlinOptions.apiVersion = "1.3" -} - -dependencies { - implementation(project(":core-compose")) - - implementation(Dependencies.Compose.layout) - implementation(Dependencies.Compose.material) - implementation(Dependencies.Compose.foundation) -} diff --git a/samples/hello-compose-binding/src/main/AndroidManifest.xml b/samples/hello-compose-binding/src/main/AndroidManifest.xml deleted file mode 100644 index 557bdb0d0e..0000000000 --- a/samples/hello-compose-binding/src/main/AndroidManifest.xml +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/samples/hello-compose-binding/src/main/res/values/strings.xml b/samples/hello-compose-binding/src/main/res/values/strings.xml deleted file mode 100644 index a2cc91abdc..0000000000 --- a/samples/hello-compose-binding/src/main/res/values/strings.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - Hello Compose Binding - diff --git a/samples/hello-compose-binding/src/main/res/values/styles.xml b/samples/hello-compose-binding/src/main/res/values/styles.xml deleted file mode 100644 index d5b0c9026f..0000000000 --- a/samples/hello-compose-binding/src/main/res/values/styles.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - diff --git a/samples/hello-compose-rendering/src/main/java/com/squareup/sample/hellocomposerendering/HelloRenderingWorkflow.kt b/samples/hello-compose-rendering/src/main/java/com/squareup/sample/hellocomposerendering/HelloRenderingWorkflow.kt deleted file mode 100644 index 7faf8e71ed..0000000000 --- a/samples/hello-compose-rendering/src/main/java/com/squareup/sample/hellocomposerendering/HelloRenderingWorkflow.kt +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2020 Square Inc. - * - * 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.squareup.sample.hellocomposerendering - -import androidx.compose.Composable -import androidx.ui.core.Alignment -import androidx.ui.core.Modifier -import androidx.ui.foundation.Text -import androidx.ui.foundation.clickable -import androidx.ui.layout.wrapContentSize -import androidx.ui.material.MaterialTheme -import androidx.ui.material.ripple.ripple -import androidx.ui.tooling.preview.Preview -import com.squareup.sample.hellocomposerendering.HelloRenderingWorkflow.Toggle -import com.squareup.workflow.Sink -import com.squareup.workflow.ui.ViewEnvironment -import com.squareup.workflow.ui.compose.ComposeWorkflow -import com.squareup.workflow.ui.compose.tooling.preview - -/** - * A [ComposeWorkflow] that is used by [HelloWorkflow] to render the screen. - * - * This workflow has type `Workflow`. - */ -object HelloRenderingWorkflow : ComposeWorkflow() { - - object Toggle - - @Composable override fun render( - props: String, - outputSink: Sink, - viewEnvironment: ViewEnvironment - ) { - MaterialTheme { - Text( - props, - modifier = Modifier - .ripple() - .clickable(onClick = { outputSink.send(Toggle) }) - .wrapContentSize(Alignment.Center) - ) - } - } -} - -@Preview(showBackground = true) -@Composable private fun HelloRenderingWorkflowPreview() { - HelloRenderingWorkflow.preview(props = "hello") -} diff --git a/samples/hello-compose-rendering/src/main/res/values/strings.xml b/samples/hello-compose-rendering/src/main/res/values/strings.xml deleted file mode 100644 index ad1e200b1f..0000000000 --- a/samples/hello-compose-rendering/src/main/res/values/strings.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - Hello Compose Rendering - diff --git a/samples/hello-compose/build.gradle.kts b/samples/hello-compose/build.gradle.kts deleted file mode 100644 index bf0b4fb0dc..0000000000 --- a/samples/hello-compose/build.gradle.kts +++ /dev/null @@ -1,48 +0,0 @@ -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile - -/* - * Copyright 2020 Square Inc. - * - * 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. - */ - -plugins { - id("com.android.application") - kotlin("android") -} - -apply(from = rootProject.file(".buildscript/configure-android-defaults.gradle")) -apply(from = rootProject.file(".buildscript/android-sample-app.gradle")) -apply(from = rootProject.file(".buildscript/android-ui-tests.gradle")) - -android { - defaultConfig { - applicationId = "com.squareup.sample.hellocompose" - minSdkVersion(25) - } -} - -apply(from = rootProject.file(".buildscript/configure-compose.gradle")) -tasks.withType { - kotlinOptions.apiVersion = "1.3" -} - -dependencies { - implementation(project(":core-compose")) - implementation(Dependencies.AndroidX.appcompat) - implementation(Dependencies.Compose.layout) - implementation(Dependencies.Compose.material) - implementation(Dependencies.Compose.foundation) - - debugImplementation(project(":compose-tooling")) -} diff --git a/samples/hello-compose/src/main/AndroidManifest.xml b/samples/hello-compose/src/main/AndroidManifest.xml deleted file mode 100644 index 14ee442651..0000000000 --- a/samples/hello-compose/src/main/AndroidManifest.xml +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/samples/hello-compose/src/main/res/values/styles.xml b/samples/hello-compose/src/main/res/values/styles.xml deleted file mode 100644 index 1c37bd6d5b..0000000000 --- a/samples/hello-compose/src/main/res/values/styles.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - diff --git a/samples/nested-renderings/build.gradle.kts b/samples/nested-renderings/build.gradle.kts deleted file mode 100644 index bcbc695a26..0000000000 --- a/samples/nested-renderings/build.gradle.kts +++ /dev/null @@ -1,44 +0,0 @@ -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile - -/* - * Copyright 2020 Square Inc. - * - * 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. - */ -plugins { - id("com.android.application") - kotlin("android") -} - -apply(from = rootProject.file(".buildscript/android-sample-app.gradle")) -apply(from = rootProject.file(".buildscript/android-ui-tests.gradle")) - -android { - defaultConfig { - applicationId = "com.squareup.sample.nestedrenderings" - } -} - -apply(from = rootProject.file(".buildscript/configure-compose.gradle")) -tasks.withType { - kotlinOptions.apiVersion = "1.3" -} - -dependencies { - implementation(project(":core-compose")) - implementation(Dependencies.AndroidX.appcompat) - implementation(Dependencies.Compose.foundation) - implementation(Dependencies.Compose.layout) - implementation(Dependencies.Compose.material) - implementation(Dependencies.Workflow.UI.coreAndroid) -} diff --git a/samples/nested-renderings/src/main/AndroidManifest.xml b/samples/nested-renderings/src/main/AndroidManifest.xml deleted file mode 100644 index a5385e3d19..0000000000 --- a/samples/nested-renderings/src/main/AndroidManifest.xml +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/samples/nested-renderings/src/main/res/values/strings.xml b/samples/nested-renderings/src/main/res/values/strings.xml deleted file mode 100644 index 2fe25f7e14..0000000000 --- a/samples/nested-renderings/src/main/res/values/strings.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - Nested Renderings - diff --git a/samples/nested-renderings/src/main/res/values/styles.xml b/samples/nested-renderings/src/main/res/values/styles.xml deleted file mode 100644 index 1c37bd6d5b..0000000000 --- a/samples/nested-renderings/src/main/res/values/styles.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - diff --git a/samples/hello-compose-binding/src/androidTest/java/com/squareup/sample/hellocomposebinding/HelloBindingTest.kt b/samples/src/androidTest/java/com/squareup/sample/hellocomposebinding/HelloBindingTest.kt similarity index 100% rename from samples/hello-compose-binding/src/androidTest/java/com/squareup/sample/hellocomposebinding/HelloBindingTest.kt rename to samples/src/androidTest/java/com/squareup/sample/hellocomposebinding/HelloBindingTest.kt diff --git a/samples/src/androidTest/java/com/squareup/sample/hellocomposerendering/HelloComposeRenderingTest.kt b/samples/src/androidTest/java/com/squareup/sample/hellocomposerendering/HelloComposeRenderingTest.kt new file mode 100644 index 0000000000..406f376b3c --- /dev/null +++ b/samples/src/androidTest/java/com/squareup/sample/hellocomposerendering/HelloComposeRenderingTest.kt @@ -0,0 +1,35 @@ +/* + * Copyright 2020 Square Inc. + * + * 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.squareup.sample.hellocomposerendering + +// This sample is broken in dev12, see https://github.com/square/workflow-kotlin-compose/issues/42. +// @RunWith(AndroidJUnit4::class) +// class HelloComposeRenderingTest { +// +// // Launches the activity. +// @Rule @JvmField val composeRule = AndroidComposeTestRule() +// +// @Test fun togglesBetweenStates() { +// findByText("Hello") +// .assertIsDisplayed() +// .doClick() +// findByText("Goodbye") +// .assertIsDisplayed() +// .doClick() +// findByText("Hello") +// .assertIsDisplayed() +// } +// } diff --git a/samples/hello-compose-rendering/src/androidTest/java/com/squareup/sample/hellocomposerendering/HelloComposeRenderingTest.kt b/samples/src/androidTest/java/com/squareup/sample/launcher/SampleLauncherTest.kt similarity index 55% rename from samples/hello-compose-rendering/src/androidTest/java/com/squareup/sample/hellocomposerendering/HelloComposeRenderingTest.kt rename to samples/src/androidTest/java/com/squareup/sample/launcher/SampleLauncherTest.kt index 8b98d717d6..1c6f6be414 100644 --- a/samples/hello-compose-rendering/src/androidTest/java/com/squareup/sample/hellocomposerendering/HelloComposeRenderingTest.kt +++ b/samples/src/androidTest/java/com/squareup/sample/launcher/SampleLauncherTest.kt @@ -13,31 +13,38 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.squareup.sample.hellocomposerendering +package com.squareup.sample.launcher +import androidx.test.espresso.Espresso.pressBack import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry import androidx.ui.test.android.AndroidComposeTestRule import androidx.ui.test.assertIsDisplayed import androidx.ui.test.doClick +import androidx.ui.test.findBySubstring import androidx.ui.test.findByText +import com.squareup.sample.R import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) -class HelloComposeRenderingTest { +class SampleLauncherTest { - // Launches the activity. - @Rule @JvmField val composeRule = AndroidComposeTestRule() + @Rule @JvmField val composeRule = AndroidComposeTestRule() - @Test fun togglesBetweenStates() { - findByText("Hello") - .assertIsDisplayed() - .doClick() - findByText("Goodbye") - .assertIsDisplayed() - .doClick() - findByText("Hello") - .assertIsDisplayed() + @Test fun allSamplesLaunch() { + val appName = + InstrumentationRegistry.getInstrumentation().targetContext.getString(R.string.app_name) + findByText(appName).assertIsDisplayed() + + samples.forEach { sample -> + try { + findBySubstring(sample.description).doClick() + pressBack() + } catch (e: Throwable) { + throw AssertionError("Failed to launch sample ${sample.name}", e) + } + } } } diff --git a/samples/nested-renderings/src/androidTest/java/com/squareup/sample/nestedrenderings/NestedRenderingsTest.kt b/samples/src/androidTest/java/com/squareup/sample/nestedrenderings/NestedRenderingsTest.kt similarity index 100% rename from samples/nested-renderings/src/androidTest/java/com/squareup/sample/nestedrenderings/NestedRenderingsTest.kt rename to samples/src/androidTest/java/com/squareup/sample/nestedrenderings/NestedRenderingsTest.kt diff --git a/samples/hello-compose-rendering/src/main/AndroidManifest.xml b/samples/src/main/AndroidManifest.xml similarity index 74% rename from samples/hello-compose-rendering/src/main/AndroidManifest.xml rename to samples/src/main/AndroidManifest.xml index dad2243e3b..bf0ed76c90 100644 --- a/samples/hello-compose-rendering/src/main/AndroidManifest.xml +++ b/samples/src/main/AndroidManifest.xml @@ -15,7 +15,7 @@ --> + package="com.squareup.sample"> - - + - + + + + + diff --git a/samples/hello-compose/src/main/java/com/squareup/sample/hellocompose/App.kt b/samples/src/main/java/com/squareup/sample/hellocompose/App.kt similarity index 100% rename from samples/hello-compose/src/main/java/com/squareup/sample/hellocompose/App.kt rename to samples/src/main/java/com/squareup/sample/hellocompose/App.kt diff --git a/samples/hello-compose/src/main/java/com/squareup/sample/hellocompose/HelloBinding.kt b/samples/src/main/java/com/squareup/sample/hellocompose/HelloBinding.kt similarity index 100% rename from samples/hello-compose/src/main/java/com/squareup/sample/hellocompose/HelloBinding.kt rename to samples/src/main/java/com/squareup/sample/hellocompose/HelloBinding.kt diff --git a/samples/hello-compose/src/main/java/com/squareup/sample/hellocompose/HelloComposeActivity.kt b/samples/src/main/java/com/squareup/sample/hellocompose/HelloComposeActivity.kt similarity index 100% rename from samples/hello-compose/src/main/java/com/squareup/sample/hellocompose/HelloComposeActivity.kt rename to samples/src/main/java/com/squareup/sample/hellocompose/HelloComposeActivity.kt diff --git a/samples/hello-compose/src/main/java/com/squareup/sample/hellocompose/HelloWorkflow.kt b/samples/src/main/java/com/squareup/sample/hellocompose/HelloWorkflow.kt similarity index 100% rename from samples/hello-compose/src/main/java/com/squareup/sample/hellocompose/HelloWorkflow.kt rename to samples/src/main/java/com/squareup/sample/hellocompose/HelloWorkflow.kt diff --git a/samples/hello-compose-binding/src/main/java/com/squareup/sample/hellocomposebinding/HelloBinding.kt b/samples/src/main/java/com/squareup/sample/hellocomposebinding/HelloBinding.kt similarity index 100% rename from samples/hello-compose-binding/src/main/java/com/squareup/sample/hellocomposebinding/HelloBinding.kt rename to samples/src/main/java/com/squareup/sample/hellocomposebinding/HelloBinding.kt diff --git a/samples/hello-compose-binding/src/main/java/com/squareup/sample/hellocomposebinding/HelloBindingActivity.kt b/samples/src/main/java/com/squareup/sample/hellocomposebinding/HelloBindingActivity.kt similarity index 100% rename from samples/hello-compose-binding/src/main/java/com/squareup/sample/hellocomposebinding/HelloBindingActivity.kt rename to samples/src/main/java/com/squareup/sample/hellocomposebinding/HelloBindingActivity.kt diff --git a/samples/hello-compose-binding/src/main/java/com/squareup/sample/hellocomposebinding/HelloWorkflow.kt b/samples/src/main/java/com/squareup/sample/hellocomposebinding/HelloWorkflow.kt similarity index 100% rename from samples/hello-compose-binding/src/main/java/com/squareup/sample/hellocomposebinding/HelloWorkflow.kt rename to samples/src/main/java/com/squareup/sample/hellocomposebinding/HelloWorkflow.kt diff --git a/samples/hello-compose-rendering/src/main/java/com/squareup/sample/hellocomposerendering/HelloComposeRenderingActivity.kt b/samples/src/main/java/com/squareup/sample/hellocomposerendering/HelloComposeRenderingActivity.kt similarity index 100% rename from samples/hello-compose-rendering/src/main/java/com/squareup/sample/hellocomposerendering/HelloComposeRenderingActivity.kt rename to samples/src/main/java/com/squareup/sample/hellocomposerendering/HelloComposeRenderingActivity.kt diff --git a/samples/src/main/java/com/squareup/sample/hellocomposerendering/HelloRenderingWorkflow.kt b/samples/src/main/java/com/squareup/sample/hellocomposerendering/HelloRenderingWorkflow.kt new file mode 100644 index 0000000000..2b48670933 --- /dev/null +++ b/samples/src/main/java/com/squareup/sample/hellocomposerendering/HelloRenderingWorkflow.kt @@ -0,0 +1,49 @@ +/* + * Copyright 2020 Square Inc. + * + * 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.squareup.sample.hellocomposerendering + +// There is a bug in Compose dev12 that crashes the compiler when processing this code. +// See https://github.com/square/workflow-kotlin-compose/issues/42. +// /** +// * A [ComposeWorkflow] that is used by [HelloWorkflow] to render the screen. +// * +// * This workflow has type `Workflow`. +// */ +// object HelloRenderingWorkflow : ComposeWorkflow() { +// +// object Toggle +// +// @Composable override fun render( +// props: String, +// outputSink: Sink, +// viewEnvironment: ViewEnvironment +// ) { +// MaterialTheme { +// Text( +// props, +// modifier = Modifier +// .ripple() +// .clickable(onClick = { outputSink.send(Toggle) }) +// .wrapContentSize(Alignment.Center) +// ) +// } +// } +// } +// +// @Preview(showBackground = true) +// @Composable private fun HelloRenderingWorkflowPreview() { +// HelloRenderingWorkflow.preview(props = "hello") +// } diff --git a/samples/hello-compose-rendering/src/main/java/com/squareup/sample/hellocomposerendering/HelloWorkflow.kt b/samples/src/main/java/com/squareup/sample/hellocomposerendering/HelloWorkflow.kt similarity index 90% rename from samples/hello-compose-rendering/src/main/java/com/squareup/sample/hellocomposerendering/HelloWorkflow.kt rename to samples/src/main/java/com/squareup/sample/hellocomposerendering/HelloWorkflow.kt index 5654a75a4f..3374caa3b8 100644 --- a/samples/hello-compose-rendering/src/main/java/com/squareup/sample/hellocomposerendering/HelloWorkflow.kt +++ b/samples/src/main/java/com/squareup/sample/hellocomposerendering/HelloWorkflow.kt @@ -22,8 +22,8 @@ import com.squareup.workflow.RenderContext import com.squareup.workflow.Snapshot import com.squareup.workflow.StatefulWorkflow import com.squareup.workflow.action -import com.squareup.workflow.ui.compose.ComposeRendering import com.squareup.workflow.parse +import com.squareup.workflow.ui.compose.ComposeRendering object HelloWorkflow : StatefulWorkflow() { enum class State { @@ -50,7 +50,9 @@ object HelloWorkflow : StatefulWorkflow( props: Unit, state: State, context: RenderContext - ): ComposeRendering = context.renderChild(HelloRenderingWorkflow, state.name) { helloAction } + ): ComposeRendering = ComposeRendering.NoopRendering + // See https://github.com/square/workflow-kotlin-compose/issues/42. + //context.renderChild(HelloRenderingWorkflow, state.name) { helloAction } override fun snapshotState(state: State): Snapshot = Snapshot.of(if (state == Hello) 1 else 0) } diff --git a/samples/src/main/java/com/squareup/sample/launcher/SampleLauncherActivity.kt b/samples/src/main/java/com/squareup/sample/launcher/SampleLauncherActivity.kt new file mode 100644 index 0000000000..b4839155cf --- /dev/null +++ b/samples/src/main/java/com/squareup/sample/launcher/SampleLauncherActivity.kt @@ -0,0 +1,29 @@ +/* + * Copyright 2020 Square Inc. + * + * 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.squareup.sample.launcher + +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import androidx.ui.core.setContent + +class SampleLauncherActivity : AppCompatActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContent { + SampleLauncherApp() + } + } +} diff --git a/samples/src/main/java/com/squareup/sample/launcher/SampleLauncherApp.kt b/samples/src/main/java/com/squareup/sample/launcher/SampleLauncherApp.kt new file mode 100644 index 0000000000..87a882f51d --- /dev/null +++ b/samples/src/main/java/com/squareup/sample/launcher/SampleLauncherApp.kt @@ -0,0 +1,62 @@ +/* + * Copyright 2020 Square Inc. + * + * 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.squareup.sample.launcher + +import android.content.Context +import android.content.Intent +import androidx.compose.Composable +import androidx.ui.core.ContextAmbient +import androidx.ui.foundation.AdapterList +import androidx.ui.foundation.Text +import androidx.ui.material.ListItem +import androidx.ui.material.MaterialTheme +import androidx.ui.material.Scaffold +import androidx.ui.material.TopAppBar +import androidx.ui.material.darkColorPalette +import androidx.ui.res.stringResource +import androidx.ui.tooling.preview.Preview +import com.squareup.sample.R + +@Composable fun SampleLauncherApp() { + val context = ContextAmbient.current + MaterialTheme(colors = darkColorPalette()) { + Scaffold( + topAppBar = { + TopAppBar(title = { + Text(stringResource(R.string.app_name)) + }) + } + ) { + AdapterList(samples) { sample -> + ListItem( + text = sample.name, + secondaryText = sample.description, + singleLineSecondaryText = false, + onClick = { context.launchSample(sample) } + ) + } + } + } +} + +@Preview @Composable private fun SampleLauncherAppPreview() { + SampleLauncherApp() +} + +private fun Context.launchSample(sample: Sample) { + val intent = Intent(this, sample.activityClass.java) + startActivity(intent) +} diff --git a/samples/src/main/java/com/squareup/sample/launcher/Samples.kt b/samples/src/main/java/com/squareup/sample/launcher/Samples.kt new file mode 100644 index 0000000000..281d81aa1d --- /dev/null +++ b/samples/src/main/java/com/squareup/sample/launcher/Samples.kt @@ -0,0 +1,48 @@ +/* + * Copyright 2020 Square Inc. + * + * 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.squareup.sample.launcher + +import androidx.activity.ComponentActivity +import com.squareup.sample.hellocompose.HelloComposeActivity +import com.squareup.sample.hellocomposebinding.HelloBindingActivity +import com.squareup.sample.hellocomposerendering.HelloComposeRenderingActivity +import com.squareup.sample.nestedrenderings.NestedRenderingsActivity +import kotlin.reflect.KClass + +val samples = listOf( + Sample( + "Hello Compose Binding", HelloBindingActivity::class, + "Creates a ViewFactory using bindCompose." + ), + Sample( + "Hello Compose Rendering", HelloComposeRenderingActivity::class, + "Uses ComposeWorkflow to create a workflow that draws itself." + ), + Sample( + "Hello Compose", HelloComposeActivity::class, + "A pure Compose app that launches its root Workflow from inside Compose." + ), + Sample( + "Nested Renderings", NestedRenderingsActivity::class, + "Demonstrates recursive view factories using both Compose and legacy view factories." + ) +) + +data class Sample( + val name: String, + val activityClass: KClass, + val description: String +) diff --git a/samples/nested-renderings/src/main/java/com/squareup/sample/nestedrenderings/LegacyRunner.kt b/samples/src/main/java/com/squareup/sample/nestedrenderings/LegacyRunner.kt similarity index 96% rename from samples/nested-renderings/src/main/java/com/squareup/sample/nestedrenderings/LegacyRunner.kt rename to samples/src/main/java/com/squareup/sample/nestedrenderings/LegacyRunner.kt index c7c680f4b4..d06a316f28 100644 --- a/samples/nested-renderings/src/main/java/com/squareup/sample/nestedrenderings/LegacyRunner.kt +++ b/samples/src/main/java/com/squareup/sample/nestedrenderings/LegacyRunner.kt @@ -19,8 +19,8 @@ import androidx.compose.Composable import androidx.ui.core.Modifier import androidx.ui.layout.fillMaxSize import androidx.ui.tooling.preview.Preview +import com.squareup.sample.databinding.LegacyViewBinding import com.squareup.sample.nestedrenderings.RecursiveWorkflow.LegacyRendering -import com.squareup.sample.nestedrenderings.databinding.LegacyViewBinding import com.squareup.workflow.ui.LayoutRunner import com.squareup.workflow.ui.LayoutRunner.Companion.bind import com.squareup.workflow.ui.ViewEnvironment diff --git a/samples/nested-renderings/src/main/java/com/squareup/sample/nestedrenderings/NestedRenderingsActivity.kt b/samples/src/main/java/com/squareup/sample/nestedrenderings/NestedRenderingsActivity.kt similarity index 100% rename from samples/nested-renderings/src/main/java/com/squareup/sample/nestedrenderings/NestedRenderingsActivity.kt rename to samples/src/main/java/com/squareup/sample/nestedrenderings/NestedRenderingsActivity.kt diff --git a/samples/nested-renderings/src/main/java/com/squareup/sample/nestedrenderings/RecursiveViewFactory.kt b/samples/src/main/java/com/squareup/sample/nestedrenderings/RecursiveViewFactory.kt similarity index 99% rename from samples/nested-renderings/src/main/java/com/squareup/sample/nestedrenderings/RecursiveViewFactory.kt rename to samples/src/main/java/com/squareup/sample/nestedrenderings/RecursiveViewFactory.kt index 5bbbd7d6d5..13a67327f7 100644 --- a/samples/nested-renderings/src/main/java/com/squareup/sample/nestedrenderings/RecursiveViewFactory.kt +++ b/samples/src/main/java/com/squareup/sample/nestedrenderings/RecursiveViewFactory.kt @@ -37,6 +37,7 @@ import androidx.ui.material.Button import androidx.ui.material.Card import androidx.ui.res.dimensionResource import androidx.ui.tooling.preview.Preview +import com.squareup.sample.R import com.squareup.sample.nestedrenderings.RecursiveWorkflow.Rendering import com.squareup.workflow.ui.ViewEnvironment import com.squareup.workflow.ui.compose.composedViewFactory diff --git a/samples/nested-renderings/src/main/java/com/squareup/sample/nestedrenderings/RecursiveWorkflow.kt b/samples/src/main/java/com/squareup/sample/nestedrenderings/RecursiveWorkflow.kt similarity index 100% rename from samples/nested-renderings/src/main/java/com/squareup/sample/nestedrenderings/RecursiveWorkflow.kt rename to samples/src/main/java/com/squareup/sample/nestedrenderings/RecursiveWorkflow.kt diff --git a/samples/nested-renderings/src/main/res/layout/legacy_view.xml b/samples/src/main/res/layout/legacy_view.xml similarity index 100% rename from samples/nested-renderings/src/main/res/layout/legacy_view.xml rename to samples/src/main/res/layout/legacy_view.xml diff --git a/samples/nested-renderings/src/main/res/values/dimens.xml b/samples/src/main/res/values/dimens.xml similarity index 100% rename from samples/nested-renderings/src/main/res/values/dimens.xml rename to samples/src/main/res/values/dimens.xml diff --git a/samples/hello-compose/src/main/res/values/strings.xml b/samples/src/main/res/values/strings.xml similarity index 91% rename from samples/hello-compose/src/main/res/values/strings.xml rename to samples/src/main/res/values/strings.xml index e83d220934..187570afd0 100644 --- a/samples/hello-compose/src/main/res/values/strings.xml +++ b/samples/src/main/res/values/strings.xml @@ -14,5 +14,5 @@ ~ limitations under the License. --> - Hello Compose + Workflow Compose Samples diff --git a/samples/hello-compose-rendering/src/main/res/values/styles.xml b/samples/src/main/res/values/styles.xml similarity index 100% rename from samples/hello-compose-rendering/src/main/res/values/styles.xml rename to samples/src/main/res/values/styles.xml diff --git a/settings.gradle.kts b/settings.gradle.kts index 7b1a1e0a84..76e8e1e28a 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -18,10 +18,5 @@ rootProject.name = "workflow-compose" include( ":compose-tooling", ":core-compose", - ":samples:hello-compose", - ":samples:hello-compose-binding", - // This module is crashing the compiler with dev12. Just disable it for now. - // See https://github.com/square/workflow-kotlin-compose/issues/42. - // ":samples:hello-compose-rendering", - ":samples:nested-renderings" + ":samples" ) From 8bdfc0ad28a7a2900cbd416f179f97f1db7e0bd6 Mon Sep 17 00:00:00 2001 From: Zach Klippenstein Date: Thu, 21 May 2020 13:52:30 -0700 Subject: [PATCH 34/67] Make sample launcher animate sample activities from their list items. --- .../sample/launcher/SampleLauncherApp.kt | 64 +++++++++++++++---- 1 file changed, 52 insertions(+), 12 deletions(-) diff --git a/samples/src/main/java/com/squareup/sample/launcher/SampleLauncherApp.kt b/samples/src/main/java/com/squareup/sample/launcher/SampleLauncherApp.kt index 87a882f51d..fd6ba491f7 100644 --- a/samples/src/main/java/com/squareup/sample/launcher/SampleLauncherApp.kt +++ b/samples/src/main/java/com/squareup/sample/launcher/SampleLauncherApp.kt @@ -15,10 +15,19 @@ */ package com.squareup.sample.launcher -import android.content.Context import android.content.Intent +import android.os.Bundle +import android.view.View import androidx.compose.Composable -import androidx.ui.core.ContextAmbient +import androidx.compose.remember +import androidx.core.app.ActivityOptionsCompat.makeScaleUpAnimation +import androidx.core.content.ContextCompat.startActivity +import androidx.ui.core.AndroidOwner +import androidx.ui.core.Modifier +import androidx.ui.core.OwnerAmbient +import androidx.ui.core.Ref +import androidx.ui.core.globalBounds +import androidx.ui.core.onPositioned import androidx.ui.foundation.AdapterList import androidx.ui.foundation.Text import androidx.ui.material.ListItem @@ -28,10 +37,12 @@ import androidx.ui.material.TopAppBar import androidx.ui.material.darkColorPalette import androidx.ui.res.stringResource import androidx.ui.tooling.preview.Preview +import androidx.ui.unit.PxBounds +import androidx.ui.unit.height +import androidx.ui.unit.width import com.squareup.sample.R @Composable fun SampleLauncherApp() { - val context = ContextAmbient.current MaterialTheme(colors = darkColorPalette()) { Scaffold( topAppBar = { @@ -41,12 +52,7 @@ import com.squareup.sample.R } ) { AdapterList(samples) { sample -> - ListItem( - text = sample.name, - secondaryText = sample.description, - singleLineSecondaryText = false, - onClick = { context.launchSample(sample) } - ) + SampleItem(sample) } } } @@ -56,7 +62,41 @@ import com.squareup.sample.R SampleLauncherApp() } -private fun Context.launchSample(sample: Sample) { - val intent = Intent(this, sample.activityClass.java) - startActivity(intent) +@Composable private fun SampleItem(sample: Sample) { + // See https://issuetracker.google.com/issues/156875705. + @Suppress("DEPRECATION") + val rootView = (OwnerAmbient.current as AndroidOwner).view + + /** + * [androidx.ui.core.LayoutCoordinates.globalBounds] corresponds to the coordinates in the root + * Android view hosting the composition. + */ + val globalBounds = remember { Ref() } + + ListItem( + text = sample.name, + secondaryText = sample.description, + singleLineSecondaryText = false, + modifier = Modifier.onPositioned { globalBounds.value = it.globalBounds }, + onClick = { launchSample(sample, rootView, globalBounds.value) } + ) +} + +private fun launchSample( + sample: Sample, + rootView: View, + sourceBounds: PxBounds? +) { + val context = rootView.context + val intent = Intent(context, sample.activityClass.java) + val options: Bundle? = sourceBounds?.let { + makeScaleUpAnimation( + rootView, + it.left.value.toInt(), + it.top.value.toInt(), + it.width.value.toInt(), + it.height.value.toInt() + ).toBundle() + } + startActivity(context, intent, options) } From 07b9dc64424da84e5fb34d1417b0a5de7266f582 Mon Sep 17 00:00:00 2001 From: Zach Klippenstein Date: Thu, 21 May 2020 15:18:54 -0700 Subject: [PATCH 35/67] Show previews of each sample in the list. --- .../hellocomposebinding/HelloBinding.kt | 2 +- .../HelloRenderingWorkflow.kt | 2 +- .../sample/launcher/SampleLauncherApp.kt | 62 ++++++++++++++++++- .../com/squareup/sample/launcher/Samples.kt | 25 +++++--- .../nestedrenderings/RecursiveViewFactory.kt | 2 +- 5 files changed, 78 insertions(+), 15 deletions(-) diff --git a/samples/src/main/java/com/squareup/sample/hellocomposebinding/HelloBinding.kt b/samples/src/main/java/com/squareup/sample/hellocomposebinding/HelloBinding.kt index a91020fad4..3ae43cf062 100644 --- a/samples/src/main/java/com/squareup/sample/hellocomposebinding/HelloBinding.kt +++ b/samples/src/main/java/com/squareup/sample/hellocomposebinding/HelloBinding.kt @@ -39,6 +39,6 @@ val HelloBinding = composedViewFactory { rendering, _ -> } @Preview(heightDp = 150, showBackground = true) -@Composable private fun DrawHelloRenderingPreview() { +@Composable fun DrawHelloRenderingPreview() { HelloBinding.preview(Rendering("Hello!", onClick = {})) } diff --git a/samples/src/main/java/com/squareup/sample/hellocomposerendering/HelloRenderingWorkflow.kt b/samples/src/main/java/com/squareup/sample/hellocomposerendering/HelloRenderingWorkflow.kt index 2b48670933..ac662cda07 100644 --- a/samples/src/main/java/com/squareup/sample/hellocomposerendering/HelloRenderingWorkflow.kt +++ b/samples/src/main/java/com/squareup/sample/hellocomposerendering/HelloRenderingWorkflow.kt @@ -44,6 +44,6 @@ package com.squareup.sample.hellocomposerendering // } // // @Preview(showBackground = true) -// @Composable private fun HelloRenderingWorkflowPreview() { +// @Composable fun HelloRenderingWorkflowPreview() { // HelloRenderingWorkflow.preview(props = "hello") // } diff --git a/samples/src/main/java/com/squareup/sample/launcher/SampleLauncherApp.kt b/samples/src/main/java/com/squareup/sample/launcher/SampleLauncherApp.kt index fd6ba491f7..6ad122ec39 100644 --- a/samples/src/main/java/com/squareup/sample/launcher/SampleLauncherApp.kt +++ b/samples/src/main/java/com/squareup/sample/launcher/SampleLauncherApp.kt @@ -23,21 +23,33 @@ import androidx.compose.remember import androidx.core.app.ActivityOptionsCompat.makeScaleUpAnimation import androidx.core.content.ContextCompat.startActivity import androidx.ui.core.AndroidOwner +import androidx.ui.core.ConfigurationAmbient +import androidx.ui.core.LayoutCoordinates import androidx.ui.core.Modifier import androidx.ui.core.OwnerAmbient +import androidx.ui.core.PointerEventPass.PreDown import androidx.ui.core.Ref +import androidx.ui.core.drawLayer +import androidx.ui.core.gesture.rawPressStartGestureFilter import androidx.ui.core.globalBounds import androidx.ui.core.onPositioned import androidx.ui.foundation.AdapterList +import androidx.ui.foundation.Box import androidx.ui.foundation.Text +import androidx.ui.layout.aspectRatio +import androidx.ui.layout.height +import androidx.ui.layout.width import androidx.ui.material.ListItem import androidx.ui.material.MaterialTheme import androidx.ui.material.Scaffold +import androidx.ui.material.Surface import androidx.ui.material.TopAppBar import androidx.ui.material.darkColorPalette +import androidx.ui.material.lightColorPalette import androidx.ui.res.stringResource import androidx.ui.tooling.preview.Preview import androidx.ui.unit.PxBounds +import androidx.ui.unit.dp import androidx.ui.unit.height import androidx.ui.unit.width import com.squareup.sample.R @@ -74,14 +86,58 @@ import com.squareup.sample.R val globalBounds = remember { Ref() } ListItem( - text = sample.name, - secondaryText = sample.description, + text = { Text(sample.name) }, + secondaryText = { Text(sample.description) }, singleLineSecondaryText = false, - modifier = Modifier.onPositioned { globalBounds.value = it.globalBounds }, + // Animate the activities as scaling up from where the preview is drawn. + icon = { SamplePreview(sample) { globalBounds.value = it.globalBounds } }, onClick = { launchSample(sample, rootView, globalBounds.value) } ) } +@Composable private fun SamplePreview( + sample: Sample, + onPreviewCoordinates: (LayoutCoordinates) -> Unit +) { + val configuration = ConfigurationAmbient.current + val screenRatio = configuration.screenWidthDp.toFloat() / configuration.screenHeightDp.toFloat() + // 88dp is taken from ListItem implementation. This doesn't seem to be coming in via any + // constraints as of dev11. + val previewHeight = 88.dp - 16.dp + val scale = previewHeight / configuration.screenHeightDp.dp + + // Force the previews to the scaled size, with the aspect ratio of the device. + // This is needed because the inner Box measures the previews at maximum size, so we have to clamp + // the measurements here otherwise the rest of the UI will think the previews are full-size even + // though they're graphically scaled down. + Box( + modifier = Modifier + .height(previewHeight) + .aspectRatio(screenRatio) + .onPositioned(onPreviewCoordinates) + ) { + // Preview the samples with a light theme, since that's what most of them use. + MaterialTheme(lightColorPalette()) { + Surface { + Box( + modifier = Modifier + // Disable touch input, since this preview isn't meant to be interactive. + .rawPressStartGestureFilter( + enabled = true, executionPass = PreDown, onPressStart = {} + ) + // Measure/layout the child at full screen size, and then just scale the pixels + // down. This way all the text and other density-dependent things get scaled + // correctly too. + .height(configuration.screenHeightDp.dp) + .width(configuration.screenWidthDp.dp) + .drawLayer(scaleX = scale, scaleY = scale), + children = sample.preview + ) + } + } + } +} + private fun launchSample( sample: Sample, rootView: View, diff --git a/samples/src/main/java/com/squareup/sample/launcher/Samples.kt b/samples/src/main/java/com/squareup/sample/launcher/Samples.kt index 281d81aa1d..678ce90966 100644 --- a/samples/src/main/java/com/squareup/sample/launcher/Samples.kt +++ b/samples/src/main/java/com/squareup/sample/launcher/Samples.kt @@ -13,36 +13,43 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +@file:Suppress("RemoveEmptyParenthesesFromAnnotationEntry") + package com.squareup.sample.launcher import androidx.activity.ComponentActivity +import androidx.compose.Composable +import com.squareup.sample.hellocompose.App import com.squareup.sample.hellocompose.HelloComposeActivity +import com.squareup.sample.hellocomposebinding.DrawHelloRenderingPreview import com.squareup.sample.hellocomposebinding.HelloBindingActivity -import com.squareup.sample.hellocomposerendering.HelloComposeRenderingActivity import com.squareup.sample.nestedrenderings.NestedRenderingsActivity +import com.squareup.sample.nestedrenderings.RecursiveViewFactoryPreview import kotlin.reflect.KClass val samples = listOf( Sample( "Hello Compose Binding", HelloBindingActivity::class, "Creates a ViewFactory using bindCompose." - ), - Sample( - "Hello Compose Rendering", HelloComposeRenderingActivity::class, - "Uses ComposeWorkflow to create a workflow that draws itself." - ), + ) { DrawHelloRenderingPreview() }, + // Broken in dev12, see https://github.com/square/workflow-kotlin-compose/issues/42. + // Sample( + // "Hello Compose Rendering", HelloComposeRenderingActivity::class, + // "Uses ComposeWorkflow to create a workflow that draws itself." + // ) { HelloRenderingWorkflowPreview() }, Sample( "Hello Compose", HelloComposeActivity::class, "A pure Compose app that launches its root Workflow from inside Compose." - ), + ) { App() }, Sample( "Nested Renderings", NestedRenderingsActivity::class, "Demonstrates recursive view factories using both Compose and legacy view factories." - ) + ) { RecursiveViewFactoryPreview() } ) data class Sample( val name: String, val activityClass: KClass, - val description: String + val description: String, + val preview: @Composable() () -> Unit ) diff --git a/samples/src/main/java/com/squareup/sample/nestedrenderings/RecursiveViewFactory.kt b/samples/src/main/java/com/squareup/sample/nestedrenderings/RecursiveViewFactory.kt index 13a67327f7..fb169129be 100644 --- a/samples/src/main/java/com/squareup/sample/nestedrenderings/RecursiveViewFactory.kt +++ b/samples/src/main/java/com/squareup/sample/nestedrenderings/RecursiveViewFactory.kt @@ -82,7 +82,7 @@ val RecursiveViewFactory = composedViewFactory { rendering, viewEnvir } @Preview -@Composable private fun RecursiveViewFactoryPreview() { +@Composable fun RecursiveViewFactoryPreview() { Providers(BackgroundColorAmbient provides Color.Green) { RecursiveViewFactory.preview( Rendering( From fff12e32b2ddad9ea5b43eb40e70bc54dfd0d325 Mon Sep 17 00:00:00 2001 From: Zach Klippenstein Date: Mon, 8 Jun 2020 12:35:48 -0700 Subject: [PATCH 36/67] Rename references to master branch to trunk. --- .github/workflows/kotlin.yml | 2 +- RELEASING.md | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/kotlin.yml b/.github/workflows/kotlin.yml index 73804b4877..2672ce5484 100644 --- a/.github/workflows/kotlin.yml +++ b/.github/workflows/kotlin.yml @@ -2,7 +2,7 @@ name: Kotlin CI on: push: - branches: [master] + branches: [trunk] pull_request: env: diff --git a/RELEASING.md b/RELEASING.md index 6c0ff1b0e8..ce38c8d83a 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -6,7 +6,7 @@ Production Releases 1. Merge an update of [the change log](CHANGELOG.md) with the changes since the last release. -1. Make sure you're on the `master` branch (or fix branch, e.g. `v0.1-fixes`). +1. Make sure you're on the `trunk` branch (or fix branch, e.g. `v0.1-fixes`). 1. Confirm that the kotlin build is green before committing any changes ```bash @@ -39,7 +39,7 @@ Production Releases 1. Push your commits and tag: ``` - git push origin master + git push origin trunk # or git push origin fix-branch git push origin v0.1.0 ``` @@ -54,13 +54,13 @@ Production Releases 1. If this is a pre-release version, check the pre-release box. 1. Hit "Publish release". -1. If this was a fix release, merge changes to the master branch: +1. If this was a fix release, merge changes to the trunk branch: ```bash - git checkout master + git checkout trunk git pull git merge --no-ff v0.1-fixes - # Resolve conflicts. Accept master's versions of gradle.properties and podspecs. - git push origin master + # Resolve conflicts. Accept trunk's versions of gradle.properties and podspecs. + git push origin trunk ``` 1. Publish the website. See below. From f46c9e5a733e73d57329a2d0cc85a9bd962c16bd Mon Sep 17 00:00:00 2001 From: Zach Klippenstein Date: Wed, 10 Jun 2020 14:53:00 -0700 Subject: [PATCH 37/67] Upgrade AGP to 4.2-alpha01 and Gradle to 6.5. --- .buildscript/configure-android-defaults.gradle | 5 +---- .github/workflows/kotlin.yml | 3 ++- .idea/misc.xml | 2 +- buildSrc/src/main/java/Dependencies.kt | 2 +- gradle/wrapper/gradle-wrapper.properties | 2 +- 5 files changed, 6 insertions(+), 8 deletions(-) diff --git a/.buildscript/configure-android-defaults.gradle b/.buildscript/configure-android-defaults.gradle index 84ffc87f67..c92e99d825 100644 --- a/.buildscript/configure-android-defaults.gradle +++ b/.buildscript/configure-android-defaults.gradle @@ -18,9 +18,6 @@ android { // See https://github.com/Kotlin/kotlinx.coroutines/issues/1064#issuecomment-479412940 packagingOptions { - exclude 'META-INF/atomicfu.kotlin_module' - exclude 'META-INF/common.kotlin_module' - exclude 'META-INF/android_debug.kotlin_module' - exclude 'META-INF/android_release.kotlin_module' + exclude 'META-INF/*.kotlin_module' } } diff --git a/.github/workflows/kotlin.yml b/.github/workflows/kotlin.yml index 2672ce5484..c46c383380 100644 --- a/.github/workflows/kotlin.yml +++ b/.github/workflows/kotlin.yml @@ -100,7 +100,8 @@ jobs: fail-fast: false matrix: api-level: - - 21 + # See https://github.com/square/workflow-kotlin-compose/issues/54 + # - 21 - 24 - 29 steps: diff --git a/.idea/misc.xml b/.idea/misc.xml index 9053c7ab2b..24bbcf34aa 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -13,7 +13,7 @@ - + diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt index 42b941cd9d..6ac60fd36d 100644 --- a/buildSrc/src/main/java/Dependencies.kt +++ b/buildSrc/src/main/java/Dependencies.kt @@ -27,7 +27,7 @@ object Versions { @Suppress("unused") object Dependencies { - const val android_gradle_plugin = "com.android.tools.build:gradle:4.1.0-alpha08" + const val android_gradle_plugin = "com.android.tools.build:gradle:4.2.0-alpha01" object AndroidX { const val appcompat = "androidx.appcompat:appcompat:1.1.0" diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 21e622da69..186b71557c 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.4.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From 309b1f4b820a58c93c8261af051e50574fc739cf Mon Sep 17 00:00:00 2001 From: Zach Klippenstein Date: Fri, 17 Jul 2020 07:59:11 -0700 Subject: [PATCH 38/67] Rename references to trunk to main. --- .github/workflows/kotlin.yml | 2 +- RELEASING.md | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/kotlin.yml b/.github/workflows/kotlin.yml index c46c383380..a92f8b2341 100644 --- a/.github/workflows/kotlin.yml +++ b/.github/workflows/kotlin.yml @@ -2,7 +2,7 @@ name: Kotlin CI on: push: - branches: [trunk] + branches: [main] pull_request: env: diff --git a/RELEASING.md b/RELEASING.md index ce38c8d83a..f157ff163a 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -6,7 +6,7 @@ Production Releases 1. Merge an update of [the change log](CHANGELOG.md) with the changes since the last release. -1. Make sure you're on the `trunk` branch (or fix branch, e.g. `v0.1-fixes`). +1. Make sure you're on the `main` branch (or fix branch, e.g. `v0.1-fixes`). 1. Confirm that the kotlin build is green before committing any changes ```bash @@ -39,7 +39,7 @@ Production Releases 1. Push your commits and tag: ``` - git push origin trunk + git push origin main # or git push origin fix-branch git push origin v0.1.0 ``` @@ -54,13 +54,13 @@ Production Releases 1. If this is a pre-release version, check the pre-release box. 1. Hit "Publish release". -1. If this was a fix release, merge changes to the trunk branch: +1. If this was a fix release, merge changes to the main branch: ```bash - git checkout trunk + git checkout main git pull git merge --no-ff v0.1-fixes - # Resolve conflicts. Accept trunk's versions of gradle.properties and podspecs. - git push origin trunk + # Resolve conflicts. Accept main's versions of gradle.properties and podspecs. + git push origin main ``` 1. Publish the website. See below. From 86f4b5473f2839cea418189a2c24b9efcd95a84e Mon Sep 17 00:00:00 2001 From: Wardell Bagby <7834478+wardellbagby@users.noreply.github.com> Date: Fri, 24 Jul 2020 18:35:54 -0700 Subject: [PATCH 39/67] Update Compose to dev15 - Updates the Compose version to dev15. - Updates the coordinates for various Compose libs that have changed. - Updates Kotlin to 1.4-M3, since that's required by Compose now. - Removes the code forcing the Kotlin compiler to use API version 1.3. - Updates the README to reflect these changes. - Removes ComposeSupport.kt and its associated tests, replacing its usages with the setContent method that now exists in the Compose API. - Updates various Compose API usages that were removed or replaced. - Adds new UI tests for testing rendering legacy Android views when in Compose. --- .buildscript/configure-compose.gradle | 13 +-- README.md | 10 +- build.gradle.kts | 16 ++- buildSrc/src/main/java/Dependencies.kt | 15 +-- compose-tooling/build.gradle.kts | 4 - .../tooling/PreviewComposeWorkflowTest.kt | 37 +++--- .../compose/tooling/PreviewViewFactoryTest.kt | 45 +++----- .../compose/tooling/PlaceholderViewFactory.kt | 8 +- core-compose/api/core-compose.api | 6 - core-compose/build.gradle.kts | 5 +- .../ui/compose/ComposeViewFactoryTest.kt | 22 ++-- .../ui/compose/CompositionRootTest.kt | 43 ++++--- .../workflow/ui/compose/RenderAsStateTest.kt | 17 +-- .../ui/compose/ViewEnvironmentsTest.kt | 11 +- .../ui/compose/WorkflowContainerTest.kt | 6 +- .../ui/compose/internal/ComposeSupportTest.kt | 97 ---------------- .../ui/compose/internal/ViewFactoriesTest.kt | 48 +++++++- .../workflow/ui/compose/ComposeViewFactory.kt | 14 +-- .../workflow/ui/compose/RenderAsState.kt | 7 +- .../ui/compose/internal/ComposeSupport.kt | 108 ------------------ .../compose/internal/ComposeWorkflowImpl.kt | 4 +- .../ui/compose/internal/ViewFactories.kt | 6 +- samples/build.gradle.kts | 5 +- .../hellocomposebinding/HelloBindingTest.kt | 14 +-- .../sample/launcher/SampleLauncherTest.kt | 9 +- .../nestedrenderings/NestedRenderingsTest.kt | 24 ++-- .../sample/hellocompose/HelloBinding.kt | 2 - .../hellocomposebinding/HelloBinding.kt | 2 - .../sample/launcher/SampleLauncherApp.kt | 21 ++-- .../nestedrenderings/RecursiveViewFactory.kt | 2 + 30 files changed, 201 insertions(+), 420 deletions(-) delete mode 100644 core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/internal/ComposeSupportTest.kt delete mode 100644 core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ComposeSupport.kt diff --git a/.buildscript/configure-compose.gradle b/.buildscript/configure-compose.gradle index 7534e01d68..cebfa2ec45 100644 --- a/.buildscript/configure-compose.gradle +++ b/.buildscript/configure-compose.gradle @@ -14,23 +14,12 @@ * limitations under the License. */ -/* -In addition applying this file, as of dev10 modules that use Compose also need to include this -code snippet to avoid warnings about using compiler version 1.4 (this is because the compiler plugin -is built against compiler source that is in a liminal state between 1.3 and 1.4, the warnings are -safe to ignore and this suppresses them): - -tasks.withType().configureEach { - kotlinOptions.apiVersion = "1.3" -} -*/ - android { buildFeatures { compose true } composeOptions { - kotlinCompilerVersion "1.3.70-dev-withExperimentalGoogleExtensions-20200424" + kotlinCompilerVersion "1.4.0-dev-withExperimentalGoogleExtensions-20200720" kotlinCompilerExtensionVersion Versions.compose } } diff --git a/README.md b/README.md index 521d5b1345..da8be40653 100644 --- a/README.md +++ b/README.md @@ -48,20 +48,12 @@ android { compose true } composeOptions { - kotlinCompilerVersion "1.3.70-dev-withExperimentalGoogleExtensions-20200424" + kotlinCompilerVersion "1.4.0-dev-withExperimentalGoogleExtensions-20200720" kotlinCompilerExtensionVersion "${compose_version}" } } ``` -You may also need to set the Kotlin API version to 1.3: - -```groovy -tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach { - kotlinOptions.apiVersion = "1.3" -} -``` - To create a `ViewFactory`, call `composedViewFactory`. The lambda passed to `composedViewFactory` is a `@Composable` function. diff --git a/build.gradle.kts b/build.gradle.kts index 7c1acfab7c..b819077ffd 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -33,8 +33,10 @@ buildscript { mavenCentral() gradlePluginPortal() google() + // For Kotlin 1.4. + maven("https://dl.bintray.com/kotlin/kotlin-eap") // For binary compatibility validator. - maven { url = uri("https://kotlin.bintray.com/kotlinx") } + maven("https://kotlin.bintray.com/kotlinx") } } @@ -46,6 +48,8 @@ subprojects { google() mavenCentral() jcenter() + // For Kotlin 1.4. + maven("https://dl.bintray.com/kotlin/kotlin-eap") } configurations.all { @@ -84,8 +88,14 @@ subprojects { jvmTarget = "1.8" // Don't panic, all this does is allow us to use the @OptIn meta-annotation. - // to define our own experiments. - freeCompilerArgs += "-Xopt-in=kotlin.RequiresOptIn" + // to define our own experiments, and some required args for compose dev15 taken from + // https://developer.android.com/jetpack/androidx/releases/compose-runtime + freeCompilerArgs += listOf( + "-Xopt-in=kotlin.RequiresOptIn", + "-Xallow-jvm-ir-dependencies", + "-Xskip-prerelease-check" + ) + } } diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt index 6ac60fd36d..2163fc5beb 100644 --- a/buildSrc/src/main/java/Dependencies.kt +++ b/buildSrc/src/main/java/Dependencies.kt @@ -19,25 +19,26 @@ import java.util.Locale.US import kotlin.reflect.full.declaredMembers object Versions { - const val compose = "0.1.0-dev12" - const val kotlin = "1.3.71" + const val compose = "0.1.0-dev15" + const val kotlin = "1.4-M3" const val targetSdk = 29 const val workflow = "0.28.0" } @Suppress("unused") object Dependencies { - const val android_gradle_plugin = "com.android.tools.build:gradle:4.2.0-alpha01" + const val android_gradle_plugin = "com.android.tools.build:gradle:4.2.0-alpha05" object AndroidX { const val appcompat = "androidx.appcompat:appcompat:1.1.0" } object Compose { - const val foundation = "androidx.ui:ui-foundation:${Versions.compose}" - const val layout = "androidx.ui:ui-layout:${Versions.compose}" - const val material = "androidx.ui:ui-material:${Versions.compose}" - const val savedstate = "androidx.ui:ui-saved-instance-state:${Versions.compose}" + const val foundation = "androidx.compose.foundation:foundation:${Versions.compose}" + const val layout = "androidx.compose.foundation:foundation-layout:${Versions.compose}" + const val material = "androidx.compose.material:material:${Versions.compose}" + const val savedstate = + "androidx.compose.runtime:runtime-saved-instance-state:${Versions.compose}" const val test = "androidx.ui:ui-test:${Versions.compose}" const val tooling = "androidx.ui:ui-tooling:${Versions.compose}" } diff --git a/compose-tooling/build.gradle.kts b/compose-tooling/build.gradle.kts index 299b6230e5..a8a5f7f305 100644 --- a/compose-tooling/build.gradle.kts +++ b/compose-tooling/build.gradle.kts @@ -1,4 +1,3 @@ -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile /* * Copyright 2020 Square Inc. @@ -30,9 +29,6 @@ apply(from = rootProject.file(".buildscript/configure-android-defaults.gradle")) apply(from = rootProject.file(".buildscript/android-ui-tests.gradle")) apply(from = rootProject.file(".buildscript/configure-compose.gradle")) -tasks.withType { - kotlinOptions.apiVersion = "1.3" -} dependencies { api(project(":core-compose")) diff --git a/compose-tooling/src/androidTest/java/com/squareup/workflow/ui/compose/tooling/PreviewComposeWorkflowTest.kt b/compose-tooling/src/androidTest/java/com/squareup/workflow/ui/compose/tooling/PreviewComposeWorkflowTest.kt index 1c09b99400..0e343a5c39 100644 --- a/compose-tooling/src/androidTest/java/com/squareup/workflow/ui/compose/tooling/PreviewComposeWorkflowTest.kt +++ b/compose-tooling/src/androidTest/java/com/squareup/workflow/ui/compose/tooling/PreviewComposeWorkflowTest.kt @@ -23,11 +23,10 @@ import androidx.ui.core.Modifier import androidx.ui.foundation.Text import androidx.ui.layout.Column import androidx.ui.layout.size -import androidx.ui.semantics.Semantics import androidx.ui.test.assertIsDisplayed import androidx.ui.test.assertIsNotDisplayed import androidx.ui.test.createComposeRule -import androidx.ui.test.findByText +import androidx.ui.test.onNodeWithText import androidx.ui.tooling.preview.Preview import androidx.ui.unit.dp import com.squareup.workflow.Workflow @@ -39,7 +38,7 @@ import org.junit.Test import org.junit.runner.RunWith /** - * Duplicate of [PreviewViewFactoryTest] but for [com.squareup.workflow.compose.ComposeWorkflow]. + * Duplicate of [PreviewViewFactoryTest] but for [com.squareup.workflow.ui.compose.ComposeWorkflow]. */ @RunWith(AndroidJUnit4::class) class PreviewComposeWorkflowTest { @@ -51,8 +50,8 @@ class PreviewComposeWorkflowTest { ParentWithOneChildPreview() } - findByText("one").assertIsDisplayed() - findByText("two").assertIsDisplayed() + onNodeWithText("one").assertIsDisplayed() + onNodeWithText("two").assertIsDisplayed() } @Test fun twoChildren() { @@ -60,9 +59,9 @@ class PreviewComposeWorkflowTest { ParentWithTwoChildrenPreview() } - findByText("one").assertIsDisplayed() - findByText("two").assertIsDisplayed() - findByText("three").assertIsDisplayed() + onNodeWithText("one").assertIsDisplayed() + onNodeWithText("two").assertIsDisplayed() + onNodeWithText("three").assertIsDisplayed() } @Test fun modifierIsApplied() { @@ -71,8 +70,8 @@ class PreviewComposeWorkflowTest { } // The view factory will be rendered with size (0,0), so it should be reported as not displayed. - findByText("one").assertIsNotDisplayed() - findByText("two").assertIsNotDisplayed() + onNodeWithText("one").assertIsNotDisplayed() + onNodeWithText("two").assertIsNotDisplayed() } @Test fun placeholderModifierIsApplied() { @@ -81,8 +80,8 @@ class PreviewComposeWorkflowTest { } // The child will be rendered with size (0,0), so it should be reported as not displayed. - findByText("one").assertIsDisplayed() - findByText("two").assertIsNotDisplayed() + onNodeWithText("one").assertIsDisplayed() + onNodeWithText("two").assertIsNotDisplayed() } @Test fun customViewEnvironment() { @@ -90,16 +89,14 @@ class PreviewComposeWorkflowTest { ParentConsumesCustomKeyPreview() } - findByText("foo").assertIsDisplayed() + onNodeWithText("foo").assertIsDisplayed() } private val ParentWithOneChild = Workflow.composed, Nothing> { props, _, environment -> Column { Text(props.first) - Semantics(container = true, mergeAllDescendants = true) { - WorkflowRendering(props.second, environment) - } + WorkflowRendering(props.second, environment) } } @@ -110,13 +107,9 @@ class PreviewComposeWorkflowTest { private val ParentWithTwoChildren = Workflow.composed, Nothing> { props, _, environment -> Column { - Semantics(container = true) { - WorkflowRendering(rendering = props.first, viewEnvironment = environment) - } + WorkflowRendering(rendering = props.first, viewEnvironment = environment) Text(props.second) - Semantics(container = true) { - WorkflowRendering(rendering = props.third, viewEnvironment = environment) - } + WorkflowRendering(rendering = props.third, viewEnvironment = environment) } } diff --git a/compose-tooling/src/androidTest/java/com/squareup/workflow/ui/compose/tooling/PreviewViewFactoryTest.kt b/compose-tooling/src/androidTest/java/com/squareup/workflow/ui/compose/tooling/PreviewViewFactoryTest.kt index 1d4b44f3d6..238e954c37 100644 --- a/compose-tooling/src/androidTest/java/com/squareup/workflow/ui/compose/tooling/PreviewViewFactoryTest.kt +++ b/compose-tooling/src/androidTest/java/com/squareup/workflow/ui/compose/tooling/PreviewViewFactoryTest.kt @@ -23,11 +23,10 @@ import androidx.ui.core.Modifier import androidx.ui.foundation.Text import androidx.ui.layout.Column import androidx.ui.layout.size -import androidx.ui.semantics.Semantics import androidx.ui.test.assertIsDisplayed import androidx.ui.test.assertIsNotDisplayed import androidx.ui.test.createComposeRule -import androidx.ui.test.findByText +import androidx.ui.test.onNodeWithText import androidx.ui.tooling.preview.Preview import androidx.ui.unit.dp import com.squareup.workflow.ui.ViewEnvironmentKey @@ -47,8 +46,8 @@ class PreviewViewFactoryTest { ParentWithOneChildPreview() } - findByText("one").assertIsDisplayed() - findByText("two").assertIsDisplayed() + onNodeWithText("one").assertIsDisplayed() + onNodeWithText("two").assertIsDisplayed() } @Test fun twoChildren() { @@ -56,9 +55,9 @@ class PreviewViewFactoryTest { ParentWithTwoChildrenPreview() } - findByText("one").assertIsDisplayed() - findByText("two").assertIsDisplayed() - findByText("three").assertIsDisplayed() + onNodeWithText("one").assertIsDisplayed() + onNodeWithText("two").assertIsDisplayed() + onNodeWithText("three").assertIsDisplayed() } @Test fun recursive() { @@ -66,9 +65,9 @@ class PreviewViewFactoryTest { ParentRecursivePreview() } - findByText("one").assertIsDisplayed() - findByText("two").assertIsDisplayed() - findByText("three").assertIsDisplayed() + onNodeWithText("one").assertIsDisplayed() + onNodeWithText("two").assertIsDisplayed() + onNodeWithText("three").assertIsDisplayed() } @Test fun modifierIsApplied() { @@ -77,8 +76,8 @@ class PreviewViewFactoryTest { } // The view factory will be rendered with size (0,0), so it should be reported as not displayed. - findByText("one").assertIsNotDisplayed() - findByText("two").assertIsNotDisplayed() + onNodeWithText("one").assertIsNotDisplayed() + onNodeWithText("two").assertIsNotDisplayed() } @Test fun placeholderModifierIsApplied() { @@ -87,8 +86,8 @@ class PreviewViewFactoryTest { } // The child will be rendered with size (0,0), so it should be reported as not displayed. - findByText("one").assertIsDisplayed() - findByText("two").assertIsNotDisplayed() + onNodeWithText("one").assertIsDisplayed() + onNodeWithText("two").assertIsNotDisplayed() } @Test fun customViewEnvironment() { @@ -96,16 +95,14 @@ class PreviewViewFactoryTest { ParentConsumesCustomKeyPreview() } - findByText("foo").assertIsDisplayed() + onNodeWithText("foo").assertIsDisplayed() } private val ParentWithOneChild = composedViewFactory> { rendering, environment -> Column { Text(rendering.first) - Semantics(container = true, mergeAllDescendants = true) { - WorkflowRendering(rendering.second, environment) - } + WorkflowRendering(rendering.second, environment) } } @@ -116,13 +113,9 @@ class PreviewViewFactoryTest { private val ParentWithTwoChildren = composedViewFactory> { rendering, environment -> Column { - Semantics(container = true) { - WorkflowRendering(rendering.first, environment) - } + WorkflowRendering(rendering.first, environment) Text(rendering.second) - Semantics(container = true) { - WorkflowRendering(rendering.third, environment) - } + WorkflowRendering(rendering.third, environment) } } @@ -139,9 +132,7 @@ class PreviewViewFactoryTest { Column { Text(rendering.text) rendering.child?.let { child -> - Semantics(container = true) { - WorkflowRendering(rendering = child, viewEnvironment = environment) - } + WorkflowRendering(rendering = child, viewEnvironment = environment) } } } diff --git a/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/PlaceholderViewFactory.kt b/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/PlaceholderViewFactory.kt index 5c66c40a93..2278801c9c 100644 --- a/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/PlaceholderViewFactory.kt +++ b/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/PlaceholderViewFactory.kt @@ -30,7 +30,6 @@ import androidx.ui.graphics.Color import androidx.ui.graphics.Paint import androidx.ui.graphics.Shadow import androidx.ui.graphics.drawscope.DrawScope -import androidx.ui.graphics.drawscope.Stroke import androidx.ui.graphics.drawscope.drawCanvas import androidx.ui.graphics.drawscope.rotate import androidx.ui.graphics.withSaveLayer @@ -112,10 +111,9 @@ private fun DrawScope.drawHatch( spaceWidth: Dp, angle: Float ) { - val strokeWidthPx = strokeWidth.toPx().value - val spaceWidthPx = spaceWidth.toPx().value + val strokeWidthPx = strokeWidth.toPx() + val spaceWidthPx = spaceWidth.toPx() val strokeColor = color.scaleColors(.5f) - val stroke = Stroke(width = strokeWidthPx) rotate(angle) { // Draw outside our bounds to fill the space even when rotated. @@ -130,7 +128,7 @@ private fun DrawScope.drawHatch( strokeColor, Offset(left, y), Offset(right, y), - stroke = stroke + strokeWidthPx ) y += spaceWidthPx * 2 } diff --git a/core-compose/api/core-compose.api b/core-compose/api/core-compose.api index 86dd0c515a..9267d5d467 100644 --- a/core-compose/api/core-compose.api +++ b/core-compose/api/core-compose.api @@ -1,6 +1,5 @@ public final class com/squareup/workflow/ui/compose/ComposeRendering { public static final field Companion Lcom/squareup/workflow/ui/compose/ComposeRendering$Companion; - public static final fun ()V public fun (Lkotlin/jvm/functions/Function4;)V } @@ -26,7 +25,6 @@ public final class com/squareup/workflow/ui/compose/ComposeWorkflowKt { } public final class com/squareup/workflow/ui/compose/CompositionRootKt { - public static final fun ()V public static final fun withCompositionRoot (Lcom/squareup/workflow/ui/ViewEnvironment;Lkotlin/jvm/functions/Function4;)Lcom/squareup/workflow/ui/ViewEnvironment; public static final fun withCompositionRoot (Lcom/squareup/workflow/ui/ViewRegistry;Lkotlin/jvm/functions/Function4;)Lcom/squareup/workflow/ui/ViewRegistry; } @@ -49,10 +47,6 @@ public final class com/squareup/workflow/ui/compose/WorkflowContainerKt { public static final fun WorkflowContainer (Lcom/squareup/workflow/Workflow;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;III)V } -public final class com/squareup/workflow/ui/compose/internal/ComposeSupportKt { - public static final fun ()V -} - public final class com/squareup/workflow/ui/core/compose/BuildConfig { public static final field BUILD_TYPE Ljava/lang/String; public static final field DEBUG Z diff --git a/core-compose/build.gradle.kts b/core-compose/build.gradle.kts index cc3559f862..a50a205c29 100644 --- a/core-compose/build.gradle.kts +++ b/core-compose/build.gradle.kts @@ -1,4 +1,4 @@ -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + /* * Copyright 2019 Square Inc. @@ -30,9 +30,6 @@ apply(from = rootProject.file(".buildscript/configure-android-defaults.gradle")) apply(from = rootProject.file(".buildscript/android-ui-tests.gradle")) apply(from = rootProject.file(".buildscript/configure-compose.gradle")) -tasks.withType { - kotlinOptions.apiVersion = "1.3" -} dependencies { api(Dependencies.Kotlin.stdlib) diff --git a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/ComposeViewFactoryTest.kt b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/ComposeViewFactoryTest.kt index 2bd3c2557e..a6e02c8895 100644 --- a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/ComposeViewFactoryTest.kt +++ b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/ComposeViewFactoryTest.kt @@ -17,13 +17,13 @@ package com.squareup.workflow.ui.compose import android.content.Context import android.widget.FrameLayout -import androidx.compose.FrameManager import androidx.compose.mutableStateOf import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.ui.foundation.Text import androidx.ui.layout.Column import androidx.ui.test.createComposeRule -import androidx.ui.test.findByText +import androidx.ui.test.onNodeWithText +import androidx.ui.viewinterop.emitView import com.squareup.workflow.ui.ViewEnvironment import com.squareup.workflow.ui.ViewRegistry import com.squareup.workflow.ui.WorkflowViewStub @@ -47,18 +47,20 @@ class ComposeViewFactoryTest { } composeRule.setContent { - // This is valid Compose code, but the IDE doesn't know that yet so it will show an - // unsuppressable error. - RootView(viewEnvironment = viewEnvironment) + emitView(::RootView) { + it.setViewEnvironment(viewEnvironment) + } } // Compose bug doesn't let us use assertIsDisplayed on older devices. // See https://issuetracker.google.com/issues/157728188. - findByText("one\ntwo").assertExists() - FrameManager.framed { - wrapperText.value = "ENO" - } - findByText("ENO\ntwo").assertExists() + onNodeWithText("one").assertExists() + onNodeWithText("two").assertExists() + + wrapperText.value = "ENO" + + onNodeWithText("ENO").assertExists() + onNodeWithText("two").assertExists() } private class RootView(context: Context) : FrameLayout(context) { diff --git a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/CompositionRootTest.kt b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/CompositionRootTest.kt index a900a1056c..3d277ed422 100644 --- a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/CompositionRootTest.kt +++ b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/CompositionRootTest.kt @@ -15,15 +15,13 @@ */ package com.squareup.workflow.ui.compose -import androidx.compose.FrameManager import androidx.compose.mutableStateOf import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.ui.foundation.Text import androidx.ui.layout.Column -import androidx.ui.semantics.Semantics import androidx.ui.test.assertIsDisplayed import androidx.ui.test.createComposeRule -import androidx.ui.test.findByText +import androidx.ui.test.onNodeWithText import com.google.common.truth.Truth.assertThat import org.junit.Rule import org.junit.Test @@ -49,7 +47,10 @@ class CompositionRootTest { } } - findByText("one\ntwo").assertIsDisplayed() + // These semantics used to merge, but as of dev15, they don't, which seems to be a bug. + // https://issuetracker.google.com/issues/161979921 + onNodeWithText("one").assertIsDisplayed() + onNodeWithText("two").assertIsDisplayed() } @Test fun wrapWithRootIfNecessary_onlyWrapsOnce() { @@ -69,7 +70,9 @@ class CompositionRootTest { } } - findByText("one\ntwo\nthree").assertIsDisplayed() + onNodeWithText("one").assertIsDisplayed() + onNodeWithText("two").assertIsDisplayed() + onNodeWithText("three").assertIsDisplayed() } @Test fun wrapWithRootIfNecessary_seesUpdatesFromRootWrapper() { @@ -87,11 +90,11 @@ class CompositionRootTest { } } - findByText("one\ntwo").assertIsDisplayed() - FrameManager.framed { - wrapperText.value = "ENO" - } - findByText("ENO\ntwo").assertIsDisplayed() + onNodeWithText("one").assertIsDisplayed() + onNodeWithText("two").assertIsDisplayed() + wrapperText.value = "ENO" + onNodeWithText("ENO").assertIsDisplayed() + onNodeWithText("two").assertIsDisplayed() } @Test fun wrapWithRootIfNecessary_rewrapsWhenDifferentRoot() { @@ -115,11 +118,11 @@ class CompositionRootTest { } } - findByText("one\ntwo").assertIsDisplayed() - FrameManager.framed { - viewEnvironment.value = root2 - } - findByText("ENO\ntwo").assertIsDisplayed() + onNodeWithText("one").assertIsDisplayed() + onNodeWithText("two").assertIsDisplayed() + viewEnvironment.value = root2 + onNodeWithText("ENO").assertIsDisplayed() + onNodeWithText("two").assertIsDisplayed() } @Test fun safeComposeViewFactoryRoot_wraps_content() { @@ -133,16 +136,12 @@ class CompositionRootTest { composeRule.setContent { safeRoot { - // Need an explicit semantics container, otherwise both Texts will be merged into a single - // Semantics object with the text "Parent\nChild". - Semantics(container = true) { - Text("Child") - } + Text("Child") } } - findByText("Parent").assertIsDisplayed() - findByText("Child").assertIsDisplayed() + onNodeWithText("Parent").assertIsDisplayed() + onNodeWithText("Child").assertIsDisplayed() } @Test fun safeComposeViewFactoryRoot_throws_whenChildrenNotInvoked() { diff --git a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/RenderAsStateTest.kt b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/RenderAsStateTest.kt index 868c6fe7ab..4eee2dd260 100644 --- a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/RenderAsStateTest.kt +++ b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/RenderAsStateTest.kt @@ -17,14 +17,13 @@ package com.squareup.workflow.ui.compose -import androidx.compose.FrameManager import androidx.compose.Providers import androidx.compose.mutableStateOf import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.ui.savedinstancestate.UiSavedStateRegistry import androidx.ui.savedinstancestate.UiSavedStateRegistryAmbient import androidx.ui.test.createComposeRule -import androidx.ui.test.runOnIdleCompose +import androidx.ui.test.runOnIdle import androidx.ui.test.waitForIdle import com.google.common.truth.Truth.assertThat import com.squareup.workflow.RenderContext @@ -56,7 +55,7 @@ class RenderAsStateTest { initialRendering = workflow.renderAsState("foo").value } - runOnIdleCompose { + runOnIdle { assertThat(initialRendering).isEqualTo("foo") } } @@ -72,9 +71,7 @@ class RenderAsStateTest { waitForIdle() assertThat(rendering).isEqualTo("foo") - FrameManager.framed { - props.value = "bar" - } + props.value = "bar" waitForIdle() assertThat(rendering).isEqualTo("bar") } @@ -124,9 +121,7 @@ class RenderAsStateTest { rendering.updateString("foo") waitForIdle() - val savedValues = FrameManager.framed { - savedStateRegistry.performSave() - } + val savedValues = savedStateRegistry.performSave() println("saved keys: ${savedValues.keys}") // Relying on the int key across all runtimes is brittle, so use an explicit key. val snapshot = ByteString.of(*(savedValues.getValue(SNAPSHOT_KEY) as ByteArray)) @@ -185,9 +180,7 @@ class RenderAsStateTest { // Change the workflow instance being rendered. This should restart the runtime, but restore // it from the snapshot. - FrameManager.framed { - currentWorkflow.value = workflow2 - } + currentWorkflow.value = workflow2 waitForIdle() assertWasRecomposed() diff --git a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/ViewEnvironmentsTest.kt b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/ViewEnvironmentsTest.kt index 364e36795d..e1dbf5b9f1 100644 --- a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/ViewEnvironmentsTest.kt +++ b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/ViewEnvironmentsTest.kt @@ -15,13 +15,12 @@ */ package com.squareup.workflow.ui.compose -import androidx.compose.FrameManager import androidx.compose.mutableStateOf import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.ui.foundation.Text import androidx.ui.test.assertIsDisplayed import androidx.ui.test.createComposeRule -import androidx.ui.test.findByText +import androidx.ui.test.onNodeWithText import com.squareup.workflow.ui.ViewEnvironment import com.squareup.workflow.ui.ViewRegistry import org.junit.Rule @@ -46,10 +45,8 @@ class ViewEnvironmentsTest { WorkflowRendering("hello", ViewEnvironment(registry.value)) } - findByText("hello").assertIsDisplayed() - FrameManager.framed { - registry.value = registry2 - } - findByText("olleh").assertIsDisplayed() + onNodeWithText("hello").assertIsDisplayed() + registry.value = registry2 + onNodeWithText("olleh").assertIsDisplayed() } } diff --git a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/WorkflowContainerTest.kt b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/WorkflowContainerTest.kt index 8ae3f540f8..5f0ad201f1 100644 --- a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/WorkflowContainerTest.kt +++ b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/WorkflowContainerTest.kt @@ -21,7 +21,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.ui.foundation.Text import androidx.ui.test.assertIsDisplayed import androidx.ui.test.createComposeRule -import androidx.ui.test.findByText +import androidx.ui.test.onNodeWithText import com.squareup.workflow.Workflow import com.squareup.workflow.stateless import com.squareup.workflow.ui.ViewEnvironment @@ -43,7 +43,7 @@ class WorkflowContainerTest { WorkflowContainer(workflow, ViewEnvironment(registry)) } - findByText("hello").assertIsDisplayed() + onNodeWithText("hello").assertIsDisplayed() } @Test fun automaticallyAddsComposeRenderingFactory() { @@ -56,6 +56,6 @@ class WorkflowContainerTest { WorkflowContainer(workflow, ViewEnvironment(registry)) } - findByText("it worked").assertIsDisplayed() + onNodeWithText("it worked").assertIsDisplayed() } } diff --git a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/internal/ComposeSupportTest.kt b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/internal/ComposeSupportTest.kt deleted file mode 100644 index 9318c83d08..0000000000 --- a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/internal/ComposeSupportTest.kt +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright 2020 Square Inc. - * - * 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:Suppress("RemoveEmptyParenthesesFromAnnotationEntry") - -package com.squareup.workflow.ui.compose.internal - -import android.content.Context -import android.widget.FrameLayout -import androidx.compose.Composable -import androidx.compose.CompositionReference -import androidx.compose.FrameManager -import androidx.compose.Providers -import androidx.compose.ambientOf -import androidx.compose.compositionReference -import androidx.compose.mutableStateOf -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.ui.foundation.Text -import androidx.ui.test.createComposeRule -import androidx.ui.test.findBySubstring -import androidx.ui.test.findByText -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith - -@RunWith(AndroidJUnit4::class) -class ComposeSupportTest { - - @Rule @JvmField val composeRule = createComposeRule() - - @Test fun ambientsPassThroughSubcomposition() { - composeRule.setContent { - TestComposable("foo") - } - - // Compose bug doesn't let us use assertIsDisplayed on older devices. - // See https://issuetracker.google.com/issues/157728188. - findByText("foo").assertExists() - } - - @Test fun ambientChangesPassThroughSubcomposition() { - val ambientValue = mutableStateOf("foo") - composeRule.setContent { - TestComposable(ambientValue.value) - } - - // Compose bug doesn't let us use assertIsDisplayed on older devices. - // See https://issuetracker.google.com/issues/157728188. - findBySubstring("foo").assertExists() - FrameManager.framed { - ambientValue.value = "bar" - } - findByText("bar").assertExists() - } - - @Composable private fun TestComposable(ambientValue: String) { - Providers(TestAmbient provides ambientValue) { - LegacyHostComposable { - Text(TestAmbient.current) - } - } - } - - @Composable private fun LegacyHostComposable(leafContent: @Composable() () -> Unit) { - val wormhole = Wormhole(compositionReference(), leafContent) - // This is valid Compose code, but the IDE doesn't know that yet so it will show an - // unsuppressable error. - WormholeView(wormhole = wormhole) - } - - private class Wormhole( - val parentReference: CompositionReference, - val childContent: @Composable() () -> Unit - ) - - private class WormholeView(context: Context) : FrameLayout(context) { - fun setWormhole(wormhole: Wormhole) { - setContent(wormhole.parentReference, wormhole.childContent) - } - } - - private companion object { - val TestAmbient = ambientOf { error("Ambient not provided") } - } -} diff --git a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/internal/ViewFactoriesTest.kt b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/internal/ViewFactoriesTest.kt index f88fe99c7a..66ad184207 100644 --- a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/internal/ViewFactoriesTest.kt +++ b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/internal/ViewFactoriesTest.kt @@ -15,16 +15,27 @@ */ package com.squareup.workflow.ui.compose.internal +import android.content.Context +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.compose.mutableStateOf +import androidx.test.espresso.Espresso.onView +import androidx.test.espresso.assertion.ViewAssertions.matches +import androidx.test.espresso.matcher.ViewMatchers.isDisplayed +import androidx.test.espresso.matcher.ViewMatchers.withText import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.ui.foundation.Text import androidx.ui.layout.Column import androidx.ui.test.assertIsDisplayed import androidx.ui.test.createComposeRule -import androidx.ui.test.findByText +import androidx.ui.test.onNodeWithText import com.squareup.workflow.ui.ViewEnvironment +import com.squareup.workflow.ui.ViewFactory import com.squareup.workflow.ui.ViewRegistry -import com.squareup.workflow.ui.compose.composedViewFactory +import com.squareup.workflow.ui.bindShowRendering import com.squareup.workflow.ui.compose.WorkflowRendering +import com.squareup.workflow.ui.compose.composedViewFactory import com.squareup.workflow.ui.compose.withCompositionRoot import org.junit.Rule import org.junit.Test @@ -48,14 +59,45 @@ class ViewFactoriesTest { WorkflowRendering(TestRendering("two"), viewEnvironment) } - findByText("one\ntwo").assertIsDisplayed() + onNodeWithText("one").assertIsDisplayed() + onNodeWithText("two").assertIsDisplayed() + } + + @Test fun WorkflowRendering_legacyAndroidViewRendersUpdates() { + val wrapperText = mutableStateOf("two") + val viewEnvironment = ViewEnvironment(ViewRegistry(LegacyViewViewFactory)) + + composeRule.setContent { + WorkflowRendering(LegacyViewRendering(wrapperText.value), viewEnvironment) + } + + onView(withText("two")).check(matches(isDisplayed())) + wrapperText.value = "OWT" + onView(withText("OWT")).check(matches(isDisplayed())) } private data class TestRendering(val text: String) + private data class LegacyViewRendering(val text: String) private companion object { val TestFactory = composedViewFactory { rendering, _ -> Text(rendering.text) } + val LegacyViewViewFactory = object : ViewFactory { + override val type = LegacyViewRendering::class + + override fun buildView( + initialRendering: LegacyViewRendering, + initialViewEnvironment: ViewEnvironment, + contextForNewView: Context, + container: ViewGroup? + ): View { + return TextView(contextForNewView).apply { + bindShowRendering(initialRendering, initialViewEnvironment) { rendering, _ -> + text = rendering.text + } + } + } + } } } diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactory.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactory.kt index 772234668b..65bd62889e 100644 --- a/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactory.kt +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactory.kt @@ -23,13 +23,14 @@ import android.view.View import android.view.ViewGroup import android.widget.FrameLayout import androidx.compose.Composable -import androidx.compose.FrameManager +import androidx.compose.ExperimentalComposeApi +import androidx.compose.Recomposer import androidx.compose.mutableStateOf +import androidx.ui.core.setContent import com.squareup.workflow.ui.ViewEnvironment import com.squareup.workflow.ui.ViewFactory import com.squareup.workflow.ui.bindShowRendering import com.squareup.workflow.ui.compose.internal.ParentComposition -import com.squareup.workflow.ui.compose.internal.setContent import kotlin.reflect.KClass /** @@ -91,6 +92,7 @@ internal class ComposeViewFactory( internal val content: @Composable() (RenderingT, ViewEnvironment) -> Unit ) : ViewFactory { + @OptIn(ExperimentalComposeApi::class) override fun buildView( initialRendering: RenderingT, initialViewEnvironment: ViewEnvironment, @@ -110,12 +112,10 @@ internal class ComposeViewFactory( initialRendering, initialViewEnvironment ) { rendering, environment -> - FrameManager.framed { - state.value = Pair(rendering, environment) - } + state.value = Pair(rendering, environment) } - composeContainer.setContent(parent = null) { + composeContainer.setContent(Recomposer.current(), parentComposition = null) { val (rendering, environment) = state.value content(rendering, environment) } @@ -131,7 +131,7 @@ internal class ComposeViewFactory( initialViewEnvironment ) { rendering, environment -> // Entry point to the world of Compose. - composeContainer.setContent(parentComposition) { + composeContainer.setContent(Recomposer.current(), parentComposition) { content(rendering, environment) } } diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/RenderAsState.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/RenderAsState.kt index 3307a3aa4b..2ca6218167 100644 --- a/core-compose/src/main/java/com/squareup/workflow/ui/compose/RenderAsState.kt +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/RenderAsState.kt @@ -20,7 +20,6 @@ package com.squareup.workflow.ui.compose import androidx.annotation.VisibleForTesting import androidx.compose.Composable import androidx.compose.CompositionLifecycleObserver -import androidx.compose.FrameManager import androidx.compose.MutableState import androidx.compose.State import androidx.compose.mutableStateOf @@ -210,10 +209,8 @@ private class WorkflowState( session.renderingsAndSnapshots .onEach { (rendering, snapshot) -> - FrameManager.framed { - renderingState.value = rendering - snapshotState.value = snapshot - } + renderingState.value = rendering + snapshotState.value = snapshot } .launchIn(this) } diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ComposeSupport.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ComposeSupport.kt deleted file mode 100644 index 2b716d4a25..0000000000 --- a/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ComposeSupport.kt +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright 2020 Square Inc. - * - * 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:Suppress("RemoveEmptyParenthesesFromAnnotationEntry") - -package com.squareup.workflow.ui.compose.internal - -import android.content.Context -import android.view.ViewGroup -import androidx.compose.Composable -import androidx.compose.Composition -import androidx.compose.CompositionReference -import androidx.compose.FrameManager -import androidx.compose.Recomposer -import androidx.compose.compositionFor -import androidx.ui.core.AndroidOwner -import androidx.ui.node.UiComposer -import com.squareup.workflow.ui.compose.internal.ReflectionSupport.createWrappedContent -import com.squareup.workflow.ui.core.compose.R - -private typealias WrappedComposition = Composition - -private val DefaultLayoutParams = ViewGroup.LayoutParams( - ViewGroup.LayoutParams.WRAP_CONTENT, - ViewGroup.LayoutParams.WRAP_CONTENT -) - -/** - * Copy of the built-in [setContent] function that takes an additional parent - * [CompositionReference]. This will eventually be built-in to Compose, but until then this function - * uses a bunch of reflection to access private Compose APIs. - * - * Once this ships in Compose, this whole file should be deleted. - * - * Tracked with Google [here](https://issuetracker.google.com/issues/156527485). - * Note that ambient _changes_ also don't seem to get propagated currently, that bug is tracked - * [here](https://issuetracker.google.com/issues/156527486). - */ -internal fun ViewGroup.setContent( - parent: CompositionReference?, - content: @Composable() () -> Unit -): Composition { - FrameManager.ensureStarted() - val composeView: AndroidOwner = - if (childCount > 0) { - getChildAt(0) as? AndroidOwner - } else { - removeAllViews(); null - } ?: AndroidOwner(context).also { addView(it.view, DefaultLayoutParams) } - return doSetContent(context, composeView, Recomposer.current(), parent, content) -} - -/** - * This is almost an exact copy of the private `doSetContent` function in Compose, but - * it also accepts a parent [CompositionReference]. - */ -private fun doSetContent( - context: Context, - owner: AndroidOwner, - recomposer: Recomposer, - parent: CompositionReference?, - content: @Composable() () -> Unit -): Composition { - // val original = compositionFor(context, owner.root, recomposer) - val original = compositionFor( - container = owner.root, - recomposer = recomposer, - parent = parent, - composerFactory = { slotTable, factoryRecomposer -> - UiComposer(context, owner.root, slotTable, factoryRecomposer) - } - ) - - val wrapped = owner.view.getTag(R.id.wrapped_composition_tag) - as? WrappedComposition - // ?: WrappedComposition(owner, original).also { - ?: createWrappedContent(owner, original).also { - owner.view.setTag(R.id.wrapped_composition_tag, it) - } - wrapped.setContent(content) - return wrapped -} - -private object ReflectionSupport { - - private val WRAPPED_COMPOSITION_CLASS = Class.forName("androidx.ui.core.WrappedComposition") - - private val WRAPPED_COMPOSITION_CTOR = - WRAPPED_COMPOSITION_CLASS.getConstructor(AndroidOwner::class.java, Composition::class.java) - .apply { isAccessible = true } - - fun createWrappedContent( - owner: AndroidOwner, - original: Composition - ): WrappedComposition = WRAPPED_COMPOSITION_CTOR.newInstance(owner, original) as Composition -} diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ComposeWorkflowImpl.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ComposeWorkflowImpl.kt index efdb89fb16..e57a2c2667 100644 --- a/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ComposeWorkflowImpl.kt +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ComposeWorkflowImpl.kt @@ -16,8 +16,8 @@ package com.squareup.workflow.ui.compose.internal import androidx.compose.MutableState -import androidx.compose.StructurallyEqual import androidx.compose.mutableStateOf +import androidx.compose.structuralEqualityPolicy import com.squareup.workflow.RenderContext import com.squareup.workflow.Sink import com.squareup.workflow.Snapshot @@ -45,7 +45,7 @@ internal class ComposeWorkflowImpl( props: PropsT, snapshot: Snapshot? ): State { - val propsHolder = mutableStateOf(props, areEquivalent = StructurallyEqual) + val propsHolder = mutableStateOf(props, policy = structuralEqualityPolicy()) val sinkHolder = SinkHolder() return State(propsHolder, sinkHolder, ComposeRendering { environment -> // The sink will get set on the first render pass, so it should never be null. diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ViewFactories.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ViewFactories.kt index 8f2302185f..da9769e2f4 100644 --- a/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ViewFactories.kt +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ViewFactories.kt @@ -24,6 +24,7 @@ import androidx.compose.compositionReference import androidx.compose.remember import androidx.ui.core.Modifier import androidx.ui.foundation.Box +import androidx.ui.viewinterop.emitView import com.squareup.workflow.ui.ViewEnvironment import com.squareup.workflow.ui.ViewFactory import com.squareup.workflow.ui.canShowRendering @@ -94,7 +95,10 @@ import kotlin.properties.Delegates.observable viewEnvironment + (ParentComposition to parentComposition) } - HostView(viewFactory = viewFactory, update = Pair(rendering, wrappedEnvironment)) + emitView(::HostView) { + it.viewFactory = viewFactory + it.update = Pair(rendering, wrappedEnvironment) + } } /** diff --git a/samples/build.gradle.kts b/samples/build.gradle.kts index 07668fdab9..94ab35bf7c 100644 --- a/samples/build.gradle.kts +++ b/samples/build.gradle.kts @@ -1,4 +1,4 @@ -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + /* * Copyright 2020 Square Inc. @@ -32,9 +32,6 @@ android { } apply(from = rootProject.file(".buildscript/configure-compose.gradle")) -tasks.withType { - kotlinOptions.apiVersion = "1.3" -} dependencies { implementation(project(":core-compose")) diff --git a/samples/src/androidTest/java/com/squareup/sample/hellocomposebinding/HelloBindingTest.kt b/samples/src/androidTest/java/com/squareup/sample/hellocomposebinding/HelloBindingTest.kt index 9723cbf5d4..5362545f0c 100644 --- a/samples/src/androidTest/java/com/squareup/sample/hellocomposebinding/HelloBindingTest.kt +++ b/samples/src/androidTest/java/com/squareup/sample/hellocomposebinding/HelloBindingTest.kt @@ -18,8 +18,8 @@ package com.squareup.sample.hellocomposebinding import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.ui.test.android.AndroidComposeTestRule import androidx.ui.test.assertIsDisplayed -import androidx.ui.test.doClick -import androidx.ui.test.findByText +import androidx.ui.test.onNodeWithText +import androidx.ui.test.performClick import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -31,13 +31,13 @@ class HelloBindingTest { @Rule @JvmField val composeRule = AndroidComposeTestRule() @Test fun togglesBetweenStates() { - findByText("Hello") + onNodeWithText("Hello") .assertIsDisplayed() - .doClick() - findByText("Goodbye") + .performClick() + onNodeWithText("Goodbye") .assertIsDisplayed() - .doClick() - findByText("Hello") + .performClick() + onNodeWithText("Hello") .assertIsDisplayed() } } diff --git a/samples/src/androidTest/java/com/squareup/sample/launcher/SampleLauncherTest.kt b/samples/src/androidTest/java/com/squareup/sample/launcher/SampleLauncherTest.kt index 1c6f6be414..60faf2b34d 100644 --- a/samples/src/androidTest/java/com/squareup/sample/launcher/SampleLauncherTest.kt +++ b/samples/src/androidTest/java/com/squareup/sample/launcher/SampleLauncherTest.kt @@ -20,9 +20,8 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry import androidx.ui.test.android.AndroidComposeTestRule import androidx.ui.test.assertIsDisplayed -import androidx.ui.test.doClick -import androidx.ui.test.findBySubstring -import androidx.ui.test.findByText +import androidx.ui.test.onNodeWithText +import androidx.ui.test.performClick import com.squareup.sample.R import org.junit.Rule import org.junit.Test @@ -36,11 +35,11 @@ class SampleLauncherTest { @Test fun allSamplesLaunch() { val appName = InstrumentationRegistry.getInstrumentation().targetContext.getString(R.string.app_name) - findByText(appName).assertIsDisplayed() + onNodeWithText(appName).assertIsDisplayed() samples.forEach { sample -> try { - findBySubstring(sample.description).doClick() + onNodeWithText(sample.description, useUnmergedTree = true).performClick() pressBack() } catch (e: Throwable) { throw AssertionError("Failed to launch sample ${sample.name}", e) diff --git a/samples/src/androidTest/java/com/squareup/sample/nestedrenderings/NestedRenderingsTest.kt b/samples/src/androidTest/java/com/squareup/sample/nestedrenderings/NestedRenderingsTest.kt index a4440c479e..139c26fc03 100644 --- a/samples/src/androidTest/java/com/squareup/sample/nestedrenderings/NestedRenderingsTest.kt +++ b/samples/src/androidTest/java/com/squareup/sample/nestedrenderings/NestedRenderingsTest.kt @@ -21,9 +21,9 @@ import androidx.ui.test.SemanticsNodeInteractionCollection import androidx.ui.test.android.AndroidComposeTestRule import androidx.ui.test.assertCountEquals import androidx.ui.test.assertIsDisplayed -import androidx.ui.test.doClick -import androidx.ui.test.findAllByText -import androidx.ui.test.findByText +import androidx.ui.test.onAllNodesWithText +import androidx.ui.test.onNodeWithText +import androidx.ui.test.performClick import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -37,23 +37,23 @@ class NestedRenderingsTest { @Rule @JvmField val composeRule = AndroidComposeTestRule() @Test fun childrenAreAddedAndRemoved() { - findByText(ADD_BUTTON_TEXT) + onNodeWithText(ADD_BUTTON_TEXT) .assertIsDisplayed() - .doClick() + .performClick() - findAllByText(ADD_BUTTON_TEXT) + onAllNodesWithText(ADD_BUTTON_TEXT) .assertCountEquals(2) - .forEach { it.doClick() } + .forEach { it.performClick() } - findAllByText(ADD_BUTTON_TEXT) + onAllNodesWithText(ADD_BUTTON_TEXT) .assertCountEquals(4) resetAll() - findAllByText(ADD_BUTTON_TEXT).assertCountEquals(1) + onAllNodesWithText(ADD_BUTTON_TEXT).assertCountEquals(1) } /** - * We can't rely on the order of nodes returned by [findAllByText], and the contents of the + * We can't rely on the order of nodes returned by [onAllNodesWithText], and the contents of the * collection will change as we remove nodes, so we have to double-loop over all reset buttons and * click them all until there is only one left. */ @@ -61,7 +61,7 @@ class NestedRenderingsTest { var foundNodes = Int.MAX_VALUE while (foundNodes > 1) { foundNodes = 0 - findAllByText("Reset").forEach { + onAllNodesWithText("Reset").forEach { try { it.assertExists() } catch (e: AssertionError) { @@ -69,7 +69,7 @@ class NestedRenderingsTest { return@forEach } foundNodes++ - it.doClick() + it.performClick() } } } diff --git a/samples/src/main/java/com/squareup/sample/hellocompose/HelloBinding.kt b/samples/src/main/java/com/squareup/sample/hellocompose/HelloBinding.kt index 5b39a449ab..10c3c9adbc 100644 --- a/samples/src/main/java/com/squareup/sample/hellocompose/HelloBinding.kt +++ b/samples/src/main/java/com/squareup/sample/hellocompose/HelloBinding.kt @@ -20,7 +20,6 @@ import androidx.ui.core.Modifier import androidx.ui.foundation.Text import androidx.ui.foundation.clickable import androidx.ui.layout.wrapContentSize -import androidx.ui.material.ripple.ripple import com.squareup.sample.hellocompose.HelloWorkflow.Rendering import com.squareup.workflow.ui.compose.composedViewFactory @@ -28,7 +27,6 @@ val HelloBinding = composedViewFactory { rendering, _ -> Text( rendering.message, modifier = Modifier - .ripple() .clickable(onClick = rendering.onClick) .wrapContentSize(Alignment.Center) ) diff --git a/samples/src/main/java/com/squareup/sample/hellocomposebinding/HelloBinding.kt b/samples/src/main/java/com/squareup/sample/hellocomposebinding/HelloBinding.kt index 3ae43cf062..682da58162 100644 --- a/samples/src/main/java/com/squareup/sample/hellocomposebinding/HelloBinding.kt +++ b/samples/src/main/java/com/squareup/sample/hellocomposebinding/HelloBinding.kt @@ -22,7 +22,6 @@ import androidx.ui.foundation.Text import androidx.ui.foundation.clickable import androidx.ui.layout.fillMaxSize import androidx.ui.layout.wrapContentSize -import androidx.ui.material.ripple.ripple import androidx.ui.tooling.preview.Preview import com.squareup.sample.hellocomposebinding.HelloWorkflow.Rendering import com.squareup.workflow.ui.compose.composedViewFactory @@ -32,7 +31,6 @@ val HelloBinding = composedViewFactory { rendering, _ -> Text( rendering.message, modifier = Modifier.fillMaxSize() - .ripple() .clickable(onClick = rendering.onClick) .wrapContentSize(Alignment.Center) ) diff --git a/samples/src/main/java/com/squareup/sample/launcher/SampleLauncherApp.kt b/samples/src/main/java/com/squareup/sample/launcher/SampleLauncherApp.kt index 6ad122ec39..0636600ce2 100644 --- a/samples/src/main/java/com/squareup/sample/launcher/SampleLauncherApp.kt +++ b/samples/src/main/java/com/squareup/sample/launcher/SampleLauncherApp.kt @@ -22,20 +22,19 @@ import androidx.compose.Composable import androidx.compose.remember import androidx.core.app.ActivityOptionsCompat.makeScaleUpAnimation import androidx.core.content.ContextCompat.startActivity -import androidx.ui.core.AndroidOwner import androidx.ui.core.ConfigurationAmbient import androidx.ui.core.LayoutCoordinates import androidx.ui.core.Modifier -import androidx.ui.core.OwnerAmbient import androidx.ui.core.PointerEventPass.PreDown import androidx.ui.core.Ref +import androidx.ui.core.ViewAmbient import androidx.ui.core.drawLayer import androidx.ui.core.gesture.rawPressStartGestureFilter import androidx.ui.core.globalBounds import androidx.ui.core.onPositioned -import androidx.ui.foundation.AdapterList import androidx.ui.foundation.Box import androidx.ui.foundation.Text +import androidx.ui.foundation.lazy.LazyColumnItems import androidx.ui.layout.aspectRatio import androidx.ui.layout.height import androidx.ui.layout.width @@ -57,13 +56,13 @@ import com.squareup.sample.R @Composable fun SampleLauncherApp() { MaterialTheme(colors = darkColorPalette()) { Scaffold( - topAppBar = { + topBar = { TopAppBar(title = { Text(stringResource(R.string.app_name)) }) } ) { - AdapterList(samples) { sample -> + LazyColumnItems(samples) { sample -> SampleItem(sample) } } @@ -75,9 +74,7 @@ import com.squareup.sample.R } @Composable private fun SampleItem(sample: Sample) { - // See https://issuetracker.google.com/issues/156875705. - @Suppress("DEPRECATION") - val rootView = (OwnerAmbient.current as AndroidOwner).view + val rootView = ViewAmbient.current /** * [androidx.ui.core.LayoutCoordinates.globalBounds] corresponds to the coordinates in the root @@ -148,10 +145,10 @@ private fun launchSample( val options: Bundle? = sourceBounds?.let { makeScaleUpAnimation( rootView, - it.left.value.toInt(), - it.top.value.toInt(), - it.width.value.toInt(), - it.height.value.toInt() + it.left.toInt(), + it.top.toInt(), + it.width.toInt(), + it.height.toInt() ).toBundle() } startActivity(context, intent, options) diff --git a/samples/src/main/java/com/squareup/sample/nestedrenderings/RecursiveViewFactory.kt b/samples/src/main/java/com/squareup/sample/nestedrenderings/RecursiveViewFactory.kt index fb169129be..9e0963e116 100644 --- a/samples/src/main/java/com/squareup/sample/nestedrenderings/RecursiveViewFactory.kt +++ b/samples/src/main/java/com/squareup/sample/nestedrenderings/RecursiveViewFactory.kt @@ -28,6 +28,7 @@ import androidx.ui.graphics.Color import androidx.ui.graphics.compositeOver import androidx.ui.layout.Arrangement.SpaceEvenly import androidx.ui.layout.Column +import androidx.ui.layout.ExperimentalLayout import androidx.ui.layout.FlowRow import androidx.ui.layout.MainAxisAlignment import androidx.ui.layout.SizeMode.Expand @@ -122,6 +123,7 @@ val RecursiveViewFactory = composedViewFactory { rendering, viewEnvir } } +@OptIn(ExperimentalLayout::class) @Composable private fun Buttons( onAdd: () -> Unit, onReset: () -> Unit From 693ba9917b2b70cb74c3ece37217c72a98c84aca Mon Sep 17 00:00:00 2001 From: Zach Klippenstein Date: Tue, 28 Jul 2020 14:35:01 -0700 Subject: [PATCH 40/67] Remove "input won't work" disclaimer from legacy view Input works now. --- samples/src/main/res/layout/legacy_view.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/src/main/res/layout/legacy_view.xml b/samples/src/main/res/layout/legacy_view.xml index 5ce02df3cf..e7afd62130 100644 --- a/samples/src/main/res/layout/legacy_view.xml +++ b/samples/src/main/res/layout/legacy_view.xml @@ -23,7 +23,7 @@ From f95f0d16e7a4c6408a61d857d3827af347d0ee4d Mon Sep 17 00:00:00 2001 From: Zach Klippenstein Date: Wed, 5 Aug 2020 10:43:57 -0700 Subject: [PATCH 41/67] Update AGP to alpha07. --- buildSrc/src/main/java/Dependencies.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt index 2163fc5beb..5cc28836df 100644 --- a/buildSrc/src/main/java/Dependencies.kt +++ b/buildSrc/src/main/java/Dependencies.kt @@ -27,7 +27,7 @@ object Versions { @Suppress("unused") object Dependencies { - const val android_gradle_plugin = "com.android.tools.build:gradle:4.2.0-alpha05" + const val android_gradle_plugin = "com.android.tools.build:gradle:4.2.0-alpha07" object AndroidX { const val appcompat = "androidx.appcompat:appcompat:1.1.0" From 2a344c7c07fc268d84078c1d456b43822691f426 Mon Sep 17 00:00:00 2001 From: Zach Klippenstein Date: Wed, 5 Aug 2020 11:22:15 -0700 Subject: [PATCH 42/67] Update Compose version to dev16 and Kotlin 1.4.0-rc. --- .buildscript/configure-compose.gradle | 2 +- buildSrc/src/main/java/Dependencies.kt | 4 +- compose-tooling/api/compose-tooling.api | 4 +- .../tooling/PreviewComposeWorkflowTest.kt | 12 ++-- .../compose/tooling/PreviewViewFactoryTest.kt | 12 ++-- .../ui/compose/tooling/ComposeWorkflows.kt | 8 +-- .../compose/tooling/PlaceholderViewFactory.kt | 42 +++++------ .../compose/tooling/PreviewViewEnvironment.kt | 8 +-- .../ui/compose/tooling/ViewFactories.kt | 4 +- core-compose/api/core-compose.api | 20 +++--- .../ui/compose/ComposeViewFactoryTest.kt | 8 +-- .../ui/compose/CompositionRootTest.kt | 6 +- .../workflow/ui/compose/RenderAsStateTest.kt | 8 +-- .../ui/compose/ViewEnvironmentsTest.kt | 4 +- .../ui/compose/WorkflowContainerTest.kt | 2 +- .../ui/compose/internal/ViewFactoriesTest.kt | 7 +- .../workflow/ui/compose/ComposeRendering.kt | 2 +- .../workflow/ui/compose/ComposeViewFactory.kt | 10 +-- .../workflow/ui/compose/ComposeWorkflow.kt | 2 +- .../workflow/ui/compose/CompositionRoot.kt | 8 +-- .../workflow/ui/compose/RenderAsState.kt | 23 +++--- .../workflow/ui/compose/ViewEnvironments.kt | 6 +- .../workflow/ui/compose/WorkflowContainer.kt | 6 +- .../compose/internal/ComposeWorkflowImpl.kt | 8 +-- .../ui/compose/internal/ParentComposition.kt | 2 +- .../ui/compose/internal/ViewFactories.kt | 12 ++-- .../hellocomposebinding/HelloBindingTest.kt | 4 +- .../sample/launcher/SampleLauncherTest.kt | 4 +- .../nestedrenderings/NestedRenderingsTest.kt | 4 +- .../com/squareup/sample/hellocompose/App.kt | 14 ++-- .../sample/hellocompose/HelloBinding.kt | 10 +-- .../hellocompose/HelloComposeActivity.kt | 2 +- .../hellocomposebinding/HelloBinding.kt | 14 ++-- .../HelloBindingActivity.kt | 2 +- .../sample/launcher/SampleLauncherActivity.kt | 2 +- .../sample/launcher/SampleLauncherApp.kt | 70 +++++++++---------- .../com/squareup/sample/launcher/Samples.kt | 2 +- .../sample/nestedrenderings/LegacyRunner.kt | 6 +- .../NestedRenderingsActivity.kt | 4 +- .../nestedrenderings/RecursiveViewFactory.kt | 42 +++++------ 40 files changed, 205 insertions(+), 205 deletions(-) diff --git a/.buildscript/configure-compose.gradle b/.buildscript/configure-compose.gradle index cebfa2ec45..456b1f2e41 100644 --- a/.buildscript/configure-compose.gradle +++ b/.buildscript/configure-compose.gradle @@ -19,7 +19,7 @@ android { compose true } composeOptions { - kotlinCompilerVersion "1.4.0-dev-withExperimentalGoogleExtensions-20200720" + kotlinCompilerVersion "1.4.0-rc" kotlinCompilerExtensionVersion Versions.compose } } diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt index 5cc28836df..ea66f88a65 100644 --- a/buildSrc/src/main/java/Dependencies.kt +++ b/buildSrc/src/main/java/Dependencies.kt @@ -19,8 +19,8 @@ import java.util.Locale.US import kotlin.reflect.full.declaredMembers object Versions { - const val compose = "0.1.0-dev15" - const val kotlin = "1.4-M3" + const val compose = "0.1.0-dev16" + const val kotlin = "1.4.0-rc" const val targetSdk = 29 const val workflow = "0.28.0" } diff --git a/compose-tooling/api/compose-tooling.api b/compose-tooling/api/compose-tooling.api index 6af39e7136..5ee0becca1 100644 --- a/compose-tooling/api/compose-tooling.api +++ b/compose-tooling/api/compose-tooling.api @@ -6,10 +6,10 @@ public final class com/squareup/workflow/ui/compose/tooling/BuildConfig { } public final class com/squareup/workflow/ui/compose/tooling/ComposeWorkflowsKt { - public static final fun preview (Lcom/squareup/workflow/ui/compose/ComposeWorkflow;Ljava/lang/Object;Landroidx/ui/core/Modifier;Landroidx/ui/core/Modifier;Lkotlin/jvm/functions/Function1;Landroidx/compose/Composer;III)V + public static final fun preview (Lcom/squareup/workflow/ui/compose/ComposeWorkflow;Ljava/lang/Object;Landroidx/compose/ui/Modifier;Landroidx/compose/ui/Modifier;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;III)V } public final class com/squareup/workflow/ui/compose/tooling/ViewFactoriesKt { - public static final fun preview (Lcom/squareup/workflow/ui/ViewFactory;Ljava/lang/Object;Landroidx/ui/core/Modifier;Landroidx/ui/core/Modifier;Lkotlin/jvm/functions/Function1;Landroidx/compose/Composer;III)V + public static final fun preview (Lcom/squareup/workflow/ui/ViewFactory;Ljava/lang/Object;Landroidx/compose/ui/Modifier;Landroidx/compose/ui/Modifier;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;III)V } diff --git a/compose-tooling/src/androidTest/java/com/squareup/workflow/ui/compose/tooling/PreviewComposeWorkflowTest.kt b/compose-tooling/src/androidTest/java/com/squareup/workflow/ui/compose/tooling/PreviewComposeWorkflowTest.kt index 0e343a5c39..8de67a0810 100644 --- a/compose-tooling/src/androidTest/java/com/squareup/workflow/ui/compose/tooling/PreviewComposeWorkflowTest.kt +++ b/compose-tooling/src/androidTest/java/com/squareup/workflow/ui/compose/tooling/PreviewComposeWorkflowTest.kt @@ -17,18 +17,18 @@ package com.squareup.workflow.ui.compose.tooling -import androidx.compose.Composable +import androidx.compose.foundation.Text +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.size +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.ui.core.Modifier -import androidx.ui.foundation.Text -import androidx.ui.layout.Column -import androidx.ui.layout.size import androidx.ui.test.assertIsDisplayed import androidx.ui.test.assertIsNotDisplayed import androidx.ui.test.createComposeRule import androidx.ui.test.onNodeWithText import androidx.ui.tooling.preview.Preview -import androidx.ui.unit.dp import com.squareup.workflow.Workflow import com.squareup.workflow.ui.ViewEnvironmentKey import com.squareup.workflow.ui.compose.WorkflowRendering diff --git a/compose-tooling/src/androidTest/java/com/squareup/workflow/ui/compose/tooling/PreviewViewFactoryTest.kt b/compose-tooling/src/androidTest/java/com/squareup/workflow/ui/compose/tooling/PreviewViewFactoryTest.kt index 238e954c37..c0a585a91a 100644 --- a/compose-tooling/src/androidTest/java/com/squareup/workflow/ui/compose/tooling/PreviewViewFactoryTest.kt +++ b/compose-tooling/src/androidTest/java/com/squareup/workflow/ui/compose/tooling/PreviewViewFactoryTest.kt @@ -17,18 +17,18 @@ package com.squareup.workflow.ui.compose.tooling -import androidx.compose.Composable +import androidx.compose.foundation.Text +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.size +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.ui.core.Modifier -import androidx.ui.foundation.Text -import androidx.ui.layout.Column -import androidx.ui.layout.size import androidx.ui.test.assertIsDisplayed import androidx.ui.test.assertIsNotDisplayed import androidx.ui.test.createComposeRule import androidx.ui.test.onNodeWithText import androidx.ui.tooling.preview.Preview -import androidx.ui.unit.dp import com.squareup.workflow.ui.ViewEnvironmentKey import com.squareup.workflow.ui.compose.WorkflowRendering import com.squareup.workflow.ui.compose.composedViewFactory diff --git a/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/ComposeWorkflows.kt b/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/ComposeWorkflows.kt index 97be59805e..ededfb98a0 100644 --- a/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/ComposeWorkflows.kt +++ b/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/ComposeWorkflows.kt @@ -17,14 +17,14 @@ package com.squareup.workflow.ui.compose.tooling -import androidx.compose.Composable -import androidx.ui.core.Modifier -import androidx.ui.foundation.Box +import androidx.compose.foundation.Box +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier import com.squareup.workflow.Sink -import com.squareup.workflow.ui.compose.ComposeWorkflow import com.squareup.workflow.ui.ViewEnvironment import com.squareup.workflow.ui.ViewFactory import com.squareup.workflow.ui.ViewRegistry +import com.squareup.workflow.ui.compose.ComposeWorkflow /** * Draws this [ComposeWorkflow] using a special preview [ViewRegistry]. diff --git a/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/PlaceholderViewFactory.kt b/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/PlaceholderViewFactory.kt index 2278801c9c..c64260fd6c 100644 --- a/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/PlaceholderViewFactory.kt +++ b/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/PlaceholderViewFactory.kt @@ -17,28 +17,28 @@ package com.squareup.workflow.ui.compose.tooling -import androidx.compose.Composable -import androidx.ui.core.Modifier -import androidx.ui.core.clipToBounds -import androidx.ui.core.drawBehind -import androidx.ui.foundation.Box -import androidx.ui.foundation.Text -import androidx.ui.foundation.drawBorder -import androidx.ui.geometry.Offset -import androidx.ui.geometry.toRect -import androidx.ui.graphics.Color -import androidx.ui.graphics.Paint -import androidx.ui.graphics.Shadow -import androidx.ui.graphics.drawscope.DrawScope -import androidx.ui.graphics.drawscope.drawCanvas -import androidx.ui.graphics.drawscope.rotate -import androidx.ui.graphics.withSaveLayer -import androidx.ui.layout.fillMaxSize -import androidx.ui.text.TextStyle -import androidx.ui.text.style.TextAlign +import androidx.compose.foundation.Box +import androidx.compose.foundation.Text +import androidx.compose.foundation.drawBorder +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clipToBounds +import androidx.compose.ui.drawBehind +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.toRect +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Paint +import androidx.compose.ui.graphics.Shadow +import androidx.compose.ui.graphics.drawscope.DrawScope +import androidx.compose.ui.graphics.drawscope.drawCanvas +import androidx.compose.ui.graphics.drawscope.rotate +import androidx.compose.ui.graphics.withSaveLayer +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp import androidx.ui.tooling.preview.Preview -import androidx.ui.unit.Dp -import androidx.ui.unit.dp import com.squareup.workflow.ui.ViewFactory import com.squareup.workflow.ui.compose.composedViewFactory diff --git a/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/PreviewViewEnvironment.kt b/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/PreviewViewEnvironment.kt index 7693e8b53f..388a0bc417 100644 --- a/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/PreviewViewEnvironment.kt +++ b/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/PreviewViewEnvironment.kt @@ -15,10 +15,10 @@ */ package com.squareup.workflow.ui.compose.tooling -import androidx.compose.Composable -import androidx.compose.Immutable -import androidx.compose.remember -import androidx.ui.core.Modifier +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier import com.squareup.workflow.ui.ViewEnvironment import com.squareup.workflow.ui.ViewFactory import com.squareup.workflow.ui.ViewRegistry diff --git a/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/ViewFactories.kt b/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/ViewFactories.kt index fe9f55a228..be858a94e2 100644 --- a/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/ViewFactories.kt +++ b/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/ViewFactories.kt @@ -17,8 +17,8 @@ package com.squareup.workflow.ui.compose.tooling -import androidx.compose.Composable -import androidx.ui.core.Modifier +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier import com.squareup.workflow.ui.ViewEnvironment import com.squareup.workflow.ui.ViewFactory import com.squareup.workflow.ui.ViewRegistry diff --git a/core-compose/api/core-compose.api b/core-compose/api/core-compose.api index 9267d5d467..80fa316f48 100644 --- a/core-compose/api/core-compose.api +++ b/core-compose/api/core-compose.api @@ -17,7 +17,7 @@ public final class com/squareup/workflow/ui/compose/ComposeViewFactory : com/squ public abstract class com/squareup/workflow/ui/compose/ComposeWorkflow : com/squareup/workflow/Workflow { public fun ()V public fun asStatefulWorkflow ()Lcom/squareup/workflow/StatefulWorkflow; - public abstract fun render (Ljava/lang/Object;Lcom/squareup/workflow/Sink;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/compose/Composer;II)V + public abstract fun render (Ljava/lang/Object;Lcom/squareup/workflow/Sink;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/compose/runtime/Composer;II)V } public final class com/squareup/workflow/ui/compose/ComposeWorkflowKt { @@ -30,21 +30,21 @@ public final class com/squareup/workflow/ui/compose/CompositionRootKt { } public final class com/squareup/workflow/ui/compose/RenderAsStateKt { - public static final fun renderAsState (Lcom/squareup/workflow/Workflow;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;III)Landroidx/compose/State; - public static final fun renderAsState (Lcom/squareup/workflow/Workflow;Ljava/lang/Object;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;III)Landroidx/compose/State; - public static final fun renderAsState (Lcom/squareup/workflow/Workflow;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;III)Landroidx/compose/State; - public static final fun renderAsState (Lcom/squareup/workflow/Workflow;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;III)Landroidx/compose/State; + public static final fun renderAsState (Lcom/squareup/workflow/Workflow;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/runtime/Composer;III)Landroidx/compose/runtime/State; + public static final fun renderAsState (Lcom/squareup/workflow/Workflow;Ljava/lang/Object;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/runtime/Composer;III)Landroidx/compose/runtime/State; + public static final fun renderAsState (Lcom/squareup/workflow/Workflow;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/runtime/Composer;III)Landroidx/compose/runtime/State; + public static final fun renderAsState (Lcom/squareup/workflow/Workflow;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/runtime/Composer;III)Landroidx/compose/runtime/State; } public final class com/squareup/workflow/ui/compose/ViewEnvironmentsKt { - public static final fun WorkflowRendering (Ljava/lang/Object;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/ui/core/Modifier;Landroidx/compose/Composer;III)V + public static final fun WorkflowRendering (Ljava/lang/Object;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/compose/ui/Modifier;Landroidx/compose/runtime/Composer;III)V } public final class com/squareup/workflow/ui/compose/WorkflowContainerKt { - public static final fun WorkflowContainer (Lcom/squareup/workflow/Workflow;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;III)V - public static final fun WorkflowContainer (Lcom/squareup/workflow/Workflow;Ljava/lang/Object;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;III)V - public static final fun WorkflowContainer (Lcom/squareup/workflow/Workflow;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;III)V - public static final fun WorkflowContainer (Lcom/squareup/workflow/Workflow;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/ui/core/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/Composer;III)V + public static final fun WorkflowContainer (Lcom/squareup/workflow/Workflow;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/compose/ui/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/runtime/Composer;III)V + public static final fun WorkflowContainer (Lcom/squareup/workflow/Workflow;Ljava/lang/Object;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/compose/ui/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/runtime/Composer;III)V + public static final fun WorkflowContainer (Lcom/squareup/workflow/Workflow;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/compose/ui/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/runtime/Composer;III)V + public static final fun WorkflowContainer (Lcom/squareup/workflow/Workflow;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/compose/ui/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/runtime/Composer;III)V } public final class com/squareup/workflow/ui/core/compose/BuildConfig { diff --git a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/ComposeViewFactoryTest.kt b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/ComposeViewFactoryTest.kt index a6e02c8895..e66367e67f 100644 --- a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/ComposeViewFactoryTest.kt +++ b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/ComposeViewFactoryTest.kt @@ -17,13 +17,13 @@ package com.squareup.workflow.ui.compose import android.content.Context import android.widget.FrameLayout -import androidx.compose.mutableStateOf +import androidx.compose.foundation.Text +import androidx.compose.foundation.layout.Column +import androidx.compose.runtime.mutableStateOf +import androidx.compose.ui.viewinterop.emitView import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.ui.foundation.Text -import androidx.ui.layout.Column import androidx.ui.test.createComposeRule import androidx.ui.test.onNodeWithText -import androidx.ui.viewinterop.emitView import com.squareup.workflow.ui.ViewEnvironment import com.squareup.workflow.ui.ViewRegistry import com.squareup.workflow.ui.WorkflowViewStub diff --git a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/CompositionRootTest.kt b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/CompositionRootTest.kt index 3d277ed422..ec29ef7071 100644 --- a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/CompositionRootTest.kt +++ b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/CompositionRootTest.kt @@ -15,10 +15,10 @@ */ package com.squareup.workflow.ui.compose -import androidx.compose.mutableStateOf +import androidx.compose.foundation.Text +import androidx.compose.foundation.layout.Column +import androidx.compose.runtime.mutableStateOf import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.ui.foundation.Text -import androidx.ui.layout.Column import androidx.ui.test.assertIsDisplayed import androidx.ui.test.createComposeRule import androidx.ui.test.onNodeWithText diff --git a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/RenderAsStateTest.kt b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/RenderAsStateTest.kt index 4eee2dd260..172467cc03 100644 --- a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/RenderAsStateTest.kt +++ b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/RenderAsStateTest.kt @@ -17,11 +17,11 @@ package com.squareup.workflow.ui.compose -import androidx.compose.Providers -import androidx.compose.mutableStateOf +import androidx.compose.runtime.Providers +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.savedinstancestate.UiSavedStateRegistry +import androidx.compose.runtime.savedinstancestate.UiSavedStateRegistryAmbient import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.ui.savedinstancestate.UiSavedStateRegistry -import androidx.ui.savedinstancestate.UiSavedStateRegistryAmbient import androidx.ui.test.createComposeRule import androidx.ui.test.runOnIdle import androidx.ui.test.waitForIdle diff --git a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/ViewEnvironmentsTest.kt b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/ViewEnvironmentsTest.kt index e1dbf5b9f1..71c4d540c1 100644 --- a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/ViewEnvironmentsTest.kt +++ b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/ViewEnvironmentsTest.kt @@ -15,9 +15,9 @@ */ package com.squareup.workflow.ui.compose -import androidx.compose.mutableStateOf +import androidx.compose.foundation.Text +import androidx.compose.runtime.mutableStateOf import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.ui.foundation.Text import androidx.ui.test.assertIsDisplayed import androidx.ui.test.createComposeRule import androidx.ui.test.onNodeWithText diff --git a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/WorkflowContainerTest.kt b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/WorkflowContainerTest.kt index 5f0ad201f1..69bce0ca4e 100644 --- a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/WorkflowContainerTest.kt +++ b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/WorkflowContainerTest.kt @@ -17,8 +17,8 @@ package com.squareup.workflow.ui.compose +import androidx.compose.foundation.Text import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.ui.foundation.Text import androidx.ui.test.assertIsDisplayed import androidx.ui.test.createComposeRule import androidx.ui.test.onNodeWithText diff --git a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/internal/ViewFactoriesTest.kt b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/internal/ViewFactoriesTest.kt index 66ad184207..9352f68193 100644 --- a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/internal/ViewFactoriesTest.kt +++ b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/internal/ViewFactoriesTest.kt @@ -19,14 +19,15 @@ import android.content.Context import android.view.View import android.view.ViewGroup import android.widget.TextView -import androidx.compose.mutableStateOf +import androidx.compose.foundation.Text +import androidx.compose.foundation.layout.Column +import androidx.compose.runtime.mutableStateOf + import androidx.test.espresso.Espresso.onView import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.matcher.ViewMatchers.isDisplayed import androidx.test.espresso.matcher.ViewMatchers.withText import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.ui.foundation.Text -import androidx.ui.layout.Column import androidx.ui.test.assertIsDisplayed import androidx.ui.test.createComposeRule import androidx.ui.test.onNodeWithText diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeRendering.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeRendering.kt index 7e1b97ce89..9cc2308beb 100644 --- a/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeRendering.kt +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeRendering.kt @@ -17,7 +17,7 @@ package com.squareup.workflow.ui.compose -import androidx.compose.Composable +import androidx.compose.runtime.Composable import com.squareup.workflow.ui.compose.ComposeRendering.Companion.Factory import com.squareup.workflow.ui.compose.ComposeRendering.Companion.NoopRendering import com.squareup.workflow.ui.ViewEnvironment diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactory.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactory.kt index 65bd62889e..65e52cdb9b 100644 --- a/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactory.kt +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactory.kt @@ -22,11 +22,11 @@ import android.content.Context import android.view.View import android.view.ViewGroup import android.widget.FrameLayout -import androidx.compose.Composable -import androidx.compose.ExperimentalComposeApi -import androidx.compose.Recomposer -import androidx.compose.mutableStateOf -import androidx.ui.core.setContent +import androidx.compose.runtime.Composable +import androidx.compose.runtime.ExperimentalComposeApi +import androidx.compose.runtime.Recomposer +import androidx.compose.runtime.mutableStateOf +import androidx.compose.ui.platform.setContent import com.squareup.workflow.ui.ViewEnvironment import com.squareup.workflow.ui.ViewFactory import com.squareup.workflow.ui.bindShowRendering diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeWorkflow.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeWorkflow.kt index 0af19c3f64..6fa52f351a 100644 --- a/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeWorkflow.kt +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeWorkflow.kt @@ -17,7 +17,7 @@ package com.squareup.workflow.ui.compose -import androidx.compose.Composable +import androidx.compose.runtime.Composable import com.squareup.workflow.Sink import com.squareup.workflow.StatefulWorkflow import com.squareup.workflow.Workflow diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/CompositionRoot.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/CompositionRoot.kt index fb9ca861dd..2576a0176f 100644 --- a/core-compose/src/main/java/com/squareup/workflow/ui/compose/CompositionRoot.kt +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/CompositionRoot.kt @@ -19,10 +19,10 @@ package com.squareup.workflow.ui.compose import androidx.annotation.VisibleForTesting import androidx.annotation.VisibleForTesting.PRIVATE -import androidx.compose.Composable -import androidx.compose.Providers -import androidx.compose.remember -import androidx.compose.staticAmbientOf +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Providers +import androidx.compose.runtime.remember +import androidx.compose.runtime.staticAmbientOf import com.squareup.workflow.ui.ViewEnvironment import com.squareup.workflow.ui.ViewRegistry import com.squareup.workflow.ui.compose.internal.mapFactories diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/RenderAsState.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/RenderAsState.kt index 2ca6218167..8721781b4b 100644 --- a/core-compose/src/main/java/com/squareup/workflow/ui/compose/RenderAsState.kt +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/RenderAsState.kt @@ -18,18 +18,17 @@ package com.squareup.workflow.ui.compose import androidx.annotation.VisibleForTesting -import androidx.compose.Composable -import androidx.compose.CompositionLifecycleObserver -import androidx.compose.MutableState -import androidx.compose.State -import androidx.compose.mutableStateOf -import androidx.compose.remember -import androidx.ui.core.CoroutineContextAmbient -import androidx.ui.core.Ref -import androidx.ui.savedinstancestate.Saver -import androidx.ui.savedinstancestate.SaverScope -import androidx.ui.savedinstancestate.UiSavedStateRegistryAmbient -import androidx.ui.savedinstancestate.savedInstanceState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLifecycleObserver +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.State +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.savedinstancestate.Saver +import androidx.compose.runtime.savedinstancestate.SaverScope +import androidx.compose.runtime.savedinstancestate.savedInstanceState +import androidx.compose.ui.node.Ref +import androidx.compose.ui.platform.CoroutineContextAmbient import com.squareup.workflow.Snapshot import com.squareup.workflow.Workflow import com.squareup.workflow.diagnostic.WorkflowDiagnosticListener diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/ViewEnvironments.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/ViewEnvironments.kt index d9bb433ca8..1847655083 100644 --- a/core-compose/src/main/java/com/squareup/workflow/ui/compose/ViewEnvironments.kt +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/ViewEnvironments.kt @@ -15,9 +15,9 @@ */ package com.squareup.workflow.ui.compose -import androidx.compose.Composable -import androidx.compose.remember -import androidx.ui.core.Modifier +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier import com.squareup.workflow.ui.ViewEnvironment import com.squareup.workflow.ui.ViewRegistry import com.squareup.workflow.ui.compose.internal.WorkflowRendering diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/WorkflowContainer.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/WorkflowContainer.kt index 908864a6f8..9b82e67178 100644 --- a/core-compose/src/main/java/com/squareup/workflow/ui/compose/WorkflowContainer.kt +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/WorkflowContainer.kt @@ -20,9 +20,9 @@ package com.squareup.workflow.ui.compose -import androidx.compose.Composable -import androidx.compose.remember -import androidx.ui.core.Modifier +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier import com.squareup.workflow.Snapshot import com.squareup.workflow.Workflow import com.squareup.workflow.diagnostic.WorkflowDiagnosticListener diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ComposeWorkflowImpl.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ComposeWorkflowImpl.kt index e57a2c2667..7e59a076a4 100644 --- a/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ComposeWorkflowImpl.kt +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ComposeWorkflowImpl.kt @@ -15,18 +15,18 @@ */ package com.squareup.workflow.ui.compose.internal -import androidx.compose.MutableState -import androidx.compose.mutableStateOf -import androidx.compose.structuralEqualityPolicy +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.structuralEqualityPolicy import com.squareup.workflow.RenderContext import com.squareup.workflow.Sink import com.squareup.workflow.Snapshot import com.squareup.workflow.StatefulWorkflow import com.squareup.workflow.action -import com.squareup.workflow.ui.compose.internal.ComposeWorkflowImpl.State import com.squareup.workflow.contraMap import com.squareup.workflow.ui.compose.ComposeRendering import com.squareup.workflow.ui.compose.ComposeWorkflow +import com.squareup.workflow.ui.compose.internal.ComposeWorkflowImpl.State internal class ComposeWorkflowImpl( private val workflow: ComposeWorkflow diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ParentComposition.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ParentComposition.kt index edec23b4db..247ca15fc9 100644 --- a/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ParentComposition.kt +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ParentComposition.kt @@ -17,7 +17,7 @@ package com.squareup.workflow.ui.compose.internal -import androidx.compose.CompositionReference +import androidx.compose.runtime.CompositionReference import com.squareup.workflow.ui.ViewEnvironment import com.squareup.workflow.ui.ViewEnvironmentKey diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ViewFactories.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ViewFactories.kt index da9769e2f4..b850fe9ebf 100644 --- a/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ViewFactories.kt +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ViewFactories.kt @@ -19,12 +19,12 @@ import android.content.Context import android.view.View import android.view.ViewGroup.LayoutParams.MATCH_PARENT import android.widget.FrameLayout -import androidx.compose.Composable -import androidx.compose.compositionReference -import androidx.compose.remember -import androidx.ui.core.Modifier -import androidx.ui.foundation.Box -import androidx.ui.viewinterop.emitView +import androidx.compose.foundation.Box +import androidx.compose.runtime.Composable +import androidx.compose.runtime.compositionReference +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.viewinterop.emitView import com.squareup.workflow.ui.ViewEnvironment import com.squareup.workflow.ui.ViewFactory import com.squareup.workflow.ui.canShowRendering diff --git a/samples/src/androidTest/java/com/squareup/sample/hellocomposebinding/HelloBindingTest.kt b/samples/src/androidTest/java/com/squareup/sample/hellocomposebinding/HelloBindingTest.kt index 5362545f0c..14c986acaf 100644 --- a/samples/src/androidTest/java/com/squareup/sample/hellocomposebinding/HelloBindingTest.kt +++ b/samples/src/androidTest/java/com/squareup/sample/hellocomposebinding/HelloBindingTest.kt @@ -16,7 +16,7 @@ package com.squareup.sample.hellocomposebinding import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.ui.test.android.AndroidComposeTestRule +import androidx.ui.test.android.createAndroidComposeRule import androidx.ui.test.assertIsDisplayed import androidx.ui.test.onNodeWithText import androidx.ui.test.performClick @@ -28,7 +28,7 @@ import org.junit.runner.RunWith class HelloBindingTest { // Launches the activity. - @Rule @JvmField val composeRule = AndroidComposeTestRule() + @Rule @JvmField val composeRule = createAndroidComposeRule() @Test fun togglesBetweenStates() { onNodeWithText("Hello") diff --git a/samples/src/androidTest/java/com/squareup/sample/launcher/SampleLauncherTest.kt b/samples/src/androidTest/java/com/squareup/sample/launcher/SampleLauncherTest.kt index 60faf2b34d..8d06d300a8 100644 --- a/samples/src/androidTest/java/com/squareup/sample/launcher/SampleLauncherTest.kt +++ b/samples/src/androidTest/java/com/squareup/sample/launcher/SampleLauncherTest.kt @@ -18,7 +18,7 @@ package com.squareup.sample.launcher import androidx.test.espresso.Espresso.pressBack import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry -import androidx.ui.test.android.AndroidComposeTestRule +import androidx.ui.test.android.createAndroidComposeRule import androidx.ui.test.assertIsDisplayed import androidx.ui.test.onNodeWithText import androidx.ui.test.performClick @@ -30,7 +30,7 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class SampleLauncherTest { - @Rule @JvmField val composeRule = AndroidComposeTestRule() + @Rule @JvmField val composeRule = createAndroidComposeRule() @Test fun allSamplesLaunch() { val appName = diff --git a/samples/src/androidTest/java/com/squareup/sample/nestedrenderings/NestedRenderingsTest.kt b/samples/src/androidTest/java/com/squareup/sample/nestedrenderings/NestedRenderingsTest.kt index 139c26fc03..daa8bab5c0 100644 --- a/samples/src/androidTest/java/com/squareup/sample/nestedrenderings/NestedRenderingsTest.kt +++ b/samples/src/androidTest/java/com/squareup/sample/nestedrenderings/NestedRenderingsTest.kt @@ -18,7 +18,7 @@ package com.squareup.sample.nestedrenderings import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.ui.test.SemanticsNodeInteraction import androidx.ui.test.SemanticsNodeInteractionCollection -import androidx.ui.test.android.AndroidComposeTestRule +import androidx.ui.test.android.createAndroidComposeRule import androidx.ui.test.assertCountEquals import androidx.ui.test.assertIsDisplayed import androidx.ui.test.onAllNodesWithText @@ -34,7 +34,7 @@ private const val ADD_BUTTON_TEXT = "Add Child" class NestedRenderingsTest { // Launches the activity. - @Rule @JvmField val composeRule = AndroidComposeTestRule() + @Rule @JvmField val composeRule = createAndroidComposeRule() @Test fun childrenAreAddedAndRemoved() { onNodeWithText(ADD_BUTTON_TEXT) diff --git a/samples/src/main/java/com/squareup/sample/hellocompose/App.kt b/samples/src/main/java/com/squareup/sample/hellocompose/App.kt index da73f86146..324e5e8518 100644 --- a/samples/src/main/java/com/squareup/sample/hellocompose/App.kt +++ b/samples/src/main/java/com/squareup/sample/hellocompose/App.kt @@ -15,14 +15,14 @@ */ package com.squareup.sample.hellocompose -import androidx.compose.Composable -import androidx.ui.core.Modifier -import androidx.ui.foundation.drawBorder -import androidx.ui.foundation.shape.corner.RoundedCornerShape -import androidx.ui.graphics.Color -import androidx.ui.material.MaterialTheme +import androidx.compose.foundation.drawBorder +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp import androidx.ui.tooling.preview.Preview -import androidx.ui.unit.dp import com.squareup.workflow.diagnostic.SimpleLoggingDiagnosticListener import com.squareup.workflow.ui.ViewEnvironment import com.squareup.workflow.ui.ViewRegistry diff --git a/samples/src/main/java/com/squareup/sample/hellocompose/HelloBinding.kt b/samples/src/main/java/com/squareup/sample/hellocompose/HelloBinding.kt index 10c3c9adbc..11bb358cc8 100644 --- a/samples/src/main/java/com/squareup/sample/hellocompose/HelloBinding.kt +++ b/samples/src/main/java/com/squareup/sample/hellocompose/HelloBinding.kt @@ -15,11 +15,11 @@ */ package com.squareup.sample.hellocompose -import androidx.ui.core.Alignment -import androidx.ui.core.Modifier -import androidx.ui.foundation.Text -import androidx.ui.foundation.clickable -import androidx.ui.layout.wrapContentSize +import androidx.compose.foundation.Text +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier import com.squareup.sample.hellocompose.HelloWorkflow.Rendering import com.squareup.workflow.ui.compose.composedViewFactory diff --git a/samples/src/main/java/com/squareup/sample/hellocompose/HelloComposeActivity.kt b/samples/src/main/java/com/squareup/sample/hellocompose/HelloComposeActivity.kt index 2f9058bc24..67614a2da9 100644 --- a/samples/src/main/java/com/squareup/sample/hellocompose/HelloComposeActivity.kt +++ b/samples/src/main/java/com/squareup/sample/hellocompose/HelloComposeActivity.kt @@ -17,7 +17,7 @@ package com.squareup.sample.hellocompose import android.os.Bundle import androidx.appcompat.app.AppCompatActivity -import androidx.ui.core.setContent +import androidx.compose.ui.platform.setContent class HelloComposeActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { diff --git a/samples/src/main/java/com/squareup/sample/hellocomposebinding/HelloBinding.kt b/samples/src/main/java/com/squareup/sample/hellocomposebinding/HelloBinding.kt index 682da58162..5fd157b2f6 100644 --- a/samples/src/main/java/com/squareup/sample/hellocomposebinding/HelloBinding.kt +++ b/samples/src/main/java/com/squareup/sample/hellocomposebinding/HelloBinding.kt @@ -15,13 +15,13 @@ */ package com.squareup.sample.hellocomposebinding -import androidx.compose.Composable -import androidx.ui.core.Alignment -import androidx.ui.core.Modifier -import androidx.ui.foundation.Text -import androidx.ui.foundation.clickable -import androidx.ui.layout.fillMaxSize -import androidx.ui.layout.wrapContentSize +import androidx.compose.foundation.Text +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier import androidx.ui.tooling.preview.Preview import com.squareup.sample.hellocomposebinding.HelloWorkflow.Rendering import com.squareup.workflow.ui.compose.composedViewFactory diff --git a/samples/src/main/java/com/squareup/sample/hellocomposebinding/HelloBindingActivity.kt b/samples/src/main/java/com/squareup/sample/hellocomposebinding/HelloBindingActivity.kt index adcb5db490..0a76132539 100644 --- a/samples/src/main/java/com/squareup/sample/hellocomposebinding/HelloBindingActivity.kt +++ b/samples/src/main/java/com/squareup/sample/hellocomposebinding/HelloBindingActivity.kt @@ -17,7 +17,7 @@ package com.squareup.sample.hellocomposebinding import android.os.Bundle import androidx.appcompat.app.AppCompatActivity -import androidx.ui.material.MaterialTheme +import androidx.compose.material.MaterialTheme import com.squareup.workflow.diagnostic.SimpleLoggingDiagnosticListener import com.squareup.workflow.ui.ViewEnvironment import com.squareup.workflow.ui.ViewRegistry diff --git a/samples/src/main/java/com/squareup/sample/launcher/SampleLauncherActivity.kt b/samples/src/main/java/com/squareup/sample/launcher/SampleLauncherActivity.kt index b4839155cf..5ad90430aa 100644 --- a/samples/src/main/java/com/squareup/sample/launcher/SampleLauncherActivity.kt +++ b/samples/src/main/java/com/squareup/sample/launcher/SampleLauncherActivity.kt @@ -17,7 +17,7 @@ package com.squareup.sample.launcher import android.os.Bundle import androidx.appcompat.app.AppCompatActivity -import androidx.ui.core.setContent +import androidx.compose.ui.platform.setContent class SampleLauncherActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { diff --git a/samples/src/main/java/com/squareup/sample/launcher/SampleLauncherApp.kt b/samples/src/main/java/com/squareup/sample/launcher/SampleLauncherApp.kt index 0636600ce2..e41c171c80 100644 --- a/samples/src/main/java/com/squareup/sample/launcher/SampleLauncherApp.kt +++ b/samples/src/main/java/com/squareup/sample/launcher/SampleLauncherApp.kt @@ -18,51 +18,51 @@ package com.squareup.sample.launcher import android.content.Intent import android.os.Bundle import android.view.View -import androidx.compose.Composable -import androidx.compose.remember +import androidx.compose.foundation.Box +import androidx.compose.foundation.Text +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumnFor +import androidx.compose.material.ListItem +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Scaffold +import androidx.compose.material.Surface +import androidx.compose.material.TopAppBar +import androidx.compose.material.darkColors +import androidx.compose.material.lightColors +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.drawLayer +import androidx.compose.ui.gesture.rawPressStartGestureFilter +import androidx.compose.ui.layout.LayoutCoordinates +import androidx.compose.ui.layout.globalBounds +import androidx.compose.ui.node.Ref +import androidx.compose.ui.onPositioned +import androidx.compose.ui.platform.ConfigurationAmbient +import androidx.compose.ui.platform.PointerEventPass.PreDown +import androidx.compose.ui.platform.ViewAmbient +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.PxBounds +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.height +import androidx.compose.ui.unit.width import androidx.core.app.ActivityOptionsCompat.makeScaleUpAnimation import androidx.core.content.ContextCompat.startActivity -import androidx.ui.core.ConfigurationAmbient -import androidx.ui.core.LayoutCoordinates -import androidx.ui.core.Modifier -import androidx.ui.core.PointerEventPass.PreDown -import androidx.ui.core.Ref -import androidx.ui.core.ViewAmbient -import androidx.ui.core.drawLayer -import androidx.ui.core.gesture.rawPressStartGestureFilter -import androidx.ui.core.globalBounds -import androidx.ui.core.onPositioned -import androidx.ui.foundation.Box -import androidx.ui.foundation.Text -import androidx.ui.foundation.lazy.LazyColumnItems -import androidx.ui.layout.aspectRatio -import androidx.ui.layout.height -import androidx.ui.layout.width -import androidx.ui.material.ListItem -import androidx.ui.material.MaterialTheme -import androidx.ui.material.Scaffold -import androidx.ui.material.Surface -import androidx.ui.material.TopAppBar -import androidx.ui.material.darkColorPalette -import androidx.ui.material.lightColorPalette -import androidx.ui.res.stringResource import androidx.ui.tooling.preview.Preview -import androidx.ui.unit.PxBounds -import androidx.ui.unit.dp -import androidx.ui.unit.height -import androidx.ui.unit.width -import com.squareup.sample.R +import com.squareup.sample.R.string @Composable fun SampleLauncherApp() { - MaterialTheme(colors = darkColorPalette()) { + MaterialTheme(colors = darkColors()) { Scaffold( topBar = { TopAppBar(title = { - Text(stringResource(R.string.app_name)) + Text(stringResource(string.app_name)) }) } ) { - LazyColumnItems(samples) { sample -> + LazyColumnFor(samples) { sample -> SampleItem(sample) } } @@ -114,7 +114,7 @@ import com.squareup.sample.R .onPositioned(onPreviewCoordinates) ) { // Preview the samples with a light theme, since that's what most of them use. - MaterialTheme(lightColorPalette()) { + MaterialTheme(lightColors()) { Surface { Box( modifier = Modifier diff --git a/samples/src/main/java/com/squareup/sample/launcher/Samples.kt b/samples/src/main/java/com/squareup/sample/launcher/Samples.kt index 678ce90966..93633bad51 100644 --- a/samples/src/main/java/com/squareup/sample/launcher/Samples.kt +++ b/samples/src/main/java/com/squareup/sample/launcher/Samples.kt @@ -18,7 +18,7 @@ package com.squareup.sample.launcher import androidx.activity.ComponentActivity -import androidx.compose.Composable +import androidx.compose.runtime.Composable import com.squareup.sample.hellocompose.App import com.squareup.sample.hellocompose.HelloComposeActivity import com.squareup.sample.hellocomposebinding.DrawHelloRenderingPreview diff --git a/samples/src/main/java/com/squareup/sample/nestedrenderings/LegacyRunner.kt b/samples/src/main/java/com/squareup/sample/nestedrenderings/LegacyRunner.kt index d06a316f28..0bd6995986 100644 --- a/samples/src/main/java/com/squareup/sample/nestedrenderings/LegacyRunner.kt +++ b/samples/src/main/java/com/squareup/sample/nestedrenderings/LegacyRunner.kt @@ -15,9 +15,9 @@ */ package com.squareup.sample.nestedrenderings -import androidx.compose.Composable -import androidx.ui.core.Modifier -import androidx.ui.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier import androidx.ui.tooling.preview.Preview import com.squareup.sample.databinding.LegacyViewBinding import com.squareup.sample.nestedrenderings.RecursiveWorkflow.LegacyRendering diff --git a/samples/src/main/java/com/squareup/sample/nestedrenderings/NestedRenderingsActivity.kt b/samples/src/main/java/com/squareup/sample/nestedrenderings/NestedRenderingsActivity.kt index 210d9df2e7..e331e05425 100644 --- a/samples/src/main/java/com/squareup/sample/nestedrenderings/NestedRenderingsActivity.kt +++ b/samples/src/main/java/com/squareup/sample/nestedrenderings/NestedRenderingsActivity.kt @@ -17,8 +17,8 @@ package com.squareup.sample.nestedrenderings import android.os.Bundle import androidx.appcompat.app.AppCompatActivity -import androidx.compose.Providers -import androidx.ui.graphics.Color +import androidx.compose.runtime.Providers +import androidx.compose.ui.graphics.Color import com.squareup.workflow.diagnostic.SimpleLoggingDiagnosticListener import com.squareup.workflow.ui.ViewEnvironment import com.squareup.workflow.ui.ViewRegistry diff --git a/samples/src/main/java/com/squareup/sample/nestedrenderings/RecursiveViewFactory.kt b/samples/src/main/java/com/squareup/sample/nestedrenderings/RecursiveViewFactory.kt index 9e0963e116..6b741818f8 100644 --- a/samples/src/main/java/com/squareup/sample/nestedrenderings/RecursiveViewFactory.kt +++ b/samples/src/main/java/com/squareup/sample/nestedrenderings/RecursiveViewFactory.kt @@ -17,32 +17,32 @@ package com.squareup.sample.nestedrenderings -import androidx.compose.Composable -import androidx.compose.Providers -import androidx.compose.ambientOf -import androidx.compose.remember -import androidx.ui.core.Alignment.Companion.CenterHorizontally -import androidx.ui.core.Modifier -import androidx.ui.foundation.Text -import androidx.ui.graphics.Color -import androidx.ui.graphics.compositeOver -import androidx.ui.layout.Arrangement.SpaceEvenly -import androidx.ui.layout.Column -import androidx.ui.layout.ExperimentalLayout -import androidx.ui.layout.FlowRow -import androidx.ui.layout.MainAxisAlignment -import androidx.ui.layout.SizeMode.Expand -import androidx.ui.layout.fillMaxSize -import androidx.ui.layout.padding -import androidx.ui.material.Button -import androidx.ui.material.Card -import androidx.ui.res.dimensionResource +import androidx.compose.foundation.Text +import androidx.compose.foundation.layout.Arrangement.SpaceEvenly +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ExperimentalLayout +import androidx.compose.foundation.layout.FlowRow +import androidx.compose.foundation.layout.MainAxisAlignment +import androidx.compose.foundation.layout.SizeMode.Expand +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material.Button +import androidx.compose.material.Card +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Providers +import androidx.compose.runtime.ambientOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment.Companion.CenterHorizontally +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.compositeOver +import androidx.compose.ui.res.dimensionResource import androidx.ui.tooling.preview.Preview import com.squareup.sample.R import com.squareup.sample.nestedrenderings.RecursiveWorkflow.Rendering import com.squareup.workflow.ui.ViewEnvironment -import com.squareup.workflow.ui.compose.composedViewFactory import com.squareup.workflow.ui.compose.WorkflowRendering +import com.squareup.workflow.ui.compose.composedViewFactory import com.squareup.workflow.ui.compose.tooling.preview /** From e32aa950626493cf31003c12bb079ab78011f0cb Mon Sep 17 00:00:00 2001 From: Zach Klippenstein Date: Wed, 5 Aug 2020 16:52:39 -0700 Subject: [PATCH 43/67] Re-enable the ComposeRendering sample. Verified that #42 has been fixed in dev16. Closes #42. --- .../HelloRenderingWorkflow.kt | 75 +++++++++++-------- .../hellocomposerendering/HelloWorkflow.kt | 5 +- .../com/squareup/sample/launcher/Samples.kt | 11 +-- 3 files changed, 51 insertions(+), 40 deletions(-) diff --git a/samples/src/main/java/com/squareup/sample/hellocomposerendering/HelloRenderingWorkflow.kt b/samples/src/main/java/com/squareup/sample/hellocomposerendering/HelloRenderingWorkflow.kt index ac662cda07..877d2b20e4 100644 --- a/samples/src/main/java/com/squareup/sample/hellocomposerendering/HelloRenderingWorkflow.kt +++ b/samples/src/main/java/com/squareup/sample/hellocomposerendering/HelloRenderingWorkflow.kt @@ -15,35 +15,46 @@ */ package com.squareup.sample.hellocomposerendering -// There is a bug in Compose dev12 that crashes the compiler when processing this code. -// See https://github.com/square/workflow-kotlin-compose/issues/42. -// /** -// * A [ComposeWorkflow] that is used by [HelloWorkflow] to render the screen. -// * -// * This workflow has type `Workflow`. -// */ -// object HelloRenderingWorkflow : ComposeWorkflow() { -// -// object Toggle -// -// @Composable override fun render( -// props: String, -// outputSink: Sink, -// viewEnvironment: ViewEnvironment -// ) { -// MaterialTheme { -// Text( -// props, -// modifier = Modifier -// .ripple() -// .clickable(onClick = { outputSink.send(Toggle) }) -// .wrapContentSize(Alignment.Center) -// ) -// } -// } -// } -// -// @Preview(showBackground = true) -// @Composable fun HelloRenderingWorkflowPreview() { -// HelloRenderingWorkflow.preview(props = "hello") -// } +import androidx.compose.foundation.Text +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.material.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.ui.tooling.preview.Preview +import com.squareup.sample.hellocomposerendering.HelloRenderingWorkflow.Toggle +import com.squareup.workflow.Sink +import com.squareup.workflow.ui.ViewEnvironment +import com.squareup.workflow.ui.compose.ComposeWorkflow +import com.squareup.workflow.ui.compose.tooling.preview + +/** + * A [ComposeWorkflow] that is used by [HelloWorkflow] to render the screen. + * + * This workflow has type `Workflow`. + */ +object HelloRenderingWorkflow : ComposeWorkflow() { + + object Toggle + + @Composable override fun render( + props: String, + outputSink: Sink, + viewEnvironment: ViewEnvironment + ) { + MaterialTheme { + Text( + props, + modifier = Modifier + .clickable(onClick = { outputSink.send(Toggle) }) + .wrapContentSize(Alignment.Center) + ) + } + } +} + +@Preview(showBackground = true) +@Composable fun HelloRenderingWorkflowPreview() { + HelloRenderingWorkflow.preview(props = "hello") +} diff --git a/samples/src/main/java/com/squareup/sample/hellocomposerendering/HelloWorkflow.kt b/samples/src/main/java/com/squareup/sample/hellocomposerendering/HelloWorkflow.kt index 3374caa3b8..62f7cfba3b 100644 --- a/samples/src/main/java/com/squareup/sample/hellocomposerendering/HelloWorkflow.kt +++ b/samples/src/main/java/com/squareup/sample/hellocomposerendering/HelloWorkflow.kt @@ -50,9 +50,8 @@ object HelloWorkflow : StatefulWorkflow( props: Unit, state: State, context: RenderContext - ): ComposeRendering = ComposeRendering.NoopRendering - // See https://github.com/square/workflow-kotlin-compose/issues/42. - //context.renderChild(HelloRenderingWorkflow, state.name) { helloAction } + ): ComposeRendering = + context.renderChild(HelloRenderingWorkflow, state.name) { helloAction } override fun snapshotState(state: State): Snapshot = Snapshot.of(if (state == Hello) 1 else 0) } diff --git a/samples/src/main/java/com/squareup/sample/launcher/Samples.kt b/samples/src/main/java/com/squareup/sample/launcher/Samples.kt index 93633bad51..77daa9155a 100644 --- a/samples/src/main/java/com/squareup/sample/launcher/Samples.kt +++ b/samples/src/main/java/com/squareup/sample/launcher/Samples.kt @@ -23,6 +23,8 @@ import com.squareup.sample.hellocompose.App import com.squareup.sample.hellocompose.HelloComposeActivity import com.squareup.sample.hellocomposebinding.DrawHelloRenderingPreview import com.squareup.sample.hellocomposebinding.HelloBindingActivity +import com.squareup.sample.hellocomposerendering.HelloComposeRenderingActivity +import com.squareup.sample.hellocomposerendering.HelloRenderingWorkflowPreview import com.squareup.sample.nestedrenderings.NestedRenderingsActivity import com.squareup.sample.nestedrenderings.RecursiveViewFactoryPreview import kotlin.reflect.KClass @@ -32,11 +34,10 @@ val samples = listOf( "Hello Compose Binding", HelloBindingActivity::class, "Creates a ViewFactory using bindCompose." ) { DrawHelloRenderingPreview() }, - // Broken in dev12, see https://github.com/square/workflow-kotlin-compose/issues/42. - // Sample( - // "Hello Compose Rendering", HelloComposeRenderingActivity::class, - // "Uses ComposeWorkflow to create a workflow that draws itself." - // ) { HelloRenderingWorkflowPreview() }, + Sample( + "Hello Compose Rendering", HelloComposeRenderingActivity::class, + "Uses ComposeWorkflow to create a workflow that draws itself." + ) { HelloRenderingWorkflowPreview() }, Sample( "Hello Compose", HelloComposeActivity::class, "A pure Compose app that launches its root Workflow from inside Compose." From 3a7f1d466fe423d7cf6352d1c8e70f0a395e47f0 Mon Sep 17 00:00:00 2001 From: Zach Klippenstein Date: Wed, 5 Aug 2020 17:00:10 -0700 Subject: [PATCH 44/67] Add CODEOWNERS file. --- .github/CODEOWNERS | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000000..b47b2cc66b --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,3 @@ +# These owners will be the default owners for everything in +# the repo, unless a later match takes precedence. +* @square/mobile-foundation-android @helios175 @wardellbagby @patrickyin From dccd90a97429e12f44dfd5c31d62a4132d4a66e5 Mon Sep 17 00:00:00 2001 From: Patrick Yin Date: Wed, 5 Aug 2020 17:10:34 -0700 Subject: [PATCH 45/67] Adding License section to README.md --- README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/README.md b/README.md index da8be40653..5173043d35 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,23 @@ The `composedViewFactory` function returns a regular [`ViewFactory`][2] which ca val viewRegistry = ViewRegistry(HelloBinding) ``` +## License +``` +Copyright 2020 Square, Inc. + +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. +``` + [1]: https://developer.android.com/jetpack/compose [2]: https://square.github.io/workflow/kotlin/api/workflow/com.squareup.workflow.ui/-view-factory/ [3]: https://square.github.io/workflow/kotlin/api/workflow/com.squareup.workflow.ui/-view-registry/ From 31032478621c57e6d20c739c99d7e3008e55feab Mon Sep 17 00:00:00 2001 From: Zach Klippenstein Date: Wed, 5 Aug 2020 18:21:41 -0700 Subject: [PATCH 46/67] Add a sample with text input. --- samples/src/main/AndroidManifest.xml | 1 + .../com/squareup/sample/launcher/Samples.kt | 8 ++- .../java/com/squareup/sample/textinput/App.kt | 41 ++++++++++++ .../sample/textinput/TextInputActivity.kt | 29 ++++++++ .../sample/textinput/TextInputViewFactory.kt | 66 ++++++++++++++++++ .../sample/textinput/TextInputWorkflow.kt | 67 +++++++++++++++++++ 6 files changed, 211 insertions(+), 1 deletion(-) create mode 100644 samples/src/main/java/com/squareup/sample/textinput/App.kt create mode 100644 samples/src/main/java/com/squareup/sample/textinput/TextInputActivity.kt create mode 100644 samples/src/main/java/com/squareup/sample/textinput/TextInputViewFactory.kt create mode 100644 samples/src/main/java/com/squareup/sample/textinput/TextInputWorkflow.kt diff --git a/samples/src/main/AndroidManifest.xml b/samples/src/main/AndroidManifest.xml index bf0ed76c90..881150216a 100644 --- a/samples/src/main/AndroidManifest.xml +++ b/samples/src/main/AndroidManifest.xml @@ -34,6 +34,7 @@ + diff --git a/samples/src/main/java/com/squareup/sample/launcher/Samples.kt b/samples/src/main/java/com/squareup/sample/launcher/Samples.kt index 93633bad51..0b3031d97c 100644 --- a/samples/src/main/java/com/squareup/sample/launcher/Samples.kt +++ b/samples/src/main/java/com/squareup/sample/launcher/Samples.kt @@ -25,6 +25,8 @@ import com.squareup.sample.hellocomposebinding.DrawHelloRenderingPreview import com.squareup.sample.hellocomposebinding.HelloBindingActivity import com.squareup.sample.nestedrenderings.NestedRenderingsActivity import com.squareup.sample.nestedrenderings.RecursiveViewFactoryPreview +import com.squareup.sample.textinput.TextInputActivity +import com.squareup.sample.textinput.TextInputAppPreview import kotlin.reflect.KClass val samples = listOf( @@ -44,7 +46,11 @@ val samples = listOf( Sample( "Nested Renderings", NestedRenderingsActivity::class, "Demonstrates recursive view factories using both Compose and legacy view factories." - ) { RecursiveViewFactoryPreview() } + ) { RecursiveViewFactoryPreview() }, + Sample( + "Text Input", TextInputActivity::class, + "Demonstrates a workflow that drives a TextField." + ) { TextInputAppPreview() } ) data class Sample( diff --git a/samples/src/main/java/com/squareup/sample/textinput/App.kt b/samples/src/main/java/com/squareup/sample/textinput/App.kt new file mode 100644 index 0000000000..9ffa0baa71 --- /dev/null +++ b/samples/src/main/java/com/squareup/sample/textinput/App.kt @@ -0,0 +1,41 @@ +/* + * Copyright 2020 Square Inc. + * + * 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.squareup.sample.textinput + +import androidx.compose.material.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.ui.tooling.preview.Preview +import com.squareup.workflow.diagnostic.SimpleLoggingDiagnosticListener +import com.squareup.workflow.ui.ViewEnvironment +import com.squareup.workflow.ui.ViewRegistry +import com.squareup.workflow.ui.compose.WorkflowContainer + +private val viewRegistry = ViewRegistry(TextInputViewFactory) +private val viewEnvironment = ViewEnvironment(viewRegistry) + +@Composable fun TextInputApp() { + MaterialTheme { + WorkflowContainer( + TextInputWorkflow, viewEnvironment, + diagnosticListener = SimpleLoggingDiagnosticListener() + ) + } +} + +@Preview(showBackground = true) +@Composable internal fun TextInputAppPreview() { + TextInputApp() +} diff --git a/samples/src/main/java/com/squareup/sample/textinput/TextInputActivity.kt b/samples/src/main/java/com/squareup/sample/textinput/TextInputActivity.kt new file mode 100644 index 0000000000..34033c1155 --- /dev/null +++ b/samples/src/main/java/com/squareup/sample/textinput/TextInputActivity.kt @@ -0,0 +1,29 @@ +/* + * Copyright 2020 Square Inc. + * + * 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.squareup.sample.textinput + +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import androidx.compose.ui.platform.setContent + +class TextInputActivity : AppCompatActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContent { + TextInputApp() + } + } +} diff --git a/samples/src/main/java/com/squareup/sample/textinput/TextInputViewFactory.kt b/samples/src/main/java/com/squareup/sample/textinput/TextInputViewFactory.kt new file mode 100644 index 0000000000..1a229a3fb1 --- /dev/null +++ b/samples/src/main/java/com/squareup/sample/textinput/TextInputViewFactory.kt @@ -0,0 +1,66 @@ +/* + * Copyright 2020 Square Inc. + * + * 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.squareup.sample.textinput + +import androidx.compose.animation.animateContentSize +import androidx.compose.foundation.Text +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.material.Button +import androidx.compose.material.OutlinedTextField +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.ExperimentalFocus +import androidx.compose.ui.unit.dp +import androidx.ui.tooling.preview.Preview +import com.squareup.sample.textinput.TextInputWorkflow.Rendering +import com.squareup.workflow.ui.compose.composedViewFactory +import com.squareup.workflow.ui.compose.tooling.preview + +@OptIn(ExperimentalFocus::class) +val TextInputViewFactory = composedViewFactory { rendering, _ -> + Column( + modifier = Modifier + .fillMaxSize() + .wrapContentSize() + .animateContentSize(clip = false), + horizontalGravity = Alignment.CenterHorizontally + ) { + Text(text = rendering.text) + OutlinedTextField( + label = {}, + placeholder = { Text("Enter some text") }, + value = rendering.text, + onValueChange = rendering.onTextChanged + ) + Spacer(modifier = Modifier.height(8.dp)) + Button(onClick = rendering.onSwapText) { + Text("Swap") + } + } +} + +@Preview @Composable private fun TextInputViewFactoryPreview() { + TextInputViewFactory.preview(Rendering( + text = "Hello world", + onTextChanged = {}, + onSwapText = {} + )) +} diff --git a/samples/src/main/java/com/squareup/sample/textinput/TextInputWorkflow.kt b/samples/src/main/java/com/squareup/sample/textinput/TextInputWorkflow.kt new file mode 100644 index 0000000000..4616f20790 --- /dev/null +++ b/samples/src/main/java/com/squareup/sample/textinput/TextInputWorkflow.kt @@ -0,0 +1,67 @@ +/* + * Copyright 2020 Square Inc. + * + * 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.squareup.sample.textinput + +import com.squareup.sample.textinput.TextInputWorkflow.Rendering +import com.squareup.sample.textinput.TextInputWorkflow.State +import com.squareup.workflow.RenderContext +import com.squareup.workflow.Snapshot +import com.squareup.workflow.StatefulWorkflow +import com.squareup.workflow.action + +object TextInputWorkflow : StatefulWorkflow() { + + data class State( + val textA: String = "", + val textB: String = "", + val showingTextA: Boolean = true + ) + + data class Rendering( + val text: String, + val onTextChanged: (String) -> Unit, + val onSwapText: () -> Unit + ) + + private val swapText = action { + nextState = nextState.copy(showingTextA = !nextState.showingTextA) + } + + private fun changeText(text: String) = action { + nextState = if (nextState.showingTextA) { + nextState.copy(textA = text) + } else { + nextState.copy(textB = text) + } + } + + override fun initialState( + props: Unit, + snapshot: Snapshot? + ): State = State() + + override fun render( + props: Unit, + state: State, + context: RenderContext + ): Rendering = Rendering( + text = if (state.showingTextA) state.textA else state.textB, + onTextChanged = { context.actionSink.send(changeText(it)) }, + onSwapText = { context.actionSink.send(swapText) } + ) + + override fun snapshotState(state: State): Snapshot = Snapshot.EMPTY +} From 551c2f2a6e526435a6dd3bd43b1cc2142c322742 Mon Sep 17 00:00:00 2001 From: Zach Klippenstein Date: Tue, 11 Aug 2020 13:58:36 -0700 Subject: [PATCH 47/67] Upgrade Gradle to 6.6. --- gradle/wrapper/gradle-wrapper.jar | Bin 58910 -> 59203 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 2 +- gradlew.bat | 21 +++------------------ 4 files changed, 5 insertions(+), 20 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 62d4c053550b91381bbd28b1afc82d634bf73a8a..e708b1c023ec8b20f512888fe07c5bd3ff77bb8f 100755 GIT binary patch delta 6656 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 Yte!%o?r4GTrVtxD61Ym!|5fq-1K$0e!T1w z1SC8j)_dObefzK9b=~*c&wBRW>;B{VGKiBofK!FMN5oJBE0V;;!kWUz!jc1W?5KdY zyZ3mCBHprpchz-9{ASiJJh&&h1|4rdw6wxD2+9= z#6#}Uq8&^1F3wgvGFoNDo?bIeEQXpcuAR0-+w$JWoK-@yUal1M&~W_O)r+Rx;{@hWH5n^oQWR36GMYBDDZyPK4L@WVjRrF+XlSzi4X4!_!U%Uujl6LHQ#|l(sUU%{ zefYd8jnVYP91K}Qn-OmmSLYFK1h~_}RPS~>+Xdz%dpvpJ{ll!IKX=JN99qowqslbO zV3DmqPZ}6>KB!9>jEObpi$u5oGPfO3O5!o3N2Mn`ozpje<}1I1H)m2rJDcB7AwXc6 z6j)tnPiql7#)r+b+p9?MVahp&=qJ^$oG+a^C*);FoJ!+V*^W+|2Olx5{*&$bXth)U zejc7mU6cBp?^Rj|dd{GL-0eHRTBi6_yJ&GLP5kIncv^z{?=0AVy^5{S8_n=rtua!J zFGY=A(yV^ZhB}1J_y(F`3QTu+zkHlw;1GiFeP&pw0N1k%NShHlO(4W+(!wy5phcg4 zA-|}(lE_1@@e6y`veg;v7m;q%(PFG&K3#}eRhJioXUU0jg_8{kn$;KVwf;zpL2X_( zC*_R#5*PaBaY73(x*oZ}oE#HPLJQRQ7brNK=v!lsu==lSG1(&q>F)`adBT~d*lMS| z%!%7(p~<7kWNmpZ5-N31*e=8`kih|g5lVrI%2wnLF-2D+G4k6@FrYsJ_80AJ}KMRi>) z-kIeHp{maorNWkF81v0FKgB==_6blyaF$5GaW)B!i4v*jNk6r)vU6?G$0pV8(Y+UK z5lgRVt%;N_gWp)^osv=h+^07UY6+$4^#t=M3>0i0`{`aEkFLL#a)93uXhYO+aKTtu zckg2T9S&GKNtZmdAS^8PzvDva-%-K&g9eqPXQ4$dM^inr@6Zl z{!Cq&C_+V;g*{>!0cZP}?ogDb$#ZS=n@NHE{>k@84lOkl&$Bt2NF)W%GClViJq14_ zQIfa^q+0aq){}CO8j%g%R9|;G0uJuND*HO$2i&U_uW_a5xJ33~(Vy?;%6_(2_Cuq1 zLhThN@xH7-BaNtkKTn^taQHrs$<<)euc6z(dhps>SM;^Wx=7;O&IfNVJq3wk4<1VS z-`*7W4DR_i^W4=dRh>AXi~J$K>`UqP>CKVVH&+T(ODhRJZO7DScU$F7D)di-%^8?O z6)Ux`zdrVOe1GNkPo0FgrrxSu1AGQkJe@pqu}8LkBDm+V!N_1l}`tjLW8${rgDLv3m@E*#zappt-Mm zSC<$o+6UO~w0C=(0$&*y**@nKe_Q{|eAuD!(0YL0_a{z%+sdfSyP={Nyd$re6Rzbp zvsgTY7~VflX0^Vf7qqomYZ_$ryrFVV2$sFyzw2r%Q8*uYDA+)iQdfKms_5(>!s#!( z!P5S(N0i9CKQKaqg(U%Gk#V3*?)lO6dLv`8KB~F<-%VhbtL8Rl>mEz+PN=qx&t*|= zQHV=qG)YKlPk4iCyWIUGjC?kpeA>hIBK*A?B0)rB=RqAal#D%1C9yVQwBcz${#Jb5 zR{TRmMrOrJsLc&6x9qDo@FJ^=do_Y?3oU0G^nV5_EU&+DS+VA7Tp{^TAF>yZbyM3c zf*1CqHY9T|aL_lyY7c)i!_MtGPA!sdy3|mrsKVj1mi&>dms@-ozSa}OZ?2I*tAndg z@S7er$t^d^-;!wLQbG60nWd@1pQVD7tw-G_B#OscoYyremiZ_hj8*sXqQdchuD^!R zpXGuSj5psk+jR>3rWu3^`17>j&*^9^rWbszP=Mf@5KIEj%b=z98v=Ymp%$FYt>%Ld zm8})EDbNOJu9n)gwhz_RS``#Ag)fr)3<*?(!9O~mTQWeh;8c;0@o=iBLQNqx3d_2#W7S9#FXzr6VXfs>4 z;QXw}-STvK9_-7H=uqgal2{GkbjVLN+=D5ddd)4^WvX;(NYA*X*(JxTdiUzqVJopd zQg#~psX4o<)cF>r=rxP`(Xsf<+HG-pf&7aFPL8z|-&B*P?Vmsu5d>Nlg^2$WRY!S@#`g2{81;(1w#o5HsvN}5pFZi});>|VK^kL{Zkx~wgn ztlZp;HW`H8(GdRfIwc~?#N6}o#h158ohI*GIsK%56I_9sf2k_K@4vD!l{(dX9E7PJ;w>$|Y;-VBJSO4@){07bo-89^LZ9g<<%;dOl zyIq{s8`8Ltp*GDwu(l_Z$6sA2nam$BM$Q~6TpZg)w2TtW?G5whV(lRwaf$6EU86is zBP9Rs&vS_~sk?Nn_b}^HkM8LiO@>J}=g(T4hLmvH@5Jj#2aHa~K)lD9VB0k>$V2BP zgh;(=y9Op(KQ=H5vj+%qs>?s4tYN~-Q|fyQePA)s?HrF~;l!+@t8VMzqUpqMLudFT z)=o~s!MM4XkgbetIsODwtQ=FF$IcIp&!pjh6Q6{tL+l*7GQ%8Wsg(tC#qU3oW$~n) zL=>XIxI}Hi7HS0F_mmi+(c%1HDuKiWm>|6Xa}nW7ei55ggru9)xjBvC#JcEIN*#cp zv*ACvr=HTC?dX9NNo9Yhulu_gX5Z~}QQ2&QZ&C77{(>Y3_ z6j5Z1Uc5FtPEpS_31HsgmSLHZijGb_p$WlRJ1p^_1!ZLP8kr6OtCEK7Qh267o$H>e zf<4cNGQRk{g5h$XfvTFQ@`qm@iju83-~}ebAYpZryARHVR$AEt3229U{y@Fp4 z-8FBBtGG&(hTyUdx5ZOfiz`c=<0F%+w|Fl=rWk{K7>70k04SN?RU(^mrKSeKDqA!K^Hsv8C?#ioj4@WUL zC*?{hTai6q0%_oBTqDHygp_Kl;({sAScYQIwMDM1U>{x0ww zve?_}E;DG?+|zsUrsph5X_G7l#Y~vqkq3@NNDabbw7|`eJBmn`Qrlr%?`va=mm$Mc{+FBbQbogAZ6{MuzT|P%QZZotd21eb1hfj|;GYAX&>bx#D5EB+=XMj2XJkpnyMUykaVo) zj3ZLqEl1&)Rturc8m@+uUuD^vaNaSxGwP4dq0-OSb~62lPv8E_K4usLvG{Qg zdR%z8dd2H!{JaT|X_bfm{##*W$YM;_J8Y8&Z)*ImOAf4+| zEyi)qK%Ld1bHuqD+}-WiCnjszDeC-%8g+8JRpG1bOc!xUGB?@?6f~FTrI%U#5R~YF z%t5(S2Q>?0`(XNHa8xKdTEZ~Z4SJOheit#ldfdg63}#W6j8kO;SjQD`vftxS+#x1B zYu|5szEvkyz|}|B3x|DNlyi$;+n+cW$Hu+?)=X1!sa%{H-^;oBO9XACZJ}wkQ!sTa zQ#J3h|HX{{&WwIG3h7d6aWktuJaO)ie6&=KJBoX@w(rBWfin`*a6OmCC5M0HzL(gv zY<*e4hmW>SWVhxk-`UGOAbD%Hk+uu<^7zJ_ytVXamfqCd0$g+W08>?QAB}Cv{b}eM z@X}ILg+uT%>-6`A25p@uhS3%;u>ccSq}8|H_^o&`nBT5S0y z;2H0I^(4MO*S+(4l$gULc4KSeKvidto5Nl0P|%9CqQ*ikY!w_GUlo}sb9HYB=L^oFpJ zfTQskXW!LFVnUo4(OHPDaZSf3zB|3{RGu1>ueE$(+dr?tT zp!SGlqDU8vu{5xLWSvj+j$arHglg54#Lx&TvuO3LIIU>hF9Uoj&=-b*Q?uYr`#V?xz?2 zhirZrv^eA{k%{hFh%9LYVXEYWd5#PuUd1QqaqB*J!CMXEM>fEB$@#1>mtB`Bfil}t zhhTIObqh5HRvT+4q_Do$Q*Jika?qV=Np-DtPkU z(KoXyWLfPwr@UY1)hBAvR3nCBZgd|CevTG?H~HqDF}dzy%2sd2`f{^CBbTk*^K~RO zN~O0+2EjAJlywF%SjgYz810l&G5AqzI<=Ber{912^PpSPRJl3dm8W@dKHL}7_@k3)Y!SXYkyxQy>Q4I2o zr`ev7fLF$1t96h|sH<-#*YzGD-b^3$_!#wsh(Yw;)b@udLz9mm`mFYh z1Zz24KIQJ(*_-E0(3&1InqG;U?wF)GYd>DFo(em`#|UaaYmkA9;GTX7b?0@C@QkTVpGD#mf$dQoRNV=n{^Zi_W*ps;3?^$s`0;ER7;==~OmQ~9 zS5P=FjxE5%|;xq6h4@!_h?@|aK&FYI2IT(OHXv2%1 zWEo-v!L7x^YT(xLVHlpJttcwaF@1Y;-S*q3CRa!g7xdzl|Jan>2#dI0`LKl!T1GMk zRKe4|bQO&ET}Z^Aiym*HII>cSxIzl|F~JEUGxz;+DB=8fxXhnBI4R12q6ews$lA`Jfi}r@A@-)6TOAUMNYFYJ zZ-Zd?lxFTyjN3mXnL!%#>Z%$0gJ4*9g;e;@zSmQ{eGGDaRRNM3s@6!;hYuVc=c+3B z=qzNNS~n^EsJU4aOGE|mdy={C^lPKEfPL-IJAsTpQsDgZ@~s+eHZYmp9yb=YW_4r?lqQaYZQ`nau){W`LY#P)>i zq^wHEuOYs#FlPZeMuT@Etb@~A6feCebq`miJE3w+gAL%bVF_s*5e*@)?xmKSo%I3? zLELHVdWia$}~s6 zr!^LfxSSB4Td&9iTXrzQpl5ZDo#SdmNr;23QsPHQ!x!UT9xtb!Ycz^JF8x)%cFOXK z^EXw%dRz_VD}7?RU^4{)1+xFO=z!EI8IUa3U*rag=1BpHX$Xi<__kSbS{y_xa*MJv z_`thq0Z^sPzjAk48ssDQj}!$N8Q$XC84(bU$t_Bm69Jf+C!h_}ep zwzpQj9sRA94<{x3{~z&ix-DwX;RAzka)4-#6ZHJqKh|SVuO|>Yrv+m30+!|sK<-|E z=)5E->#y<_1V|T1f%Af!ZYqXg}`O zI$qKOWdnclF`%_Z`WGOe{`A`l-#a?s=Q1a#@BOWmExH2;Wl`OB!B-%lq3nO{4=WO& z#k_x|N&(qzm*6S{G*|GCegF2N2ulC+(58z2DG~yUs}i8zvRf&$CJCaexJ6Xu!`qz( z)*v8*kAE#D0KCo*s{8^Rbg=`*E2MzeIt0|x55%n-gO&yX#$l=3W7-_~&(G8j1E(XB hw}tl`5K!1C(72%nnjQrp<7@!WCh47rWB+@R{{wClNUHz< diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 186b71557c..bca17f3656 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.6-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index fbd7c51583..4f906e0c81 100755 --- a/gradlew +++ b/gradlew @@ -130,7 +130,7 @@ fi if [ "$cygwin" = "true" -o "$msys" = "true" ] ; 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 diff --git a/gradlew.bat b/gradlew.bat index a9f778a7a9..ac1b06f938 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -40,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if "%ERRORLEVEL%" == "0" goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -54,7 +54,7 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% @@ -64,21 +64,6 @@ echo location of your Java installation. goto fail -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - :execute @rem Setup the command line @@ -86,7 +71,7 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell From bdf090ba9e389a8e7aa7f0ed141bab23351cdae4 Mon Sep 17 00:00:00 2001 From: Zach Klippenstein Date: Tue, 18 Aug 2020 11:45:46 -0700 Subject: [PATCH 48/67] Upgrade to Kotlin 1.4. --- .buildscript/configure-compose.gradle | 2 +- buildSrc/src/main/java/Dependencies.kt | 3 +-- compose-tooling/build.gradle.kts | 1 - core-compose/build.gradle.kts | 1 - 4 files changed, 2 insertions(+), 5 deletions(-) diff --git a/.buildscript/configure-compose.gradle b/.buildscript/configure-compose.gradle index 456b1f2e41..c9c46450e1 100644 --- a/.buildscript/configure-compose.gradle +++ b/.buildscript/configure-compose.gradle @@ -19,7 +19,7 @@ android { compose true } composeOptions { - kotlinCompilerVersion "1.4.0-rc" + kotlinCompilerVersion Versions.kotlin kotlinCompilerExtensionVersion Versions.compose } } diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt index ea66f88a65..2b1f049d11 100644 --- a/buildSrc/src/main/java/Dependencies.kt +++ b/buildSrc/src/main/java/Dependencies.kt @@ -20,7 +20,7 @@ import kotlin.reflect.full.declaredMembers object Versions { const val compose = "0.1.0-dev16" - const val kotlin = "1.4.0-rc" + const val kotlin = "1.4.0" const val targetSdk = 29 const val workflow = "0.28.0" } @@ -49,7 +49,6 @@ object Dependencies { const val binaryCompatibilityValidatorPlugin = "org.jetbrains.kotlinx:binary-compatibility-validator:0.2.3" const val gradlePlugin = "org.jetbrains.kotlin:kotlin-gradle-plugin:${Versions.kotlin}" - const val stdlib = "org.jetbrains.kotlin:kotlin-stdlib-jdk8" const val reflect = "org.jetbrains.kotlin:kotlin-reflect:${Versions.kotlin}" } diff --git a/compose-tooling/build.gradle.kts b/compose-tooling/build.gradle.kts index a8a5f7f305..999c2b632d 100644 --- a/compose-tooling/build.gradle.kts +++ b/compose-tooling/build.gradle.kts @@ -33,7 +33,6 @@ apply(from = rootProject.file(".buildscript/configure-compose.gradle")) dependencies { api(project(":core-compose")) api(Dependencies.Compose.tooling) - api(Dependencies.Kotlin.stdlib) implementation(Dependencies.Compose.foundation) } diff --git a/core-compose/build.gradle.kts b/core-compose/build.gradle.kts index a50a205c29..eacda20b3d 100644 --- a/core-compose/build.gradle.kts +++ b/core-compose/build.gradle.kts @@ -32,7 +32,6 @@ apply(from = rootProject.file(".buildscript/android-ui-tests.gradle")) apply(from = rootProject.file(".buildscript/configure-compose.gradle")) dependencies { - api(Dependencies.Kotlin.stdlib) api(Dependencies.Workflow.UI.coreAndroid) implementation(Dependencies.Compose.foundation) From 557262fca4aeabac4cbf636f2878ab5372637723 Mon Sep 17 00:00:00 2001 From: Zach Klippenstein Date: Thu, 20 Aug 2020 00:15:03 -0700 Subject: [PATCH 49/67] Upgrade Compose to 1.0.0-alpha01. Fixes #67. --- buildSrc/src/main/java/Dependencies.kt | 2 +- .../ui/compose/ComposeViewFactoryTest.kt | 4 ++-- .../workflow/ui/compose/RenderAsStateTest.kt | 4 ++-- .../workflow/ui/compose/RenderAsState.kt | 19 ++++++++-------- .../ui/compose/internal/ViewFactories.kt | 22 +++++++++++++++---- .../com/squareup/sample/hellocompose/App.kt | 6 ++--- .../sample/launcher/SampleLauncherApp.kt | 15 ++++++------- .../nestedrenderings/RecursiveViewFactory.kt | 2 +- 8 files changed, 43 insertions(+), 31 deletions(-) diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt index 2b1f049d11..19161f4992 100644 --- a/buildSrc/src/main/java/Dependencies.kt +++ b/buildSrc/src/main/java/Dependencies.kt @@ -19,7 +19,7 @@ import java.util.Locale.US import kotlin.reflect.full.declaredMembers object Versions { - const val compose = "0.1.0-dev16" + const val compose = "1.0.0-alpha01" const val kotlin = "1.4.0" const val targetSdk = 29 const val workflow = "0.28.0" diff --git a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/ComposeViewFactoryTest.kt b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/ComposeViewFactoryTest.kt index e66367e67f..3caf54ea4b 100644 --- a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/ComposeViewFactoryTest.kt +++ b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/ComposeViewFactoryTest.kt @@ -20,7 +20,7 @@ import android.widget.FrameLayout import androidx.compose.foundation.Text import androidx.compose.foundation.layout.Column import androidx.compose.runtime.mutableStateOf -import androidx.compose.ui.viewinterop.emitView +import androidx.compose.ui.viewinterop.AndroidView import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.ui.test.createComposeRule import androidx.ui.test.onNodeWithText @@ -47,7 +47,7 @@ class ComposeViewFactoryTest { } composeRule.setContent { - emitView(::RootView) { + AndroidView(::RootView) { it.setViewEnvironment(viewEnvironment) } } diff --git a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/RenderAsStateTest.kt b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/RenderAsStateTest.kt index 172467cc03..e5c5d750f4 100644 --- a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/RenderAsStateTest.kt +++ b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/RenderAsStateTest.kt @@ -124,14 +124,14 @@ class RenderAsStateTest { val savedValues = savedStateRegistry.performSave() println("saved keys: ${savedValues.keys}") // Relying on the int key across all runtimes is brittle, so use an explicit key. - val snapshot = ByteString.of(*(savedValues.getValue(SNAPSHOT_KEY) as ByteArray)) + val snapshot = ByteString.of(*(savedValues.getValue(SNAPSHOT_KEY).single() as ByteArray)) println("snapshot: ${snapshot.base64()}") assertThat(snapshot).isEqualTo(EXPECTED_SNAPSHOT) } @Test fun restoresSnapshot() { val workflow = SnapshottingWorkflow() - val restoreValues = mapOf(SNAPSHOT_KEY to EXPECTED_SNAPSHOT.toByteArray()) + val restoreValues = mapOf(SNAPSHOT_KEY to listOf(EXPECTED_SNAPSHOT.toByteArray())) val savedStateRegistry = UiSavedStateRegistry(restoreValues) { true } lateinit var rendering: SnapshottedRendering diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/RenderAsState.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/RenderAsState.kt index 8721781b4b..b275b9fbcc 100644 --- a/core-compose/src/main/java/com/squareup/workflow/ui/compose/RenderAsState.kt +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/RenderAsState.kt @@ -24,11 +24,11 @@ import androidx.compose.runtime.MutableState import androidx.compose.runtime.State import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.savedinstancestate.Saver import androidx.compose.runtime.savedinstancestate.SaverScope import androidx.compose.runtime.savedinstancestate.savedInstanceState import androidx.compose.ui.node.Ref -import androidx.compose.ui.platform.CoroutineContextAmbient import com.squareup.workflow.Snapshot import com.squareup.workflow.Workflow import com.squareup.workflow.diagnostic.WorkflowDiagnosticListener @@ -41,8 +41,8 @@ import kotlinx.coroutines.flow.consumeAsFlow import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.plus import okio.ByteString -import kotlin.coroutines.CoroutineContext /** * Runs this [Workflow] as long as this composable is part of the composition, and returns a @@ -158,8 +158,10 @@ inline fun Workflow.renderAsState( diagnosticListener: WorkflowDiagnosticListener?, snapshotKey: String? = null ): State { - @Suppress("DEPRECATION") - val coroutineContext = CoroutineContextAmbient.current + Dispatchers.Main.immediate + // TODO Pass Dispatchers.Main.immediate and merge two scope vals when this bug is fixed: + // https://issuetracker.google.com/issues/165674304 + val baseScope = rememberCoroutineScope() + val workflowScope = remember { baseScope + Dispatchers.Main.immediate } val snapshotState = savedInstanceState(key = snapshotKey, saver = SnapshotSaver) { null } val outputRef = remember { Ref<(OutputT) -> Unit>() } @@ -167,8 +169,8 @@ inline fun Workflow.renderAsState( // We can't use onActive/on(Pre)Commit because they won't run their callback until after this // function returns, and we need to run this immediately so we get the rendering synchronously. - val state = remember(coroutineContext, workflow, diagnosticListener) { - WorkflowState(coroutineContext, workflow, props, outputRef, snapshotState, diagnosticListener) + val state = remember(workflow, diagnosticListener) { + WorkflowState(workflowScope, workflow, props, outputRef, snapshotState, diagnosticListener) } state.setProps(props) @@ -177,7 +179,7 @@ inline fun Workflow.renderAsState( @Suppress("EXPERIMENTAL_API_USAGE") private class WorkflowState( - coroutineContext: CoroutineContext, + private val workflowScope: CoroutineScope, workflow: Workflow, initialProps: PropsT, private val outputRef: Ref<(OutputT) -> Unit>, @@ -185,7 +187,6 @@ private class WorkflowState( private val diagnosticListener: WorkflowDiagnosticListener? ) : CompositionLifecycleObserver { - private val workflowScope = CoroutineScope(coroutineContext) private val renderingState = mutableStateOf(null) // This can be a StateFlow once coroutines is upgraded to 1.3.6. @@ -236,5 +237,3 @@ private object SnapshotSaver : Saver { ?.let { bytes -> Snapshot.of(ByteString.of(*bytes)) } } } - -private class OutputCallback(var onOutput: (OutputT) -> Unit) diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ViewFactories.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ViewFactories.kt index b850fe9ebf..4043be6308 100644 --- a/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ViewFactories.kt +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ViewFactories.kt @@ -22,9 +22,11 @@ import android.widget.FrameLayout import androidx.compose.foundation.Box import androidx.compose.runtime.Composable import androidx.compose.runtime.compositionReference +import androidx.compose.runtime.onPreCommit import androidx.compose.runtime.remember import androidx.compose.ui.Modifier -import androidx.compose.ui.viewinterop.emitView +import androidx.compose.ui.node.Ref +import androidx.compose.ui.viewinterop.AndroidView import com.squareup.workflow.ui.ViewEnvironment import com.squareup.workflow.ui.ViewFactory import com.squareup.workflow.ui.canShowRendering @@ -95,9 +97,21 @@ import kotlin.properties.Delegates.observable viewEnvironment + (ParentComposition to parentComposition) } - emitView(::HostView) { - it.viewFactory = viewFactory - it.update = Pair(rendering, wrappedEnvironment) + // We can't trigger subcompositions during the composition itself, we have to wait until + // the composition is committed. So instead of sending the update in the AndroidView update + // lambda, we just store the view here, and then send the update and view factory in an + // onPreCommit hook. See https://github.com/square/workflow-kotlin-compose/issues/67. + val hostViewRef = remember { Ref() } + + AndroidView(::HostView) { + hostViewRef.value = it + } + + onPreCommit { + hostViewRef.value?.let { hostView -> + hostView.viewFactory = viewFactory + hostView.update = Pair(rendering, wrappedEnvironment) + } } } diff --git a/samples/src/main/java/com/squareup/sample/hellocompose/App.kt b/samples/src/main/java/com/squareup/sample/hellocompose/App.kt index 324e5e8518..15774fc920 100644 --- a/samples/src/main/java/com/squareup/sample/hellocompose/App.kt +++ b/samples/src/main/java/com/squareup/sample/hellocompose/App.kt @@ -15,7 +15,7 @@ */ package com.squareup.sample.hellocompose -import androidx.compose.foundation.drawBorder +import androidx.compose.foundation.border import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.MaterialTheme import androidx.compose.runtime.Composable @@ -35,9 +35,9 @@ private val viewEnvironment = ViewEnvironment(viewRegistry) MaterialTheme { WorkflowContainer( HelloWorkflow, viewEnvironment, - modifier = Modifier.drawBorder( + modifier = Modifier.border( shape = RoundedCornerShape(10.dp), - size = 10.dp, + width = 10.dp, color = Color.Magenta ), diagnosticListener = SimpleLoggingDiagnosticListener() diff --git a/samples/src/main/java/com/squareup/sample/launcher/SampleLauncherApp.kt b/samples/src/main/java/com/squareup/sample/launcher/SampleLauncherApp.kt index e41c171c80..b0c424b1b3 100644 --- a/samples/src/main/java/com/squareup/sample/launcher/SampleLauncherApp.kt +++ b/samples/src/main/java/com/squareup/sample/launcher/SampleLauncherApp.kt @@ -20,6 +20,7 @@ import android.os.Bundle import android.view.View import androidx.compose.foundation.Box import androidx.compose.foundation.Text +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.width @@ -35,19 +36,17 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.drawLayer +import androidx.compose.ui.geometry.Rect import androidx.compose.ui.gesture.rawPressStartGestureFilter +import androidx.compose.ui.input.pointer.PointerEventPass.Initial import androidx.compose.ui.layout.LayoutCoordinates import androidx.compose.ui.layout.globalBounds import androidx.compose.ui.node.Ref import androidx.compose.ui.onPositioned import androidx.compose.ui.platform.ConfigurationAmbient -import androidx.compose.ui.platform.PointerEventPass.PreDown import androidx.compose.ui.platform.ViewAmbient import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.PxBounds import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.height -import androidx.compose.ui.unit.width import androidx.core.app.ActivityOptionsCompat.makeScaleUpAnimation import androidx.core.content.ContextCompat.startActivity import androidx.ui.tooling.preview.Preview @@ -80,7 +79,7 @@ import com.squareup.sample.R.string * [androidx.ui.core.LayoutCoordinates.globalBounds] corresponds to the coordinates in the root * Android view hosting the composition. */ - val globalBounds = remember { Ref() } + val globalBounds = remember { Ref() } ListItem( text = { Text(sample.name) }, @@ -88,7 +87,7 @@ import com.squareup.sample.R.string singleLineSecondaryText = false, // Animate the activities as scaling up from where the preview is drawn. icon = { SamplePreview(sample) { globalBounds.value = it.globalBounds } }, - onClick = { launchSample(sample, rootView, globalBounds.value) } + modifier = Modifier.clickable { launchSample(sample, rootView, globalBounds.value) } ) } @@ -120,7 +119,7 @@ import com.squareup.sample.R.string modifier = Modifier // Disable touch input, since this preview isn't meant to be interactive. .rawPressStartGestureFilter( - enabled = true, executionPass = PreDown, onPressStart = {} + enabled = true, executionPass = Initial, onPressStart = {} ) // Measure/layout the child at full screen size, and then just scale the pixels // down. This way all the text and other density-dependent things get scaled @@ -138,7 +137,7 @@ import com.squareup.sample.R.string private fun launchSample( sample: Sample, rootView: View, - sourceBounds: PxBounds? + sourceBounds: Rect? ) { val context = rootView.context val intent = Intent(context, sample.activityClass.java) diff --git a/samples/src/main/java/com/squareup/sample/nestedrenderings/RecursiveViewFactory.kt b/samples/src/main/java/com/squareup/sample/nestedrenderings/RecursiveViewFactory.kt index 6b741818f8..e593d651a6 100644 --- a/samples/src/main/java/com/squareup/sample/nestedrenderings/RecursiveViewFactory.kt +++ b/samples/src/main/java/com/squareup/sample/nestedrenderings/RecursiveViewFactory.kt @@ -61,7 +61,7 @@ val RecursiveViewFactory = composedViewFactory { rendering, viewEnvir .compositeOver(Color.Black) } - Card(color = color) { + Card(backgroundColor = color) { Column( Modifier.padding(dimensionResource(R.dimen.recursive_padding)) .fillMaxSize(), From cdec99389bf279db17a611fd28e37a19bcc16055 Mon Sep 17 00:00:00 2001 From: Zach Klippenstein Date: Mon, 14 Sep 2020 08:23:05 -0700 Subject: [PATCH 50/67] Upgrade gradle to 6.6.1. `gw wrapper --gradle-version=6.6.1 --distribution-type=all` --- gradle/wrapper/gradle-wrapper.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index bca17f3656..33682bbbf9 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.6-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From 319d921bb7c8635dae8e98dfe8d4a3be0297d7c5 Mon Sep 17 00:00:00 2001 From: Zach Klippenstein Date: Mon, 14 Sep 2020 08:25:34 -0700 Subject: [PATCH 51/67] Upgrade AGP to 4.2.0-alpha10. --- buildSrc/src/main/java/Dependencies.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt index 19161f4992..b8c6d0eaed 100644 --- a/buildSrc/src/main/java/Dependencies.kt +++ b/buildSrc/src/main/java/Dependencies.kt @@ -27,7 +27,7 @@ object Versions { @Suppress("unused") object Dependencies { - const val android_gradle_plugin = "com.android.tools.build:gradle:4.2.0-alpha07" + const val android_gradle_plugin = "com.android.tools.build:gradle:4.2.0-alpha10" object AndroidX { const val appcompat = "androidx.appcompat:appcompat:1.1.0" From 8c9b3b3f5f5650732c1e1efb78181184878b87c3 Mon Sep 17 00:00:00 2001 From: Zach Klippenstein Date: Mon, 14 Sep 2020 08:48:49 -0700 Subject: [PATCH 52/67] Upgrade Compose to alpha02. --- buildSrc/src/main/java/Dependencies.kt | 2 +- compose-tooling/api/compose-tooling.api | 4 +-- core-compose/api/core-compose.api | 30 +++++++++---------- .../ui/compose/internal/ViewFactories.kt | 4 +-- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt index b8c6d0eaed..2cb1d7bfd7 100644 --- a/buildSrc/src/main/java/Dependencies.kt +++ b/buildSrc/src/main/java/Dependencies.kt @@ -19,7 +19,7 @@ import java.util.Locale.US import kotlin.reflect.full.declaredMembers object Versions { - const val compose = "1.0.0-alpha01" + const val compose = "1.0.0-alpha02" const val kotlin = "1.4.0" const val targetSdk = 29 const val workflow = "0.28.0" diff --git a/compose-tooling/api/compose-tooling.api b/compose-tooling/api/compose-tooling.api index 5ee0becca1..6179136415 100644 --- a/compose-tooling/api/compose-tooling.api +++ b/compose-tooling/api/compose-tooling.api @@ -6,10 +6,10 @@ public final class com/squareup/workflow/ui/compose/tooling/BuildConfig { } public final class com/squareup/workflow/ui/compose/tooling/ComposeWorkflowsKt { - public static final fun preview (Lcom/squareup/workflow/ui/compose/ComposeWorkflow;Ljava/lang/Object;Landroidx/compose/ui/Modifier;Landroidx/compose/ui/Modifier;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;III)V + public static final fun preview (Lcom/squareup/workflow/ui/compose/ComposeWorkflow;Ljava/lang/Object;Landroidx/compose/ui/Modifier;Landroidx/compose/ui/Modifier;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V } public final class com/squareup/workflow/ui/compose/tooling/ViewFactoriesKt { - public static final fun preview (Lcom/squareup/workflow/ui/ViewFactory;Ljava/lang/Object;Landroidx/compose/ui/Modifier;Landroidx/compose/ui/Modifier;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;III)V + public static final fun preview (Lcom/squareup/workflow/ui/ViewFactory;Ljava/lang/Object;Landroidx/compose/ui/Modifier;Landroidx/compose/ui/Modifier;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V } diff --git a/core-compose/api/core-compose.api b/core-compose/api/core-compose.api index 80fa316f48..af0574a288 100644 --- a/core-compose/api/core-compose.api +++ b/core-compose/api/core-compose.api @@ -1,6 +1,6 @@ public final class com/squareup/workflow/ui/compose/ComposeRendering { public static final field Companion Lcom/squareup/workflow/ui/compose/ComposeRendering$Companion; - public fun (Lkotlin/jvm/functions/Function4;)V + public fun (Lkotlin/jvm/functions/Function3;)V } public final class com/squareup/workflow/ui/compose/ComposeRendering$Companion { @@ -9,7 +9,7 @@ public final class com/squareup/workflow/ui/compose/ComposeRendering$Companion { } public final class com/squareup/workflow/ui/compose/ComposeViewFactory : com/squareup/workflow/ui/ViewFactory { - public fun (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function5;)V + public fun (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function4;)V public fun buildView (Ljava/lang/Object;Lcom/squareup/workflow/ui/ViewEnvironment;Landroid/content/Context;Landroid/view/ViewGroup;)Landroid/view/View; public fun getType ()Lkotlin/reflect/KClass; } @@ -17,34 +17,34 @@ public final class com/squareup/workflow/ui/compose/ComposeViewFactory : com/squ public abstract class com/squareup/workflow/ui/compose/ComposeWorkflow : com/squareup/workflow/Workflow { public fun ()V public fun asStatefulWorkflow ()Lcom/squareup/workflow/StatefulWorkflow; - public abstract fun render (Ljava/lang/Object;Lcom/squareup/workflow/Sink;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/compose/runtime/Composer;II)V + public abstract fun render (Ljava/lang/Object;Lcom/squareup/workflow/Sink;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/compose/runtime/Composer;I)V } public final class com/squareup/workflow/ui/compose/ComposeWorkflowKt { - public static final fun composed (Lcom/squareup/workflow/Workflow$Companion;Lkotlin/jvm/functions/Function6;)Lcom/squareup/workflow/ui/compose/ComposeWorkflow; + public static final fun composed (Lcom/squareup/workflow/Workflow$Companion;Lkotlin/jvm/functions/Function5;)Lcom/squareup/workflow/ui/compose/ComposeWorkflow; } public final class com/squareup/workflow/ui/compose/CompositionRootKt { - public static final fun withCompositionRoot (Lcom/squareup/workflow/ui/ViewEnvironment;Lkotlin/jvm/functions/Function4;)Lcom/squareup/workflow/ui/ViewEnvironment; - public static final fun withCompositionRoot (Lcom/squareup/workflow/ui/ViewRegistry;Lkotlin/jvm/functions/Function4;)Lcom/squareup/workflow/ui/ViewRegistry; + public static final fun withCompositionRoot (Lcom/squareup/workflow/ui/ViewEnvironment;Lkotlin/jvm/functions/Function3;)Lcom/squareup/workflow/ui/ViewEnvironment; + public static final fun withCompositionRoot (Lcom/squareup/workflow/ui/ViewRegistry;Lkotlin/jvm/functions/Function3;)Lcom/squareup/workflow/ui/ViewRegistry; } public final class com/squareup/workflow/ui/compose/RenderAsStateKt { - public static final fun renderAsState (Lcom/squareup/workflow/Workflow;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/runtime/Composer;III)Landroidx/compose/runtime/State; - public static final fun renderAsState (Lcom/squareup/workflow/Workflow;Ljava/lang/Object;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/runtime/Composer;III)Landroidx/compose/runtime/State; - public static final fun renderAsState (Lcom/squareup/workflow/Workflow;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/runtime/Composer;III)Landroidx/compose/runtime/State; - public static final fun renderAsState (Lcom/squareup/workflow/Workflow;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/runtime/Composer;III)Landroidx/compose/runtime/State; + public static final fun renderAsState (Lcom/squareup/workflow/Workflow;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/runtime/Composer;II)Landroidx/compose/runtime/State; + public static final fun renderAsState (Lcom/squareup/workflow/Workflow;Ljava/lang/Object;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/runtime/Composer;II)Landroidx/compose/runtime/State; + public static final fun renderAsState (Lcom/squareup/workflow/Workflow;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/runtime/Composer;II)Landroidx/compose/runtime/State; + public static final fun renderAsState (Lcom/squareup/workflow/Workflow;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/runtime/Composer;II)Landroidx/compose/runtime/State; } public final class com/squareup/workflow/ui/compose/ViewEnvironmentsKt { - public static final fun WorkflowRendering (Ljava/lang/Object;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/compose/ui/Modifier;Landroidx/compose/runtime/Composer;III)V + public static final fun WorkflowRendering (Ljava/lang/Object;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/compose/ui/Modifier;Landroidx/compose/runtime/Composer;II)V } public final class com/squareup/workflow/ui/compose/WorkflowContainerKt { - public static final fun WorkflowContainer (Lcom/squareup/workflow/Workflow;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/compose/ui/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/runtime/Composer;III)V - public static final fun WorkflowContainer (Lcom/squareup/workflow/Workflow;Ljava/lang/Object;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/compose/ui/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/runtime/Composer;III)V - public static final fun WorkflowContainer (Lcom/squareup/workflow/Workflow;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/compose/ui/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/runtime/Composer;III)V - public static final fun WorkflowContainer (Lcom/squareup/workflow/Workflow;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/compose/ui/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/runtime/Composer;III)V + public static final fun WorkflowContainer (Lcom/squareup/workflow/Workflow;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/compose/ui/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/runtime/Composer;II)V + public static final fun WorkflowContainer (Lcom/squareup/workflow/Workflow;Ljava/lang/Object;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/compose/ui/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/runtime/Composer;II)V + public static final fun WorkflowContainer (Lcom/squareup/workflow/Workflow;Ljava/lang/Object;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/compose/ui/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/runtime/Composer;II)V + public static final fun WorkflowContainer (Lcom/squareup/workflow/Workflow;Lkotlin/jvm/functions/Function1;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/compose/ui/Modifier;Lcom/squareup/workflow/diagnostic/WorkflowDiagnosticListener;Landroidx/compose/runtime/Composer;II)V } public final class com/squareup/workflow/ui/core/compose/BuildConfig { diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ViewFactories.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ViewFactories.kt index 4043be6308..2c9c12d6bb 100644 --- a/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ViewFactories.kt +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ViewFactories.kt @@ -22,7 +22,7 @@ import android.widget.FrameLayout import androidx.compose.foundation.Box import androidx.compose.runtime.Composable import androidx.compose.runtime.compositionReference -import androidx.compose.runtime.onPreCommit +import androidx.compose.runtime.onCommit import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.node.Ref @@ -107,7 +107,7 @@ import kotlin.properties.Delegates.observable hostViewRef.value = it } - onPreCommit { + onCommit { hostViewRef.value?.let { hostView -> hostView.viewFactory = viewFactory hostView.update = Pair(rendering, wrappedEnvironment) From 99915a71a69c1a8cf2a7be3fefb3525050212851 Mon Sep 17 00:00:00 2001 From: Zach Klippenstein Date: Mon, 14 Sep 2020 09:01:00 -0700 Subject: [PATCH 53/67] Upgrade Kotlin to 1.4.10. --- buildSrc/src/main/java/Dependencies.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt index 2cb1d7bfd7..87ea0d8c16 100644 --- a/buildSrc/src/main/java/Dependencies.kt +++ b/buildSrc/src/main/java/Dependencies.kt @@ -20,7 +20,7 @@ import kotlin.reflect.full.declaredMembers object Versions { const val compose = "1.0.0-alpha02" - const val kotlin = "1.4.0" + const val kotlin = "1.4.10" const val targetSdk = 29 const val workflow = "0.28.0" } From 4a6e96336d29ff0b5aef67c250b1b36d63ec4c6c Mon Sep 17 00:00:00 2001 From: Zach Klippenstein Date: Tue, 13 Oct 2020 12:05:46 -0700 Subject: [PATCH 54/67] Upgrade Compose and AGP to latest versions. Compose alpha04 and AGP alpha13 are mutually dependent, so they need to be updated in lockstep. --- .../configure-android-defaults.gradle | 6 ++- .github/workflows/kotlin.yml | 2 +- buildSrc/src/main/java/Dependencies.kt | 7 ++- .../tooling/PreviewComposeWorkflowTest.kt | 20 ++++----- .../compose/tooling/PreviewViewFactoryTest.kt | 26 +++++------ .../ui/compose/tooling/ComposeWorkflows.kt | 9 ++-- .../compose/tooling/PlaceholderViewFactory.kt | 7 +-- .../ui/compose/tooling/ViewFactories.kt | 3 +- .../ui/compose/ComposeViewFactoryTest.kt | 8 ++-- .../ui/compose/CompositionRootTest.kt | 30 ++++++------- .../workflow/ui/compose/RenderAsStateTest.kt | 26 +++++------ .../ui/compose/ViewEnvironmentsTest.kt | 4 +- .../ui/compose/WorkflowContainerTest.kt | 4 +- .../ui/compose/internal/ViewFactoriesTest.kt | 5 +-- .../ui/compose/internal/ViewFactories.kt | 2 +- .../hellocomposebinding/HelloBindingTest.kt | 8 ++-- .../HelloComposeRenderingTest.kt | 44 +++++++++++-------- .../sample/launcher/SampleLauncherTest.kt | 6 +-- .../nestedrenderings/NestedRenderingsTest.kt | 12 ++--- .../sample/launcher/SampleLauncherApp.kt | 9 ++-- .../nestedrenderings/RecursiveViewFactory.kt | 4 +- .../sample/textinput/TextInputViewFactory.kt | 2 +- 22 files changed, 127 insertions(+), 117 deletions(-) diff --git a/.buildscript/configure-android-defaults.gradle b/.buildscript/configure-android-defaults.gradle index c92e99d825..c88f8c4a80 100644 --- a/.buildscript/configure-android-defaults.gradle +++ b/.buildscript/configure-android-defaults.gradle @@ -1,6 +1,5 @@ android { compileSdkVersion Versions.targetSdk - buildToolsVersion '29.0.2' compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 @@ -20,4 +19,9 @@ android { packagingOptions { exclude 'META-INF/*.kotlin_module' } + + lintOptions { + // Workaround lint bug. + disable "InvalidFragmentVersionForActivityResult" + } } diff --git a/.github/workflows/kotlin.yml b/.github/workflows/kotlin.yml index a92f8b2341..5c59f82d10 100644 --- a/.github/workflows/kotlin.yml +++ b/.github/workflows/kotlin.yml @@ -57,7 +57,7 @@ jobs: name: Check needs: assemble runs-on: ubuntu-latest - timeout-minutes: 10 + timeout-minutes: 20 strategy: # Run all checks, even if some fail. fail-fast: false diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt index 87ea0d8c16..6a73106569 100644 --- a/buildSrc/src/main/java/Dependencies.kt +++ b/buildSrc/src/main/java/Dependencies.kt @@ -19,15 +19,18 @@ import java.util.Locale.US import kotlin.reflect.full.declaredMembers object Versions { - const val compose = "1.0.0-alpha02" + const val compose = "1.0.0-alpha04" const val kotlin = "1.4.10" + + // This *is* actually used. + @Suppress("unused") const val targetSdk = 29 const val workflow = "0.28.0" } @Suppress("unused") object Dependencies { - const val android_gradle_plugin = "com.android.tools.build:gradle:4.2.0-alpha10" + const val android_gradle_plugin = "com.android.tools.build:gradle:4.2.0-alpha13" object AndroidX { const val appcompat = "androidx.appcompat:appcompat:1.1.0" diff --git a/compose-tooling/src/androidTest/java/com/squareup/workflow/ui/compose/tooling/PreviewComposeWorkflowTest.kt b/compose-tooling/src/androidTest/java/com/squareup/workflow/ui/compose/tooling/PreviewComposeWorkflowTest.kt index 8de67a0810..82022034af 100644 --- a/compose-tooling/src/androidTest/java/com/squareup/workflow/ui/compose/tooling/PreviewComposeWorkflowTest.kt +++ b/compose-tooling/src/androidTest/java/com/squareup/workflow/ui/compose/tooling/PreviewComposeWorkflowTest.kt @@ -50,8 +50,8 @@ class PreviewComposeWorkflowTest { ParentWithOneChildPreview() } - onNodeWithText("one").assertIsDisplayed() - onNodeWithText("two").assertIsDisplayed() + composeRule.onNodeWithText("one").assertIsDisplayed() + composeRule.onNodeWithText("two").assertIsDisplayed() } @Test fun twoChildren() { @@ -59,9 +59,9 @@ class PreviewComposeWorkflowTest { ParentWithTwoChildrenPreview() } - onNodeWithText("one").assertIsDisplayed() - onNodeWithText("two").assertIsDisplayed() - onNodeWithText("three").assertIsDisplayed() + composeRule.onNodeWithText("one").assertIsDisplayed() + composeRule.onNodeWithText("two").assertIsDisplayed() + composeRule.onNodeWithText("three").assertIsDisplayed() } @Test fun modifierIsApplied() { @@ -70,8 +70,8 @@ class PreviewComposeWorkflowTest { } // The view factory will be rendered with size (0,0), so it should be reported as not displayed. - onNodeWithText("one").assertIsNotDisplayed() - onNodeWithText("two").assertIsNotDisplayed() + composeRule.onNodeWithText("one").assertIsNotDisplayed() + composeRule.onNodeWithText("two").assertIsNotDisplayed() } @Test fun placeholderModifierIsApplied() { @@ -80,8 +80,8 @@ class PreviewComposeWorkflowTest { } // The child will be rendered with size (0,0), so it should be reported as not displayed. - onNodeWithText("one").assertIsDisplayed() - onNodeWithText("two").assertIsNotDisplayed() + composeRule.onNodeWithText("one").assertIsDisplayed() + composeRule.onNodeWithText("two").assertIsNotDisplayed() } @Test fun customViewEnvironment() { @@ -89,7 +89,7 @@ class PreviewComposeWorkflowTest { ParentConsumesCustomKeyPreview() } - onNodeWithText("foo").assertIsDisplayed() + composeRule.onNodeWithText("foo").assertIsDisplayed() } private val ParentWithOneChild = diff --git a/compose-tooling/src/androidTest/java/com/squareup/workflow/ui/compose/tooling/PreviewViewFactoryTest.kt b/compose-tooling/src/androidTest/java/com/squareup/workflow/ui/compose/tooling/PreviewViewFactoryTest.kt index c0a585a91a..8011296074 100644 --- a/compose-tooling/src/androidTest/java/com/squareup/workflow/ui/compose/tooling/PreviewViewFactoryTest.kt +++ b/compose-tooling/src/androidTest/java/com/squareup/workflow/ui/compose/tooling/PreviewViewFactoryTest.kt @@ -46,8 +46,8 @@ class PreviewViewFactoryTest { ParentWithOneChildPreview() } - onNodeWithText("one").assertIsDisplayed() - onNodeWithText("two").assertIsDisplayed() + composeRule.onNodeWithText("one").assertIsDisplayed() + composeRule.onNodeWithText("two").assertIsDisplayed() } @Test fun twoChildren() { @@ -55,9 +55,9 @@ class PreviewViewFactoryTest { ParentWithTwoChildrenPreview() } - onNodeWithText("one").assertIsDisplayed() - onNodeWithText("two").assertIsDisplayed() - onNodeWithText("three").assertIsDisplayed() + composeRule.onNodeWithText("one").assertIsDisplayed() + composeRule.onNodeWithText("two").assertIsDisplayed() + composeRule.onNodeWithText("three").assertIsDisplayed() } @Test fun recursive() { @@ -65,9 +65,9 @@ class PreviewViewFactoryTest { ParentRecursivePreview() } - onNodeWithText("one").assertIsDisplayed() - onNodeWithText("two").assertIsDisplayed() - onNodeWithText("three").assertIsDisplayed() + composeRule.onNodeWithText("one").assertIsDisplayed() + composeRule.onNodeWithText("two").assertIsDisplayed() + composeRule.onNodeWithText("three").assertIsDisplayed() } @Test fun modifierIsApplied() { @@ -76,8 +76,8 @@ class PreviewViewFactoryTest { } // The view factory will be rendered with size (0,0), so it should be reported as not displayed. - onNodeWithText("one").assertIsNotDisplayed() - onNodeWithText("two").assertIsNotDisplayed() + composeRule.onNodeWithText("one").assertIsNotDisplayed() + composeRule.onNodeWithText("two").assertIsNotDisplayed() } @Test fun placeholderModifierIsApplied() { @@ -86,8 +86,8 @@ class PreviewViewFactoryTest { } // The child will be rendered with size (0,0), so it should be reported as not displayed. - onNodeWithText("one").assertIsDisplayed() - onNodeWithText("two").assertIsNotDisplayed() + composeRule.onNodeWithText("one").assertIsDisplayed() + composeRule.onNodeWithText("two").assertIsNotDisplayed() } @Test fun customViewEnvironment() { @@ -95,7 +95,7 @@ class PreviewViewFactoryTest { ParentConsumesCustomKeyPreview() } - onNodeWithText("foo").assertIsDisplayed() + composeRule.onNodeWithText("foo").assertIsDisplayed() } private val ParentWithOneChild = diff --git a/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/ComposeWorkflows.kt b/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/ComposeWorkflows.kt index ededfb98a0..c13f2516f0 100644 --- a/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/ComposeWorkflows.kt +++ b/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/ComposeWorkflows.kt @@ -17,7 +17,7 @@ package com.squareup.workflow.ui.compose.tooling -import androidx.compose.foundation.Box +import androidx.compose.foundation.layout.Box import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import com.squareup.workflow.Sink @@ -41,8 +41,7 @@ import com.squareup.workflow.ui.compose.ComposeWorkflow * @param viewEnvironmentUpdater Function that configures the [ViewEnvironment] passed to this * factory. */ -// TODO(https://issuetracker.google.com/issues/156527332) Should be ViewFactory -@Composable fun ComposeWorkflow<*, *>.preview( +@Composable fun ComposeWorkflow.preview( props: PropsT, modifier: Modifier = Modifier, placeholderModifier: Modifier = Modifier, @@ -50,9 +49,7 @@ import com.squareup.workflow.ui.compose.ComposeWorkflow ) { val previewEnvironment = previewViewEnvironment(placeholderModifier, viewEnvironmentUpdater) Box(modifier = modifier) { - // Cast is needed due to bug that prevents the receiver from using PropsT. - @Suppress("UNCHECKED_CAST") - (this as ComposeWorkflow).render(props, NoopSink, previewEnvironment) + render(props, NoopSink, previewEnvironment) } } diff --git a/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/PlaceholderViewFactory.kt b/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/PlaceholderViewFactory.kt index c64260fd6c..c3e62f33b5 100644 --- a/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/PlaceholderViewFactory.kt +++ b/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/PlaceholderViewFactory.kt @@ -17,9 +17,10 @@ package com.squareup.workflow.ui.compose.tooling -import androidx.compose.foundation.Box import androidx.compose.foundation.Text +import androidx.compose.foundation.background import androidx.compose.foundation.drawBorder +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -75,14 +76,14 @@ internal fun placeholderViewFactory(modifier: Modifier): ViewFactory = @Preview(widthDp = 200, heightDp = 200) @Composable private fun PreviewStubViewBindingOnWhite() { - Box(backgroundColor = Color.White) { + Box(Modifier.background(Color.White)) { PreviewStubBindingPreviewTemplate() } } @Preview(widthDp = 200, heightDp = 200) @Composable private fun PreviewStubViewBindingOnBlack() { - Box(backgroundColor = Color.Black) { + Box(Modifier.background(Color.Black)) { PreviewStubBindingPreviewTemplate() } } diff --git a/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/ViewFactories.kt b/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/ViewFactories.kt index be858a94e2..783a97df64 100644 --- a/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/ViewFactories.kt +++ b/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/ViewFactories.kt @@ -39,8 +39,7 @@ import com.squareup.workflow.ui.compose.WorkflowRendering * @param viewEnvironmentUpdater Function that configures the [ViewEnvironment] passed to this * factory. */ -// TODO(https://issuetracker.google.com/issues/156527332) Should be ViewFactory -@Composable fun ViewFactory<*>.preview( +@Composable fun ViewFactory.preview( rendering: RenderingT, modifier: Modifier = Modifier, placeholderModifier: Modifier = Modifier, diff --git a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/ComposeViewFactoryTest.kt b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/ComposeViewFactoryTest.kt index 3caf54ea4b..a98131b2ce 100644 --- a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/ComposeViewFactoryTest.kt +++ b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/ComposeViewFactoryTest.kt @@ -54,13 +54,13 @@ class ComposeViewFactoryTest { // Compose bug doesn't let us use assertIsDisplayed on older devices. // See https://issuetracker.google.com/issues/157728188. - onNodeWithText("one").assertExists() - onNodeWithText("two").assertExists() + composeRule.onNodeWithText("one").assertExists() + composeRule.onNodeWithText("two").assertExists() wrapperText.value = "ENO" - onNodeWithText("ENO").assertExists() - onNodeWithText("two").assertExists() + composeRule.onNodeWithText("ENO").assertExists() + composeRule.onNodeWithText("two").assertExists() } private class RootView(context: Context) : FrameLayout(context) { diff --git a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/CompositionRootTest.kt b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/CompositionRootTest.kt index ec29ef7071..c91f6ed2b2 100644 --- a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/CompositionRootTest.kt +++ b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/CompositionRootTest.kt @@ -49,8 +49,8 @@ class CompositionRootTest { // These semantics used to merge, but as of dev15, they don't, which seems to be a bug. // https://issuetracker.google.com/issues/161979921 - onNodeWithText("one").assertIsDisplayed() - onNodeWithText("two").assertIsDisplayed() + composeRule.onNodeWithText("one").assertIsDisplayed() + composeRule.onNodeWithText("two").assertIsDisplayed() } @Test fun wrapWithRootIfNecessary_onlyWrapsOnce() { @@ -70,9 +70,9 @@ class CompositionRootTest { } } - onNodeWithText("one").assertIsDisplayed() - onNodeWithText("two").assertIsDisplayed() - onNodeWithText("three").assertIsDisplayed() + composeRule.onNodeWithText("one").assertIsDisplayed() + composeRule.onNodeWithText("two").assertIsDisplayed() + composeRule.onNodeWithText("three").assertIsDisplayed() } @Test fun wrapWithRootIfNecessary_seesUpdatesFromRootWrapper() { @@ -90,11 +90,11 @@ class CompositionRootTest { } } - onNodeWithText("one").assertIsDisplayed() - onNodeWithText("two").assertIsDisplayed() + composeRule.onNodeWithText("one").assertIsDisplayed() + composeRule.onNodeWithText("two").assertIsDisplayed() wrapperText.value = "ENO" - onNodeWithText("ENO").assertIsDisplayed() - onNodeWithText("two").assertIsDisplayed() + composeRule.onNodeWithText("ENO").assertIsDisplayed() + composeRule.onNodeWithText("two").assertIsDisplayed() } @Test fun wrapWithRootIfNecessary_rewrapsWhenDifferentRoot() { @@ -118,11 +118,11 @@ class CompositionRootTest { } } - onNodeWithText("one").assertIsDisplayed() - onNodeWithText("two").assertIsDisplayed() + composeRule.onNodeWithText("one").assertIsDisplayed() + composeRule.onNodeWithText("two").assertIsDisplayed() viewEnvironment.value = root2 - onNodeWithText("ENO").assertIsDisplayed() - onNodeWithText("two").assertIsDisplayed() + composeRule.onNodeWithText("ENO").assertIsDisplayed() + composeRule.onNodeWithText("two").assertIsDisplayed() } @Test fun safeComposeViewFactoryRoot_wraps_content() { @@ -140,8 +140,8 @@ class CompositionRootTest { } } - onNodeWithText("Parent").assertIsDisplayed() - onNodeWithText("Child").assertIsDisplayed() + composeRule.onNodeWithText("Parent").assertIsDisplayed() + composeRule.onNodeWithText("Child").assertIsDisplayed() } @Test fun safeComposeViewFactoryRoot_throws_whenChildrenNotInvoked() { diff --git a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/RenderAsStateTest.kt b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/RenderAsStateTest.kt index e5c5d750f4..1d2cf4c71b 100644 --- a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/RenderAsStateTest.kt +++ b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/RenderAsStateTest.kt @@ -23,8 +23,6 @@ import androidx.compose.runtime.savedinstancestate.UiSavedStateRegistry import androidx.compose.runtime.savedinstancestate.UiSavedStateRegistryAmbient import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.ui.test.createComposeRule -import androidx.ui.test.runOnIdle -import androidx.ui.test.waitForIdle import com.google.common.truth.Truth.assertThat import com.squareup.workflow.RenderContext import com.squareup.workflow.Snapshot @@ -55,7 +53,7 @@ class RenderAsStateTest { initialRendering = workflow.renderAsState("foo").value } - runOnIdle { + composeRule.runOnIdle { assertThat(initialRendering).isEqualTo("foo") } } @@ -69,10 +67,10 @@ class RenderAsStateTest { rendering = workflow.renderAsState(props.value).value } - waitForIdle() + composeRule.waitForIdle() assertThat(rendering).isEqualTo("foo") props.value = "bar" - waitForIdle() + composeRule.waitForIdle() assertThat(rendering).isEqualTo("bar") } @@ -87,15 +85,15 @@ class RenderAsStateTest { rendering = workflow.renderAsState(onOutput = { receivedOutputs += it }).value } - waitForIdle() + composeRule.waitForIdle() assertThat(receivedOutputs).isEmpty() rendering("one") - waitForIdle() + composeRule.waitForIdle() assertThat(receivedOutputs).isEqualTo(listOf("one")) rendering("two") - waitForIdle() + composeRule.waitForIdle() assertThat(receivedOutputs).isEqualTo(listOf("one", "two")) } @@ -116,11 +114,11 @@ class RenderAsStateTest { } } - waitForIdle() + composeRule.waitForIdle() assertThat(rendering.string).isEmpty() rendering.updateString("foo") - waitForIdle() + composeRule.waitForIdle() val savedValues = savedStateRegistry.performSave() println("saved keys: ${savedValues.keys}") // Relying on the int key across all runtimes is brittle, so use an explicit key. @@ -147,7 +145,7 @@ class RenderAsStateTest { } } - waitForIdle() + composeRule.waitForIdle() assertThat(rendering.string).isEqualTo("foo") } @@ -170,11 +168,11 @@ class RenderAsStateTest { } // Initialize the first workflow. - waitForIdle() + composeRule.waitForIdle() assertThat(rendering.string).isEmpty() assertWasRecomposed() rendering.updateString("one") - waitForIdle() + composeRule.waitForIdle() assertWasRecomposed() assertThat(rendering.string).isEqualTo("one") @@ -182,7 +180,7 @@ class RenderAsStateTest { // it from the snapshot. currentWorkflow.value = workflow2 - waitForIdle() + composeRule.waitForIdle() assertWasRecomposed() assertThat(rendering.string).isEqualTo("one") } diff --git a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/ViewEnvironmentsTest.kt b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/ViewEnvironmentsTest.kt index 71c4d540c1..7b4255e857 100644 --- a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/ViewEnvironmentsTest.kt +++ b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/ViewEnvironmentsTest.kt @@ -45,8 +45,8 @@ class ViewEnvironmentsTest { WorkflowRendering("hello", ViewEnvironment(registry.value)) } - onNodeWithText("hello").assertIsDisplayed() + composeRule.onNodeWithText("hello").assertIsDisplayed() registry.value = registry2 - onNodeWithText("olleh").assertIsDisplayed() + composeRule.onNodeWithText("olleh").assertIsDisplayed() } } diff --git a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/WorkflowContainerTest.kt b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/WorkflowContainerTest.kt index 69bce0ca4e..c98afb57f2 100644 --- a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/WorkflowContainerTest.kt +++ b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/WorkflowContainerTest.kt @@ -43,7 +43,7 @@ class WorkflowContainerTest { WorkflowContainer(workflow, ViewEnvironment(registry)) } - onNodeWithText("hello").assertIsDisplayed() + composeRule.onNodeWithText("hello").assertIsDisplayed() } @Test fun automaticallyAddsComposeRenderingFactory() { @@ -56,6 +56,6 @@ class WorkflowContainerTest { WorkflowContainer(workflow, ViewEnvironment(registry)) } - onNodeWithText("it worked").assertIsDisplayed() + composeRule.onNodeWithText("it worked").assertIsDisplayed() } } diff --git a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/internal/ViewFactoriesTest.kt b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/internal/ViewFactoriesTest.kt index 9352f68193..8015971933 100644 --- a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/internal/ViewFactoriesTest.kt +++ b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/internal/ViewFactoriesTest.kt @@ -22,7 +22,6 @@ import android.widget.TextView import androidx.compose.foundation.Text import androidx.compose.foundation.layout.Column import androidx.compose.runtime.mutableStateOf - import androidx.test.espresso.Espresso.onView import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.matcher.ViewMatchers.isDisplayed @@ -60,8 +59,8 @@ class ViewFactoriesTest { WorkflowRendering(TestRendering("two"), viewEnvironment) } - onNodeWithText("one").assertIsDisplayed() - onNodeWithText("two").assertIsDisplayed() + composeRule.onNodeWithText("one").assertIsDisplayed() + composeRule.onNodeWithText("two").assertIsDisplayed() } @Test fun WorkflowRendering_legacyAndroidViewRendersUpdates() { diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ViewFactories.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ViewFactories.kt index 2c9c12d6bb..1576e4ff76 100644 --- a/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ViewFactories.kt +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ViewFactories.kt @@ -19,7 +19,7 @@ import android.content.Context import android.view.View import android.view.ViewGroup.LayoutParams.MATCH_PARENT import android.widget.FrameLayout -import androidx.compose.foundation.Box +import androidx.compose.foundation.layout.Box import androidx.compose.runtime.Composable import androidx.compose.runtime.compositionReference import androidx.compose.runtime.onCommit diff --git a/samples/src/androidTest/java/com/squareup/sample/hellocomposebinding/HelloBindingTest.kt b/samples/src/androidTest/java/com/squareup/sample/hellocomposebinding/HelloBindingTest.kt index 14c986acaf..90ac58022a 100644 --- a/samples/src/androidTest/java/com/squareup/sample/hellocomposebinding/HelloBindingTest.kt +++ b/samples/src/androidTest/java/com/squareup/sample/hellocomposebinding/HelloBindingTest.kt @@ -16,8 +16,8 @@ package com.squareup.sample.hellocomposebinding import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.ui.test.android.createAndroidComposeRule import androidx.ui.test.assertIsDisplayed +import androidx.ui.test.createAndroidComposeRule import androidx.ui.test.onNodeWithText import androidx.ui.test.performClick import org.junit.Rule @@ -31,13 +31,13 @@ class HelloBindingTest { @Rule @JvmField val composeRule = createAndroidComposeRule() @Test fun togglesBetweenStates() { - onNodeWithText("Hello") + composeRule.onNodeWithText("Hello") .assertIsDisplayed() .performClick() - onNodeWithText("Goodbye") + composeRule.onNodeWithText("Goodbye") .assertIsDisplayed() .performClick() - onNodeWithText("Hello") + composeRule.onNodeWithText("Hello") .assertIsDisplayed() } } diff --git a/samples/src/androidTest/java/com/squareup/sample/hellocomposerendering/HelloComposeRenderingTest.kt b/samples/src/androidTest/java/com/squareup/sample/hellocomposerendering/HelloComposeRenderingTest.kt index 406f376b3c..b3a1036756 100644 --- a/samples/src/androidTest/java/com/squareup/sample/hellocomposerendering/HelloComposeRenderingTest.kt +++ b/samples/src/androidTest/java/com/squareup/sample/hellocomposerendering/HelloComposeRenderingTest.kt @@ -15,21 +15,29 @@ */ package com.squareup.sample.hellocomposerendering -// This sample is broken in dev12, see https://github.com/square/workflow-kotlin-compose/issues/42. -// @RunWith(AndroidJUnit4::class) -// class HelloComposeRenderingTest { -// -// // Launches the activity. -// @Rule @JvmField val composeRule = AndroidComposeTestRule() -// -// @Test fun togglesBetweenStates() { -// findByText("Hello") -// .assertIsDisplayed() -// .doClick() -// findByText("Goodbye") -// .assertIsDisplayed() -// .doClick() -// findByText("Hello") -// .assertIsDisplayed() -// } -// } +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.ui.test.assertIsDisplayed +import androidx.ui.test.createAndroidComposeRule +import androidx.ui.test.onNodeWithText +import androidx.ui.test.performClick +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class HelloComposeRenderingTest { + + // Launches the activity. + @Rule @JvmField val composeRule = createAndroidComposeRule() + + @Test fun togglesBetweenStates() { + composeRule.onNodeWithText("Hello") + .assertIsDisplayed() + .performClick() + composeRule.onNodeWithText("Goodbye") + .assertIsDisplayed() + .performClick() + composeRule.onNodeWithText("Hello") + .assertIsDisplayed() + } +} diff --git a/samples/src/androidTest/java/com/squareup/sample/launcher/SampleLauncherTest.kt b/samples/src/androidTest/java/com/squareup/sample/launcher/SampleLauncherTest.kt index 8d06d300a8..de7be70ac2 100644 --- a/samples/src/androidTest/java/com/squareup/sample/launcher/SampleLauncherTest.kt +++ b/samples/src/androidTest/java/com/squareup/sample/launcher/SampleLauncherTest.kt @@ -18,8 +18,8 @@ package com.squareup.sample.launcher import androidx.test.espresso.Espresso.pressBack import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry -import androidx.ui.test.android.createAndroidComposeRule import androidx.ui.test.assertIsDisplayed +import androidx.ui.test.createAndroidComposeRule import androidx.ui.test.onNodeWithText import androidx.ui.test.performClick import com.squareup.sample.R @@ -35,11 +35,11 @@ class SampleLauncherTest { @Test fun allSamplesLaunch() { val appName = InstrumentationRegistry.getInstrumentation().targetContext.getString(R.string.app_name) - onNodeWithText(appName).assertIsDisplayed() + composeRule.onNodeWithText(appName).assertIsDisplayed() samples.forEach { sample -> try { - onNodeWithText(sample.description, useUnmergedTree = true).performClick() + composeRule.onNodeWithText(sample.description, useUnmergedTree = true).performClick() pressBack() } catch (e: Throwable) { throw AssertionError("Failed to launch sample ${sample.name}", e) diff --git a/samples/src/androidTest/java/com/squareup/sample/nestedrenderings/NestedRenderingsTest.kt b/samples/src/androidTest/java/com/squareup/sample/nestedrenderings/NestedRenderingsTest.kt index daa8bab5c0..5934bd9199 100644 --- a/samples/src/androidTest/java/com/squareup/sample/nestedrenderings/NestedRenderingsTest.kt +++ b/samples/src/androidTest/java/com/squareup/sample/nestedrenderings/NestedRenderingsTest.kt @@ -18,9 +18,9 @@ package com.squareup.sample.nestedrenderings import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.ui.test.SemanticsNodeInteraction import androidx.ui.test.SemanticsNodeInteractionCollection -import androidx.ui.test.android.createAndroidComposeRule import androidx.ui.test.assertCountEquals import androidx.ui.test.assertIsDisplayed +import androidx.ui.test.createAndroidComposeRule import androidx.ui.test.onAllNodesWithText import androidx.ui.test.onNodeWithText import androidx.ui.test.performClick @@ -37,19 +37,19 @@ class NestedRenderingsTest { @Rule @JvmField val composeRule = createAndroidComposeRule() @Test fun childrenAreAddedAndRemoved() { - onNodeWithText(ADD_BUTTON_TEXT) + composeRule.onNodeWithText(ADD_BUTTON_TEXT) .assertIsDisplayed() .performClick() - onAllNodesWithText(ADD_BUTTON_TEXT) + composeRule.onAllNodesWithText(ADD_BUTTON_TEXT) .assertCountEquals(2) .forEach { it.performClick() } - onAllNodesWithText(ADD_BUTTON_TEXT) + composeRule.onAllNodesWithText(ADD_BUTTON_TEXT) .assertCountEquals(4) resetAll() - onAllNodesWithText(ADD_BUTTON_TEXT).assertCountEquals(1) + composeRule.onAllNodesWithText(ADD_BUTTON_TEXT).assertCountEquals(1) } /** @@ -61,7 +61,7 @@ class NestedRenderingsTest { var foundNodes = Int.MAX_VALUE while (foundNodes > 1) { foundNodes = 0 - onAllNodesWithText("Reset").forEach { + composeRule.onAllNodesWithText("Reset").forEach { try { it.assertExists() } catch (e: AssertionError) { diff --git a/samples/src/main/java/com/squareup/sample/launcher/SampleLauncherApp.kt b/samples/src/main/java/com/squareup/sample/launcher/SampleLauncherApp.kt index b0c424b1b3..3c97ebb2ef 100644 --- a/samples/src/main/java/com/squareup/sample/launcher/SampleLauncherApp.kt +++ b/samples/src/main/java/com/squareup/sample/launcher/SampleLauncherApp.kt @@ -18,9 +18,9 @@ package com.squareup.sample.launcher import android.content.Intent import android.os.Bundle import android.view.View -import androidx.compose.foundation.Box import androidx.compose.foundation.Text import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.width @@ -126,9 +126,10 @@ import com.squareup.sample.R.string // correctly too. .height(configuration.screenHeightDp.dp) .width(configuration.screenWidthDp.dp) - .drawLayer(scaleX = scale, scaleY = scale), - children = sample.preview - ) + .drawLayer(scaleX = scale, scaleY = scale) + ) { + sample.preview() + } } } } diff --git a/samples/src/main/java/com/squareup/sample/nestedrenderings/RecursiveViewFactory.kt b/samples/src/main/java/com/squareup/sample/nestedrenderings/RecursiveViewFactory.kt index e593d651a6..2b00ac2802 100644 --- a/samples/src/main/java/com/squareup/sample/nestedrenderings/RecursiveViewFactory.kt +++ b/samples/src/main/java/com/squareup/sample/nestedrenderings/RecursiveViewFactory.kt @@ -65,7 +65,7 @@ val RecursiveViewFactory = composedViewFactory { rendering, viewEnvir Column( Modifier.padding(dimensionResource(R.dimen.recursive_padding)) .fillMaxSize(), - horizontalGravity = CenterHorizontally + horizontalAlignment = CenterHorizontally ) { Providers(BackgroundColorAmbient provides childColor) { Children( @@ -108,7 +108,7 @@ val RecursiveViewFactory = composedViewFactory { rendering, viewEnvir Column( modifier = modifier, verticalArrangement = SpaceEvenly, - horizontalGravity = CenterHorizontally + horizontalAlignment = CenterHorizontally ) { children.forEach { childRendering -> WorkflowRendering( diff --git a/samples/src/main/java/com/squareup/sample/textinput/TextInputViewFactory.kt b/samples/src/main/java/com/squareup/sample/textinput/TextInputViewFactory.kt index 1a229a3fb1..b141c1855e 100644 --- a/samples/src/main/java/com/squareup/sample/textinput/TextInputViewFactory.kt +++ b/samples/src/main/java/com/squareup/sample/textinput/TextInputViewFactory.kt @@ -41,7 +41,7 @@ val TextInputViewFactory = composedViewFactory { rendering, _ -> .fillMaxSize() .wrapContentSize() .animateContentSize(clip = false), - horizontalGravity = Alignment.CenterHorizontally + horizontalAlignment = Alignment.CenterHorizontally ) { Text(text = rendering.text) OutlinedTextField( From dd6ff37640709a7b4ed87ea291255465706f6724 Mon Sep 17 00:00:00 2001 From: Zach Klippenstein Date: Tue, 13 Oct 2020 14:45:35 -0700 Subject: [PATCH 55/67] Remove coroutine forced dependency resolution. Preview works now. Closes #13. --- build.gradle.kts | 13 ------------- .../sample/textinput/TextInputViewFactory.kt | 3 ++- 2 files changed, 2 insertions(+), 14 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index b819077ffd..e3be246f0c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -52,19 +52,6 @@ subprojects { maven("https://dl.bintray.com/kotlin/kotlin-eap") } - configurations.all { - resolutionStrategy { - // There's an issue with running Compose @Previews in Android Studio with newer versions of - // the coroutines library (exception about an unresolved symbol). Until that's fixed, we have - // to force the coroutines version brought in by Workflows to 1.3.0. - // See https://github.com/square/workflow-kotlin-compose/issues/13. - force( - "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.0", - "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.0" - ) - } - } - apply(plugin = "org.jlleitschuh.gradle.ktlint") apply(plugin = "io.gitlab.arturbosch.detekt") afterEvaluate { diff --git a/samples/src/main/java/com/squareup/sample/textinput/TextInputViewFactory.kt b/samples/src/main/java/com/squareup/sample/textinput/TextInputViewFactory.kt index b141c1855e..18fc9704e9 100644 --- a/samples/src/main/java/com/squareup/sample/textinput/TextInputViewFactory.kt +++ b/samples/src/main/java/com/squareup/sample/textinput/TextInputViewFactory.kt @@ -57,7 +57,8 @@ val TextInputViewFactory = composedViewFactory { rendering, _ -> } } -@Preview @Composable private fun TextInputViewFactoryPreview() { +@Preview(showBackground = true) +@Composable private fun TextInputViewFactoryPreview() { TextInputViewFactory.preview(Rendering( text = "Hello world", onTextChanged = {}, From 80c4a7598b38a0fb9149efb46df1b22598aa554c Mon Sep 17 00:00:00 2001 From: Zach Klippenstein Date: Tue, 13 Oct 2020 12:43:10 -0700 Subject: [PATCH 56/67] Remove redundant parentheses after @Composable annotations. These used to be required because the compiler couldn't differentiate between () for annotation params vs () for a function type, but now it can. --- build.gradle.kts | 5 ++++- .../com/squareup/workflow/ui/compose/ComposeRendering.kt | 2 +- .../com/squareup/workflow/ui/compose/ComposeViewFactory.kt | 4 ++-- .../java/com/squareup/workflow/ui/compose/ComposeWorkflow.kt | 2 +- .../java/com/squareup/workflow/ui/compose/CompositionRoot.kt | 4 ++-- .../src/main/java/com/squareup/sample/launcher/Samples.kt | 2 +- 6 files changed, 11 insertions(+), 8 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index e3be246f0c..19d9f0bdcc 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -99,7 +99,10 @@ subprojects { setOf( // IntelliJ refuses to sort imports correctly. // This is a known issue: https://github.com/pinterest/ktlint/issues/527 - "import-ordering" + "import-ordering", + // Ktlint doesn't know how to handle nullary annotations on function types, e.g. + // @Composable () -> Unit. + "paren-spacing" ) ) } diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeRendering.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeRendering.kt index 9cc2308beb..a90681cc4a 100644 --- a/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeRendering.kt +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeRendering.kt @@ -32,7 +32,7 @@ import com.squareup.workflow.ui.ViewFactory * To use this type, make sure your `ViewRegistry` registers [Factory]. */ class ComposeRendering internal constructor( - internal val render: @Composable() (ViewEnvironment) -> Unit + internal val render: @Composable (ViewEnvironment) -> Unit ) { companion object { /** diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactory.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactory.kt index 65e52cdb9b..4b056e4934 100644 --- a/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactory.kt +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactory.kt @@ -80,7 +80,7 @@ import kotlin.reflect.KClass * with the [CompositionRoot]. See the documentation on [CompositionRoot] for more information. */ inline fun composedViewFactory( - noinline showRendering: @Composable() ( + noinline showRendering: @Composable ( rendering: RenderingT, environment: ViewEnvironment ) -> Unit @@ -89,7 +89,7 @@ inline fun composedViewFactory( @PublishedApi internal class ComposeViewFactory( override val type: KClass, - internal val content: @Composable() (RenderingT, ViewEnvironment) -> Unit + internal val content: @Composable (RenderingT, ViewEnvironment) -> Unit ) : ViewFactory { @OptIn(ExperimentalComposeApi::class) diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeWorkflow.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeWorkflow.kt index 6fa52f351a..726bc5e7c9 100644 --- a/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeWorkflow.kt +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeWorkflow.kt @@ -63,7 +63,7 @@ abstract class ComposeWorkflow : * Returns a [ComposeWorkflow] that renders itself using the given [render] function. */ inline fun Workflow.Companion.composed( - crossinline render: @Composable() ( + crossinline render: @Composable ( props: PropsT, outputSink: Sink, environment: ViewEnvironment diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/CompositionRoot.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/CompositionRoot.kt index 2576a0176f..c1325e45ec 100644 --- a/core-compose/src/main/java/com/squareup/workflow/ui/compose/CompositionRoot.kt +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/CompositionRoot.kt @@ -41,7 +41,7 @@ private val HasViewFactoryRootBeenApplied = staticAmbientOf { false } * However, ambients are propagated down to child [composedViewFactory] compositions, so any * ambients provided here will be available in _all_ [composedViewFactory] compositions. */ -typealias CompositionRoot = @Composable() (content: @Composable() () -> Unit) -> Unit +typealias CompositionRoot = @Composable (content: @Composable () -> Unit) -> Unit /** * Convenience function for applying a [CompositionRoot] to this [ViewEnvironment]'s [ViewRegistry]. @@ -75,7 +75,7 @@ fun ViewRegistry.withCompositionRoot(root: CompositionRoot): ViewRegistry = @VisibleForTesting(otherwise = PRIVATE) @Composable internal fun wrapWithRootIfNecessary( root: CompositionRoot, - content: @Composable() () -> Unit + content: @Composable () -> Unit ) { if (HasViewFactoryRootBeenApplied.current) { // The only way this ambient can have the value true is if, somewhere above this point in the diff --git a/samples/src/main/java/com/squareup/sample/launcher/Samples.kt b/samples/src/main/java/com/squareup/sample/launcher/Samples.kt index e8edfe8fdd..b2ae7a9933 100644 --- a/samples/src/main/java/com/squareup/sample/launcher/Samples.kt +++ b/samples/src/main/java/com/squareup/sample/launcher/Samples.kt @@ -58,5 +58,5 @@ data class Sample( val name: String, val activityClass: KClass, val description: String, - val preview: @Composable() () -> Unit + val preview: @Composable () -> Unit ) From 9c7aafb22ca49dfe087960f2b5da627c60135a4a Mon Sep 17 00:00:00 2001 From: Zach Klippenstein Date: Wed, 14 Oct 2020 11:29:14 -0700 Subject: [PATCH 57/67] Upgrade Compose to latest version. --- buildSrc/src/main/java/Dependencies.kt | 2 +- .../com/squareup/sample/launcher/SampleLauncherApp.kt | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt index 6a73106569..2b46accbb2 100644 --- a/buildSrc/src/main/java/Dependencies.kt +++ b/buildSrc/src/main/java/Dependencies.kt @@ -19,7 +19,7 @@ import java.util.Locale.US import kotlin.reflect.full.declaredMembers object Versions { - const val compose = "1.0.0-alpha04" + const val compose = "1.0.0-alpha05" const val kotlin = "1.4.10" // This *is* actually used. diff --git a/samples/src/main/java/com/squareup/sample/launcher/SampleLauncherApp.kt b/samples/src/main/java/com/squareup/sample/launcher/SampleLauncherApp.kt index 3c97ebb2ef..112e7aa5b3 100644 --- a/samples/src/main/java/com/squareup/sample/launcher/SampleLauncherApp.kt +++ b/samples/src/main/java/com/squareup/sample/launcher/SampleLauncherApp.kt @@ -42,7 +42,7 @@ import androidx.compose.ui.input.pointer.PointerEventPass.Initial import androidx.compose.ui.layout.LayoutCoordinates import androidx.compose.ui.layout.globalBounds import androidx.compose.ui.node.Ref -import androidx.compose.ui.onPositioned +import androidx.compose.ui.onGloballyPositioned import androidx.compose.ui.platform.ConfigurationAmbient import androidx.compose.ui.platform.ViewAmbient import androidx.compose.ui.res.stringResource @@ -76,8 +76,8 @@ import com.squareup.sample.R.string val rootView = ViewAmbient.current /** - * [androidx.ui.core.LayoutCoordinates.globalBounds] corresponds to the coordinates in the root - * Android view hosting the composition. + * [androidx.compose.ui.layout.LayoutCoordinates.globalBounds] corresponds to the coordinates in + * the root Android view hosting the composition. */ val globalBounds = remember { Ref() } @@ -110,7 +110,7 @@ import com.squareup.sample.R.string modifier = Modifier .height(previewHeight) .aspectRatio(screenRatio) - .onPositioned(onPreviewCoordinates) + .onGloballyPositioned(onPreviewCoordinates) ) { // Preview the samples with a light theme, since that's what most of them use. MaterialTheme(lightColors()) { From 96e76547eca893a038901f0f397670ae3d444deb Mon Sep 17 00:00:00 2001 From: Zach Klippenstein Date: Wed, 14 Oct 2020 14:08:52 -0700 Subject: [PATCH 58/67] Upgrade Gradle to latest version. --- gradle/wrapper/gradle-wrapper.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 33682bbbf9..14e30f7416 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From 3ead7dd67acb7ae6d0336266c2af557edaca744a Mon Sep 17 00:00:00 2001 From: Zach Klippenstein Date: Tue, 20 Oct 2020 12:37:26 -0700 Subject: [PATCH 59/67] Upgrade AGP to alpha14. --- buildSrc/src/main/java/Dependencies.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt index 2b46accbb2..0603ec5d0d 100644 --- a/buildSrc/src/main/java/Dependencies.kt +++ b/buildSrc/src/main/java/Dependencies.kt @@ -30,7 +30,7 @@ object Versions { @Suppress("unused") object Dependencies { - const val android_gradle_plugin = "com.android.tools.build:gradle:4.2.0-alpha13" + const val android_gradle_plugin = "com.android.tools.build:gradle:4.2.0-alpha14" object AndroidX { const val appcompat = "androidx.appcompat:appcompat:1.1.0" From 44dd2e68af1891e60e75a5065959eee323fd6d55 Mon Sep 17 00:00:00 2001 From: Zach Klippenstein Date: Tue, 27 Oct 2020 09:32:34 -0700 Subject: [PATCH 60/67] Upgrade AGP. --- buildSrc/src/main/java/Dependencies.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt index 0603ec5d0d..e245f4d3cb 100644 --- a/buildSrc/src/main/java/Dependencies.kt +++ b/buildSrc/src/main/java/Dependencies.kt @@ -30,7 +30,7 @@ object Versions { @Suppress("unused") object Dependencies { - const val android_gradle_plugin = "com.android.tools.build:gradle:4.2.0-alpha14" + const val android_gradle_plugin = "com.android.tools.build:gradle:4.2.0-alpha15" object AndroidX { const val appcompat = "androidx.appcompat:appcompat:1.1.0" From caf1d4af60b3f31fae0f19f2540410f588f5fde8 Mon Sep 17 00:00:00 2001 From: Zach Klippenstein Date: Wed, 28 Oct 2020 15:19:05 -0700 Subject: [PATCH 61/67] Upgrade compose to alpha06. --- buildSrc/src/main/java/Dependencies.kt | 2 +- .../workflow/ui/compose/tooling/PlaceholderViewFactory.kt | 4 ++-- .../com/squareup/workflow/ui/compose/ComposeViewFactory.kt | 5 ++--- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt index e245f4d3cb..9001482ae7 100644 --- a/buildSrc/src/main/java/Dependencies.kt +++ b/buildSrc/src/main/java/Dependencies.kt @@ -19,7 +19,7 @@ import java.util.Locale.US import kotlin.reflect.full.declaredMembers object Versions { - const val compose = "1.0.0-alpha05" + const val compose = "1.0.0-alpha06" const val kotlin = "1.4.10" // This *is* actually used. diff --git a/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/PlaceholderViewFactory.kt b/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/PlaceholderViewFactory.kt index c3e62f33b5..80ea7c2b5e 100644 --- a/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/PlaceholderViewFactory.kt +++ b/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/PlaceholderViewFactory.kt @@ -32,7 +32,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Paint import androidx.compose.ui.graphics.Shadow import androidx.compose.ui.graphics.drawscope.DrawScope -import androidx.compose.ui.graphics.drawscope.drawCanvas +import androidx.compose.ui.graphics.drawscope.drawIntoCanvas import androidx.compose.ui.graphics.drawscope.rotate import androidx.compose.ui.graphics.withSaveLayer import androidx.compose.ui.text.TextStyle @@ -53,7 +53,7 @@ internal fun placeholderViewFactory(modifier: Modifier): ViewFactory = modifier = modifier .clipToBounds() .drawBehind { - drawCanvas { canvas, size -> + drawIntoCanvas { canvas -> canvas.withSaveLayer(size.toRect(), Paint().apply { alpha = .2f }) { canvas.drawRect(size.toRect(), Paint().apply { color = Color.Gray }) drawCrossHatch( diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactory.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactory.kt index 4b056e4934..9f49a80210 100644 --- a/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactory.kt +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactory.kt @@ -24,7 +24,6 @@ import android.view.ViewGroup import android.widget.FrameLayout import androidx.compose.runtime.Composable import androidx.compose.runtime.ExperimentalComposeApi -import androidx.compose.runtime.Recomposer import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.platform.setContent import com.squareup.workflow.ui.ViewEnvironment @@ -115,7 +114,7 @@ internal class ComposeViewFactory( state.value = Pair(rendering, environment) } - composeContainer.setContent(Recomposer.current(), parentComposition = null) { + composeContainer.setContent { val (rendering, environment) = state.value content(rendering, environment) } @@ -131,7 +130,7 @@ internal class ComposeViewFactory( initialViewEnvironment ) { rendering, environment -> // Entry point to the world of Compose. - composeContainer.setContent(Recomposer.current(), parentComposition) { + composeContainer.setContent(parentComposition) { content(rendering, environment) } } From ec5695e67aa9fa015bd7505157804b0a0a72b7e9 Mon Sep 17 00:00:00 2001 From: Zach Klippenstein Date: Wed, 28 Oct 2020 15:27:12 -0700 Subject: [PATCH 62/67] Fix the samples not being full-size. --- samples/src/main/java/com/squareup/sample/hellocompose/App.kt | 1 - .../main/java/com/squareup/sample/hellocompose/HelloBinding.kt | 2 ++ .../sample/hellocomposerendering/HelloRenderingWorkflow.kt | 2 ++ 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/samples/src/main/java/com/squareup/sample/hellocompose/App.kt b/samples/src/main/java/com/squareup/sample/hellocompose/App.kt index 15774fc920..eeeeb076ba 100644 --- a/samples/src/main/java/com/squareup/sample/hellocompose/App.kt +++ b/samples/src/main/java/com/squareup/sample/hellocompose/App.kt @@ -45,7 +45,6 @@ private val viewEnvironment = ViewEnvironment(viewRegistry) } } -// This preview is broken in dev10, Compose runtime throws an ArrayIndexOutOfBoundsException. @Preview(showBackground = true) @Composable private fun AppPreview() { App() diff --git a/samples/src/main/java/com/squareup/sample/hellocompose/HelloBinding.kt b/samples/src/main/java/com/squareup/sample/hellocompose/HelloBinding.kt index 11bb358cc8..58667589f1 100644 --- a/samples/src/main/java/com/squareup/sample/hellocompose/HelloBinding.kt +++ b/samples/src/main/java/com/squareup/sample/hellocompose/HelloBinding.kt @@ -17,6 +17,7 @@ package com.squareup.sample.hellocompose import androidx.compose.foundation.Text import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -28,6 +29,7 @@ val HelloBinding = composedViewFactory { rendering, _ -> rendering.message, modifier = Modifier .clickable(onClick = rendering.onClick) + .fillMaxSize() .wrapContentSize(Alignment.Center) ) } diff --git a/samples/src/main/java/com/squareup/sample/hellocomposerendering/HelloRenderingWorkflow.kt b/samples/src/main/java/com/squareup/sample/hellocomposerendering/HelloRenderingWorkflow.kt index 877d2b20e4..49cf99c23a 100644 --- a/samples/src/main/java/com/squareup/sample/hellocomposerendering/HelloRenderingWorkflow.kt +++ b/samples/src/main/java/com/squareup/sample/hellocomposerendering/HelloRenderingWorkflow.kt @@ -17,6 +17,7 @@ package com.squareup.sample.hellocomposerendering import androidx.compose.foundation.Text import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.material.MaterialTheme import androidx.compose.runtime.Composable @@ -48,6 +49,7 @@ object HelloRenderingWorkflow : ComposeWorkflow() { props, modifier = Modifier .clickable(onClick = { outputSink.send(Toggle) }) + .fillMaxSize() .wrapContentSize(Alignment.Center) ) } From a883cff4d8bc992605e5e4cc7348dc05feddaf01 Mon Sep 17 00:00:00 2001 From: Zach Klippenstein Date: Thu, 12 Nov 2020 10:19:31 -0800 Subject: [PATCH 63/67] Upgrade Compose to alpha07. This seems to fix #79. --- .../configure-android-defaults.gradle | 2 ++ buildSrc/src/main/java/Dependencies.kt | 2 +- .../tooling/PreviewComposeWorkflowTest.kt | 14 ++++---- .../compose/tooling/PreviewViewFactoryTest.kt | 16 +++++----- .../compose/tooling/PlaceholderViewFactory.kt | 4 +-- core-compose/api/core-compose.api | 2 ++ .../ui/compose/ComposeViewFactoryTest.kt | 8 ++--- .../ui/compose/CompositionRootTest.kt | 32 +++++++++---------- .../ui/compose/ViewEnvironmentsTest.kt | 10 +++--- .../ui/compose/WorkflowContainerTest.kt | 12 ++++--- .../ui/compose/internal/ViewFactoriesTest.kt | 10 +++--- .../hellocomposebinding/HelloBindingTest.kt | 8 ++--- .../HelloComposeRenderingTest.kt | 8 ++--- .../sample/launcher/SampleLauncherTest.kt | 11 ++++--- .../nestedrenderings/NestedRenderingsTest.kt | 16 +++++----- .../sample/hellocompose/HelloBinding.kt | 2 +- .../hellocomposebinding/HelloBinding.kt | 2 +- .../HelloRenderingWorkflow.kt | 2 +- .../sample/launcher/SampleLauncherApp.kt | 4 +-- .../nestedrenderings/RecursiveViewFactory.kt | 2 +- .../sample/textinput/TextInputViewFactory.kt | 2 +- 21 files changed, 88 insertions(+), 81 deletions(-) diff --git a/.buildscript/configure-android-defaults.gradle b/.buildscript/configure-android-defaults.gradle index c88f8c4a80..87365868ca 100644 --- a/.buildscript/configure-android-defaults.gradle +++ b/.buildscript/configure-android-defaults.gradle @@ -18,6 +18,8 @@ android { // See https://github.com/Kotlin/kotlinx.coroutines/issues/1064#issuecomment-479412940 packagingOptions { exclude 'META-INF/*.kotlin_module' + exclude 'META-INF/AL2.0' + exclude 'META-INF/LGPL2.1' } lintOptions { diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt index 9001482ae7..d76750692b 100644 --- a/buildSrc/src/main/java/Dependencies.kt +++ b/buildSrc/src/main/java/Dependencies.kt @@ -19,7 +19,7 @@ import java.util.Locale.US import kotlin.reflect.full.declaredMembers object Versions { - const val compose = "1.0.0-alpha06" + const val compose = "1.0.0-alpha07" const val kotlin = "1.4.10" // This *is* actually used. diff --git a/compose-tooling/src/androidTest/java/com/squareup/workflow/ui/compose/tooling/PreviewComposeWorkflowTest.kt b/compose-tooling/src/androidTest/java/com/squareup/workflow/ui/compose/tooling/PreviewComposeWorkflowTest.kt index 82022034af..1f86e5a9e8 100644 --- a/compose-tooling/src/androidTest/java/com/squareup/workflow/ui/compose/tooling/PreviewComposeWorkflowTest.kt +++ b/compose-tooling/src/androidTest/java/com/squareup/workflow/ui/compose/tooling/PreviewComposeWorkflowTest.kt @@ -17,17 +17,17 @@ package com.squareup.workflow.ui.compose.tooling -import androidx.compose.foundation.Text import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.size +import androidx.compose.foundation.text.BasicText import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.assertIsNotDisplayed +import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.unit.dp import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.ui.test.assertIsDisplayed -import androidx.ui.test.assertIsNotDisplayed import androidx.ui.test.createComposeRule -import androidx.ui.test.onNodeWithText import androidx.ui.tooling.preview.Preview import com.squareup.workflow.Workflow import com.squareup.workflow.ui.ViewEnvironmentKey @@ -95,7 +95,7 @@ class PreviewComposeWorkflowTest { private val ParentWithOneChild = Workflow.composed, Nothing> { props, _, environment -> Column { - Text(props.first) + BasicText(props.first) WorkflowRendering(props.second, environment) } } @@ -108,7 +108,7 @@ class PreviewComposeWorkflowTest { Workflow.composed, Nothing> { props, _, environment -> Column { WorkflowRendering(rendering = props.first, viewEnvironment = environment) - Text(props.second) + BasicText(props.second) WorkflowRendering(rendering = props.third, viewEnvironment = environment) } } @@ -136,7 +136,7 @@ class PreviewComposeWorkflowTest { } private val ParentConsumesCustomKey = Workflow.composed { _, _, environment -> - Text(environment[TestEnvironmentKey]) + BasicText(environment[TestEnvironmentKey]) } @Preview @Composable private fun ParentConsumesCustomKeyPreview() { diff --git a/compose-tooling/src/androidTest/java/com/squareup/workflow/ui/compose/tooling/PreviewViewFactoryTest.kt b/compose-tooling/src/androidTest/java/com/squareup/workflow/ui/compose/tooling/PreviewViewFactoryTest.kt index 8011296074..d39bb091ef 100644 --- a/compose-tooling/src/androidTest/java/com/squareup/workflow/ui/compose/tooling/PreviewViewFactoryTest.kt +++ b/compose-tooling/src/androidTest/java/com/squareup/workflow/ui/compose/tooling/PreviewViewFactoryTest.kt @@ -17,17 +17,17 @@ package com.squareup.workflow.ui.compose.tooling -import androidx.compose.foundation.Text import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.size +import androidx.compose.foundation.text.BasicText import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.assertIsNotDisplayed +import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.unit.dp import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.ui.test.assertIsDisplayed -import androidx.ui.test.assertIsNotDisplayed import androidx.ui.test.createComposeRule -import androidx.ui.test.onNodeWithText import androidx.ui.tooling.preview.Preview import com.squareup.workflow.ui.ViewEnvironmentKey import com.squareup.workflow.ui.compose.WorkflowRendering @@ -101,7 +101,7 @@ class PreviewViewFactoryTest { private val ParentWithOneChild = composedViewFactory> { rendering, environment -> Column { - Text(rendering.first) + BasicText(rendering.first) WorkflowRendering(rendering.second, environment) } } @@ -114,7 +114,7 @@ class PreviewViewFactoryTest { composedViewFactory> { rendering, environment -> Column { WorkflowRendering(rendering.first, environment) - Text(rendering.second) + BasicText(rendering.second) WorkflowRendering(rendering.third, environment) } } @@ -130,7 +130,7 @@ class PreviewViewFactoryTest { private val ParentRecursive = composedViewFactory { rendering, environment -> Column { - Text(rendering.text) + BasicText(rendering.text) rendering.child?.let { child -> WorkflowRendering(rendering = child, viewEnvironment = environment) } @@ -168,7 +168,7 @@ class PreviewViewFactoryTest { } private val ParentConsumesCustomKey = composedViewFactory { _, environment -> - Text(environment[TestEnvironmentKey]) + BasicText(environment[TestEnvironmentKey]) } @Preview @Composable private fun ParentConsumesCustomKeyPreview() { diff --git a/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/PlaceholderViewFactory.kt b/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/PlaceholderViewFactory.kt index 80ea7c2b5e..7e623ac053 100644 --- a/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/PlaceholderViewFactory.kt +++ b/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/PlaceholderViewFactory.kt @@ -17,11 +17,11 @@ package com.squareup.workflow.ui.compose.tooling -import androidx.compose.foundation.Text import androidx.compose.foundation.background import androidx.compose.foundation.drawBorder import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.text.BasicText import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clipToBounds @@ -49,7 +49,7 @@ import com.squareup.workflow.ui.compose.composedViewFactory */ internal fun placeholderViewFactory(modifier: Modifier): ViewFactory = composedViewFactory { rendering, _ -> - Text( + BasicText( modifier = modifier .clipToBounds() .drawBehind { diff --git a/core-compose/api/core-compose.api b/core-compose/api/core-compose.api index af0574a288..3ab1ac14e3 100644 --- a/core-compose/api/core-compose.api +++ b/core-compose/api/core-compose.api @@ -1,4 +1,5 @@ public final class com/squareup/workflow/ui/compose/ComposeRendering { + public static final field $stable I public static final field Companion Lcom/squareup/workflow/ui/compose/ComposeRendering$Companion; public fun (Lkotlin/jvm/functions/Function3;)V } @@ -15,6 +16,7 @@ public final class com/squareup/workflow/ui/compose/ComposeViewFactory : com/squ } public abstract class com/squareup/workflow/ui/compose/ComposeWorkflow : com/squareup/workflow/Workflow { + public static final field $stable I public fun ()V public fun asStatefulWorkflow ()Lcom/squareup/workflow/StatefulWorkflow; public abstract fun render (Ljava/lang/Object;Lcom/squareup/workflow/Sink;Lcom/squareup/workflow/ui/ViewEnvironment;Landroidx/compose/runtime/Composer;I)V diff --git a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/ComposeViewFactoryTest.kt b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/ComposeViewFactoryTest.kt index a98131b2ce..90450d6182 100644 --- a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/ComposeViewFactoryTest.kt +++ b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/ComposeViewFactoryTest.kt @@ -17,13 +17,13 @@ package com.squareup.workflow.ui.compose import android.content.Context import android.widget.FrameLayout -import androidx.compose.foundation.Text import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.text.BasicText import androidx.compose.runtime.mutableStateOf +import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.viewinterop.AndroidView import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.ui.test.createComposeRule -import androidx.ui.test.onNodeWithText import com.squareup.workflow.ui.ViewEnvironment import com.squareup.workflow.ui.ViewRegistry import com.squareup.workflow.ui.WorkflowViewStub @@ -41,7 +41,7 @@ class ComposeViewFactoryTest { val viewEnvironment = ViewEnvironment(ViewRegistry(TestFactory)) .withCompositionRoot { content -> Column { - Text(wrapperText.value) + BasicText(wrapperText.value) content() } } @@ -75,7 +75,7 @@ class ComposeViewFactoryTest { private companion object { val TestFactory = composedViewFactory { rendering, _ -> - Text(rendering.text) + BasicText(rendering.text) } } } diff --git a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/CompositionRootTest.kt b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/CompositionRootTest.kt index c91f6ed2b2..eba09624ec 100644 --- a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/CompositionRootTest.kt +++ b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/CompositionRootTest.kt @@ -15,13 +15,13 @@ */ package com.squareup.workflow.ui.compose -import androidx.compose.foundation.Text import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.text.BasicText import androidx.compose.runtime.mutableStateOf +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.onNodeWithText import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.ui.test.assertIsDisplayed import androidx.ui.test.createComposeRule -import androidx.ui.test.onNodeWithText import com.google.common.truth.Truth.assertThat import org.junit.Rule import org.junit.Test @@ -36,14 +36,14 @@ class CompositionRootTest { @Test fun wrapWithRootIfNecessary_wrapsWhenNecessary() { val root: CompositionRoot = { content -> Column { - Text("one") + BasicText("one") content() } } composeRule.setContent { wrapWithRootIfNecessary(root) { - Text("two") + BasicText("two") } } @@ -56,16 +56,16 @@ class CompositionRootTest { @Test fun wrapWithRootIfNecessary_onlyWrapsOnce() { val root: CompositionRoot = { content -> Column { - Text("one") + BasicText("one") content() } } composeRule.setContent { wrapWithRootIfNecessary(root) { - Text("two") + BasicText("two") wrapWithRootIfNecessary(root) { - Text("three") + BasicText("three") } } } @@ -79,14 +79,14 @@ class CompositionRootTest { val wrapperText = mutableStateOf("one") val root: CompositionRoot = { content -> Column { - Text(wrapperText.value) + BasicText(wrapperText.value) content() } } composeRule.setContent { wrapWithRootIfNecessary(root) { - Text("two") + BasicText("two") } } @@ -100,13 +100,13 @@ class CompositionRootTest { @Test fun wrapWithRootIfNecessary_rewrapsWhenDifferentRoot() { val root1: CompositionRoot = { content -> Column { - Text("one") + BasicText("one") content() } } val root2: CompositionRoot = { content -> Column { - Text("ENO") + BasicText("ENO") content() } } @@ -114,7 +114,7 @@ class CompositionRootTest { composeRule.setContent { wrapWithRootIfNecessary(viewEnvironment.value) { - Text("two") + BasicText("two") } } @@ -128,7 +128,7 @@ class CompositionRootTest { @Test fun safeComposeViewFactoryRoot_wraps_content() { val wrapped: CompositionRoot = { content -> Column { - Text("Parent") + BasicText("Parent") content() } } @@ -136,7 +136,7 @@ class CompositionRootTest { composeRule.setContent { safeRoot { - Text("Child") + BasicText("Child") } } @@ -170,7 +170,7 @@ class CompositionRootTest { val error = assertFailsWith { composeRule.setContent { safeRoot { - Text("Hello") + BasicText("Hello") } } } diff --git a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/ViewEnvironmentsTest.kt b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/ViewEnvironmentsTest.kt index 7b4255e857..2328505394 100644 --- a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/ViewEnvironmentsTest.kt +++ b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/ViewEnvironmentsTest.kt @@ -15,12 +15,12 @@ */ package com.squareup.workflow.ui.compose -import androidx.compose.foundation.Text +import androidx.compose.foundation.text.BasicText import androidx.compose.runtime.mutableStateOf +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.onNodeWithText import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.ui.test.assertIsDisplayed import androidx.ui.test.createComposeRule -import androidx.ui.test.onNodeWithText import com.squareup.workflow.ui.ViewEnvironment import com.squareup.workflow.ui.ViewRegistry import org.junit.Rule @@ -34,10 +34,10 @@ class ViewEnvironmentsTest { @Test fun workflowRendering_recomposes_whenFactoryChanged() { val registry1 = ViewRegistry(composedViewFactory { rendering, _ -> - Text(rendering) + BasicText(rendering) }) val registry2 = ViewRegistry(composedViewFactory { rendering, _ -> - Text(rendering.reversed()) + BasicText(rendering.reversed()) }) val registry = mutableStateOf(registry1) diff --git a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/WorkflowContainerTest.kt b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/WorkflowContainerTest.kt index c98afb57f2..85f1dedcaf 100644 --- a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/WorkflowContainerTest.kt +++ b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/WorkflowContainerTest.kt @@ -17,11 +17,11 @@ package com.squareup.workflow.ui.compose -import androidx.compose.foundation.Text +import androidx.compose.foundation.text.BasicText +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.onNodeWithText import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.ui.test.assertIsDisplayed import androidx.ui.test.createComposeRule -import androidx.ui.test.onNodeWithText import com.squareup.workflow.Workflow import com.squareup.workflow.stateless import com.squareup.workflow.ui.ViewEnvironment @@ -37,7 +37,9 @@ class WorkflowContainerTest { @Test fun rendersFromViewRegistry() { val workflow = Workflow.stateless { "hello" } - val registry = ViewRegistry(composedViewFactory { rendering, _ -> Text(rendering) }) + val registry = ViewRegistry(composedViewFactory { rendering, _ -> + BasicText(rendering) + }) composeRule.setContent { WorkflowContainer(workflow, ViewEnvironment(registry)) @@ -48,7 +50,7 @@ class WorkflowContainerTest { @Test fun automaticallyAddsComposeRenderingFactory() { val workflow = Workflow.composed { _, _, _ -> - Text("it worked") + BasicText("it worked") } val registry = ViewRegistry() diff --git a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/internal/ViewFactoriesTest.kt b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/internal/ViewFactoriesTest.kt index 8015971933..92328e7fd1 100644 --- a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/internal/ViewFactoriesTest.kt +++ b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/internal/ViewFactoriesTest.kt @@ -19,17 +19,17 @@ import android.content.Context import android.view.View import android.view.ViewGroup import android.widget.TextView -import androidx.compose.foundation.Text import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.text.BasicText import androidx.compose.runtime.mutableStateOf +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.onNodeWithText import androidx.test.espresso.Espresso.onView import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.matcher.ViewMatchers.isDisplayed import androidx.test.espresso.matcher.ViewMatchers.withText import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.ui.test.assertIsDisplayed import androidx.ui.test.createComposeRule -import androidx.ui.test.onNodeWithText import com.squareup.workflow.ui.ViewEnvironment import com.squareup.workflow.ui.ViewFactory import com.squareup.workflow.ui.ViewRegistry @@ -50,7 +50,7 @@ class ViewFactoriesTest { val viewEnvironment = ViewEnvironment(ViewRegistry(TestFactory)) .withCompositionRoot { content -> Column { - Text("one") + BasicText("one") content() } } @@ -81,7 +81,7 @@ class ViewFactoriesTest { private companion object { val TestFactory = composedViewFactory { rendering, _ -> - Text(rendering.text) + BasicText(rendering.text) } val LegacyViewViewFactory = object : ViewFactory { override val type = LegacyViewRendering::class diff --git a/samples/src/androidTest/java/com/squareup/sample/hellocomposebinding/HelloBindingTest.kt b/samples/src/androidTest/java/com/squareup/sample/hellocomposebinding/HelloBindingTest.kt index 90ac58022a..74f90bd7ed 100644 --- a/samples/src/androidTest/java/com/squareup/sample/hellocomposebinding/HelloBindingTest.kt +++ b/samples/src/androidTest/java/com/squareup/sample/hellocomposebinding/HelloBindingTest.kt @@ -15,11 +15,11 @@ */ package com.squareup.sample.hellocomposebinding +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performClick import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.ui.test.assertIsDisplayed -import androidx.ui.test.createAndroidComposeRule -import androidx.ui.test.onNodeWithText -import androidx.ui.test.performClick import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith diff --git a/samples/src/androidTest/java/com/squareup/sample/hellocomposerendering/HelloComposeRenderingTest.kt b/samples/src/androidTest/java/com/squareup/sample/hellocomposerendering/HelloComposeRenderingTest.kt index b3a1036756..44b2048e23 100644 --- a/samples/src/androidTest/java/com/squareup/sample/hellocomposerendering/HelloComposeRenderingTest.kt +++ b/samples/src/androidTest/java/com/squareup/sample/hellocomposerendering/HelloComposeRenderingTest.kt @@ -15,11 +15,11 @@ */ package com.squareup.sample.hellocomposerendering +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performClick import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.ui.test.assertIsDisplayed -import androidx.ui.test.createAndroidComposeRule -import androidx.ui.test.onNodeWithText -import androidx.ui.test.performClick import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith diff --git a/samples/src/androidTest/java/com/squareup/sample/launcher/SampleLauncherTest.kt b/samples/src/androidTest/java/com/squareup/sample/launcher/SampleLauncherTest.kt index de7be70ac2..699ba80a8a 100644 --- a/samples/src/androidTest/java/com/squareup/sample/launcher/SampleLauncherTest.kt +++ b/samples/src/androidTest/java/com/squareup/sample/launcher/SampleLauncherTest.kt @@ -15,13 +15,13 @@ */ package com.squareup.sample.launcher +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performClick import androidx.test.espresso.Espresso.pressBack import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry -import androidx.ui.test.assertIsDisplayed -import androidx.ui.test.createAndroidComposeRule -import androidx.ui.test.onNodeWithText -import androidx.ui.test.performClick import com.squareup.sample.R import org.junit.Rule import org.junit.Test @@ -39,7 +39,8 @@ class SampleLauncherTest { samples.forEach { sample -> try { - composeRule.onNodeWithText(sample.description, useUnmergedTree = true).performClick() + composeRule.onNodeWithText(sample.description, useUnmergedTree = true) + .performClick() pressBack() } catch (e: Throwable) { throw AssertionError("Failed to launch sample ${sample.name}", e) diff --git a/samples/src/androidTest/java/com/squareup/sample/nestedrenderings/NestedRenderingsTest.kt b/samples/src/androidTest/java/com/squareup/sample/nestedrenderings/NestedRenderingsTest.kt index 5934bd9199..ffa1073733 100644 --- a/samples/src/androidTest/java/com/squareup/sample/nestedrenderings/NestedRenderingsTest.kt +++ b/samples/src/androidTest/java/com/squareup/sample/nestedrenderings/NestedRenderingsTest.kt @@ -15,15 +15,15 @@ */ package com.squareup.sample.nestedrenderings +import androidx.compose.ui.test.SemanticsNodeInteraction +import androidx.compose.ui.test.SemanticsNodeInteractionCollection +import androidx.compose.ui.test.assertCountEquals +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.onAllNodesWithText +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performClick import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.ui.test.SemanticsNodeInteraction -import androidx.ui.test.SemanticsNodeInteractionCollection -import androidx.ui.test.assertCountEquals -import androidx.ui.test.assertIsDisplayed -import androidx.ui.test.createAndroidComposeRule -import androidx.ui.test.onAllNodesWithText -import androidx.ui.test.onNodeWithText -import androidx.ui.test.performClick import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith diff --git a/samples/src/main/java/com/squareup/sample/hellocompose/HelloBinding.kt b/samples/src/main/java/com/squareup/sample/hellocompose/HelloBinding.kt index 58667589f1..3a83df292b 100644 --- a/samples/src/main/java/com/squareup/sample/hellocompose/HelloBinding.kt +++ b/samples/src/main/java/com/squareup/sample/hellocompose/HelloBinding.kt @@ -15,7 +15,7 @@ */ package com.squareup.sample.hellocompose -import androidx.compose.foundation.Text +import androidx.compose.material.Text import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.wrapContentSize diff --git a/samples/src/main/java/com/squareup/sample/hellocomposebinding/HelloBinding.kt b/samples/src/main/java/com/squareup/sample/hellocomposebinding/HelloBinding.kt index 5fd157b2f6..cf0b5f11b1 100644 --- a/samples/src/main/java/com/squareup/sample/hellocomposebinding/HelloBinding.kt +++ b/samples/src/main/java/com/squareup/sample/hellocomposebinding/HelloBinding.kt @@ -15,7 +15,7 @@ */ package com.squareup.sample.hellocomposebinding -import androidx.compose.foundation.Text +import androidx.compose.material.Text import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.wrapContentSize diff --git a/samples/src/main/java/com/squareup/sample/hellocomposerendering/HelloRenderingWorkflow.kt b/samples/src/main/java/com/squareup/sample/hellocomposerendering/HelloRenderingWorkflow.kt index 49cf99c23a..0e8d2326f8 100644 --- a/samples/src/main/java/com/squareup/sample/hellocomposerendering/HelloRenderingWorkflow.kt +++ b/samples/src/main/java/com/squareup/sample/hellocomposerendering/HelloRenderingWorkflow.kt @@ -15,7 +15,7 @@ */ package com.squareup.sample.hellocomposerendering -import androidx.compose.foundation.Text +import androidx.compose.material.Text import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.wrapContentSize diff --git a/samples/src/main/java/com/squareup/sample/launcher/SampleLauncherApp.kt b/samples/src/main/java/com/squareup/sample/launcher/SampleLauncherApp.kt index 112e7aa5b3..b7930ad2a1 100644 --- a/samples/src/main/java/com/squareup/sample/launcher/SampleLauncherApp.kt +++ b/samples/src/main/java/com/squareup/sample/launcher/SampleLauncherApp.kt @@ -18,7 +18,6 @@ package com.squareup.sample.launcher import android.content.Intent import android.os.Bundle import android.view.View -import androidx.compose.foundation.Text import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.aspectRatio @@ -29,6 +28,7 @@ import androidx.compose.material.ListItem import androidx.compose.material.MaterialTheme import androidx.compose.material.Scaffold import androidx.compose.material.Surface +import androidx.compose.material.Text import androidx.compose.material.TopAppBar import androidx.compose.material.darkColors import androidx.compose.material.lightColors @@ -41,8 +41,8 @@ import androidx.compose.ui.gesture.rawPressStartGestureFilter import androidx.compose.ui.input.pointer.PointerEventPass.Initial import androidx.compose.ui.layout.LayoutCoordinates import androidx.compose.ui.layout.globalBounds +import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.node.Ref -import androidx.compose.ui.onGloballyPositioned import androidx.compose.ui.platform.ConfigurationAmbient import androidx.compose.ui.platform.ViewAmbient import androidx.compose.ui.res.stringResource diff --git a/samples/src/main/java/com/squareup/sample/nestedrenderings/RecursiveViewFactory.kt b/samples/src/main/java/com/squareup/sample/nestedrenderings/RecursiveViewFactory.kt index 2b00ac2802..4216ad97ce 100644 --- a/samples/src/main/java/com/squareup/sample/nestedrenderings/RecursiveViewFactory.kt +++ b/samples/src/main/java/com/squareup/sample/nestedrenderings/RecursiveViewFactory.kt @@ -17,7 +17,7 @@ package com.squareup.sample.nestedrenderings -import androidx.compose.foundation.Text +import androidx.compose.material.Text import androidx.compose.foundation.layout.Arrangement.SpaceEvenly import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ExperimentalLayout diff --git a/samples/src/main/java/com/squareup/sample/textinput/TextInputViewFactory.kt b/samples/src/main/java/com/squareup/sample/textinput/TextInputViewFactory.kt index 18fc9704e9..f3bc5463e8 100644 --- a/samples/src/main/java/com/squareup/sample/textinput/TextInputViewFactory.kt +++ b/samples/src/main/java/com/squareup/sample/textinput/TextInputViewFactory.kt @@ -16,7 +16,7 @@ package com.squareup.sample.textinput import androidx.compose.animation.animateContentSize -import androidx.compose.foundation.Text +import androidx.compose.material.Text import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize From d47c0aedfd0a4e475a2e75e88e64d1cc9f103117 Mon Sep 17 00:00:00 2001 From: Zach Klippenstein Date: Fri, 8 Jan 2021 10:23:38 -0800 Subject: [PATCH 64/67] Update indentation to match latest code style. --- build.gradle.kts | 24 +++---- buildSrc/src/main/java/Dependencies.kt | 4 +- compose-tooling/build.gradle.kts | 1 - .../tooling/PreviewComposeWorkflowTest.kt | 8 +-- .../compose/tooling/PreviewViewFactoryTest.kt | 20 +++--- .../compose/tooling/PlaceholderViewFactory.kt | 56 ++++++++--------- core-compose/build.gradle.kts | 2 - .../ui/compose/ComposeViewFactoryTest.kt | 10 +-- .../ui/compose/CompositionRootTest.kt | 12 ++-- .../workflow/ui/compose/RenderAsStateTest.kt | 26 ++++---- .../ui/compose/internal/ViewFactoriesTest.kt | 10 +-- .../workflow/ui/compose/ComposeViewFactory.kt | 8 +-- .../workflow/ui/compose/ComposeWorkflow.kt | 2 +- .../workflow/ui/compose/RenderAsState.kt | 18 +++--- .../workflow/ui/compose/WorkflowContainer.kt | 4 +- .../ui/compose/internal/ViewFactories.kt | 2 +- samples/build.gradle.kts | 2 - .../hellocomposebinding/HelloBindingTest.kt | 10 +-- .../HelloComposeRenderingTest.kt | 10 +-- .../sample/launcher/SampleLauncherTest.kt | 2 +- .../nestedrenderings/NestedRenderingsTest.kt | 10 +-- .../com/squareup/sample/hellocompose/App.kt | 14 ++--- .../sample/hellocompose/HelloBinding.kt | 10 +-- .../sample/hellocompose/HelloWorkflow.kt | 6 +- .../hellocomposebinding/HelloBinding.kt | 8 +-- .../HelloBindingActivity.kt | 4 +- .../hellocomposebinding/HelloWorkflow.kt | 6 +- .../HelloComposeRenderingActivity.kt | 4 +- .../HelloRenderingWorkflow.kt | 10 +-- .../hellocomposerendering/HelloWorkflow.kt | 2 +- .../sample/launcher/SampleLauncherApp.kt | 62 +++++++++---------- .../com/squareup/sample/launcher/Samples.kt | 40 ++++++------ .../sample/nestedrenderings/LegacyRunner.kt | 6 +- .../NestedRenderingsActivity.kt | 8 +-- .../nestedrenderings/RecursiveViewFactory.kt | 62 +++++++++---------- .../nestedrenderings/RecursiveWorkflow.kt | 12 ++-- .../java/com/squareup/sample/textinput/App.kt | 4 +- .../sample/textinput/TextInputViewFactory.kt | 24 +++---- .../sample/textinput/TextInputWorkflow.kt | 6 +- settings.gradle.kts | 6 +- 40 files changed, 265 insertions(+), 270 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 19d9f0bdcc..24495fddbc 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -56,7 +56,7 @@ subprojects { apply(plugin = "io.gitlab.arturbosch.detekt") afterEvaluate { tasks.findByName("check") - ?.dependsOn("detekt") + ?.dependsOn("detekt") configurations.configureEach { // There could be transitive dependencies in tests with a lower version. This could cause @@ -78,9 +78,9 @@ subprojects { // to define our own experiments, and some required args for compose dev15 taken from // https://developer.android.com/jetpack/androidx/releases/compose-runtime freeCompilerArgs += listOf( - "-Xopt-in=kotlin.RequiresOptIn", - "-Xallow-jvm-ir-dependencies", - "-Xskip-prerelease-check" + "-Xopt-in=kotlin.RequiresOptIn", + "-Xallow-jvm-ir-dependencies", + "-Xskip-prerelease-check" ) } @@ -96,14 +96,14 @@ subprojects { } disabledRules.set( - setOf( - // IntelliJ refuses to sort imports correctly. - // This is a known issue: https://github.com/pinterest/ktlint/issues/527 - "import-ordering", - // Ktlint doesn't know how to handle nullary annotations on function types, e.g. - // @Composable () -> Unit. - "paren-spacing" - ) + setOf( + // IntelliJ refuses to sort imports correctly. + // This is a known issue: https://github.com/pinterest/ktlint/issues/527 + "import-ordering", + // Ktlint doesn't know how to handle nullary annotations on function types, e.g. + // @Composable () -> Unit. + "paren-spacing" + ) ) } diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt index d76750692b..ecdf40e449 100644 --- a/buildSrc/src/main/java/Dependencies.kt +++ b/buildSrc/src/main/java/Dependencies.kt @@ -99,8 +99,8 @@ object Dependencies { @JvmName("get") fun getDependencyFromGroovy(path: String): String = try { Dependencies.resolveObject( - path.toLowerCase(US) - .split(".") + path.toLowerCase(US) + .split(".") ) } catch (e: Throwable) { throw IllegalArgumentException("Error resolving dependency: $path", e) diff --git a/compose-tooling/build.gradle.kts b/compose-tooling/build.gradle.kts index 999c2b632d..fe90d6941a 100644 --- a/compose-tooling/build.gradle.kts +++ b/compose-tooling/build.gradle.kts @@ -1,4 +1,3 @@ - /* * Copyright 2020 Square Inc. * diff --git a/compose-tooling/src/androidTest/java/com/squareup/workflow/ui/compose/tooling/PreviewComposeWorkflowTest.kt b/compose-tooling/src/androidTest/java/com/squareup/workflow/ui/compose/tooling/PreviewComposeWorkflowTest.kt index 1f86e5a9e8..84b38af755 100644 --- a/compose-tooling/src/androidTest/java/com/squareup/workflow/ui/compose/tooling/PreviewComposeWorkflowTest.kt +++ b/compose-tooling/src/androidTest/java/com/squareup/workflow/ui/compose/tooling/PreviewComposeWorkflowTest.kt @@ -119,15 +119,15 @@ class PreviewComposeWorkflowTest { @Preview @Composable private fun ParentWithModifier() { ParentWithOneChild.preview( - Pair("one", "two"), - modifier = Modifier.size(0.dp) + Pair("one", "two"), + modifier = Modifier.size(0.dp) ) } @Preview @Composable private fun ParentWithPlaceholderModifier() { ParentWithOneChild.preview( - Pair("one", "two"), - placeholderModifier = Modifier.size(0.dp) + Pair("one", "two"), + placeholderModifier = Modifier.size(0.dp) ) } diff --git a/compose-tooling/src/androidTest/java/com/squareup/workflow/ui/compose/tooling/PreviewViewFactoryTest.kt b/compose-tooling/src/androidTest/java/com/squareup/workflow/ui/compose/tooling/PreviewViewFactoryTest.kt index d39bb091ef..de471822e9 100644 --- a/compose-tooling/src/androidTest/java/com/squareup/workflow/ui/compose/tooling/PreviewViewFactoryTest.kt +++ b/compose-tooling/src/androidTest/java/com/squareup/workflow/ui/compose/tooling/PreviewViewFactoryTest.kt @@ -139,27 +139,27 @@ class PreviewViewFactoryTest { @Preview @Composable private fun ParentRecursivePreview() { ParentRecursive.preview( - RecursiveRendering( - text = "one", - child = RecursiveRendering( - text = "two", - child = RecursiveRendering(text = "three") - ) + RecursiveRendering( + text = "one", + child = RecursiveRendering( + text = "two", + child = RecursiveRendering(text = "three") ) + ) ) } @Preview @Composable private fun ParentWithModifier() { ParentWithOneChild.preview( - Pair("one", "two"), - modifier = Modifier.size(0.dp) + Pair("one", "two"), + modifier = Modifier.size(0.dp) ) } @Preview @Composable private fun ParentWithPlaceholderModifier() { ParentWithOneChild.preview( - Pair("one", "two"), - placeholderModifier = Modifier.size(0.dp) + Pair("one", "two"), + placeholderModifier = Modifier.size(0.dp) ) } diff --git a/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/PlaceholderViewFactory.kt b/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/PlaceholderViewFactory.kt index 7e623ac053..5623586037 100644 --- a/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/PlaceholderViewFactory.kt +++ b/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/PlaceholderViewFactory.kt @@ -50,27 +50,27 @@ import com.squareup.workflow.ui.compose.composedViewFactory internal fun placeholderViewFactory(modifier: Modifier): ViewFactory = composedViewFactory { rendering, _ -> BasicText( - modifier = modifier - .clipToBounds() - .drawBehind { - drawIntoCanvas { canvas -> - canvas.withSaveLayer(size.toRect(), Paint().apply { alpha = .2f }) { - canvas.drawRect(size.toRect(), Paint().apply { color = Color.Gray }) - drawCrossHatch( - color = Color.Red, - strokeWidth = 2.dp, - spaceWidth = 5.dp, - angle = 45f - ) - } - } - }, - text = rendering.toString(), - style = TextStyle( - textAlign = TextAlign.Center, - color = Color.White, - shadow = Shadow(blurRadius = 5f, color = Color.Black) - ) + modifier = modifier + .clipToBounds() + .drawBehind { + drawIntoCanvas { canvas -> + canvas.withSaveLayer(size.toRect(), Paint().apply { alpha = .2f }) { + canvas.drawRect(size.toRect(), Paint().apply { color = Color.Gray }) + drawCrossHatch( + color = Color.Red, + strokeWidth = 2.dp, + spaceWidth = 5.dp, + angle = 45f + ) + } + } + }, + text = rendering.toString(), + style = TextStyle( + textAlign = TextAlign.Center, + color = Color.White, + shadow = Shadow(blurRadius = 5f, color = Color.Black) + ) ) } @@ -90,9 +90,9 @@ internal fun placeholderViewFactory(modifier: Modifier): ViewFactory = @Composable private fun PreviewStubBindingPreviewTemplate() { placeholderViewFactory(Modifier).preview( - rendering = "preview", - placeholderModifier = Modifier.fillMaxSize() - .drawBorder(size = 1.dp, color = Color.Red) + rendering = "preview", + placeholderModifier = Modifier.fillMaxSize() + .drawBorder(size = 1.dp, color = Color.Red) ) } @@ -126,10 +126,10 @@ private fun DrawScope.drawHatch( var y = top + strokeWidthPx * 2f while (y < bottom) { drawLine( - strokeColor, - Offset(left, y), - Offset(right, y), - strokeWidthPx + strokeColor, + Offset(left, y), + Offset(right, y), + strokeWidthPx ) y += spaceWidthPx * 2 } diff --git a/core-compose/build.gradle.kts b/core-compose/build.gradle.kts index eacda20b3d..ceb6d7c474 100644 --- a/core-compose/build.gradle.kts +++ b/core-compose/build.gradle.kts @@ -1,5 +1,3 @@ - - /* * Copyright 2019 Square Inc. * diff --git a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/ComposeViewFactoryTest.kt b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/ComposeViewFactoryTest.kt index 90450d6182..1b9490760e 100644 --- a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/ComposeViewFactoryTest.kt +++ b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/ComposeViewFactoryTest.kt @@ -39,12 +39,12 @@ class ComposeViewFactoryTest { @Test fun wrapsFactoryWithRoot() { val wrapperText = mutableStateOf("one") val viewEnvironment = ViewEnvironment(ViewRegistry(TestFactory)) - .withCompositionRoot { content -> - Column { - BasicText(wrapperText.value) - content() - } + .withCompositionRoot { content -> + Column { + BasicText(wrapperText.value) + content() } + } composeRule.setContent { AndroidView(::RootView) { diff --git a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/CompositionRootTest.kt b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/CompositionRootTest.kt index eba09624ec..c9ffaeeccc 100644 --- a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/CompositionRootTest.kt +++ b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/CompositionRootTest.kt @@ -155,9 +155,9 @@ class CompositionRootTest { } assertThat(error).hasMessageThat() - .isEqualTo( - "Expected ComposableDecorator to invoke children exactly once, but was invoked 0 times." - ) + .isEqualTo( + "Expected ComposableDecorator to invoke children exactly once, but was invoked 0 times." + ) } @Test fun safeComposeViewFactoryRoot_throws_whenChildrenInvokedMultipleTimes() { @@ -176,8 +176,8 @@ class CompositionRootTest { } assertThat(error).hasMessageThat() - .isEqualTo( - "Expected ComposableDecorator to invoke children exactly once, but was invoked 2 times." - ) + .isEqualTo( + "Expected ComposableDecorator to invoke children exactly once, but was invoked 2 times." + ) } } diff --git a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/RenderAsStateTest.kt b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/RenderAsStateTest.kt index 1d2cf4c71b..21723b7f5b 100644 --- a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/RenderAsStateTest.kt +++ b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/RenderAsStateTest.kt @@ -105,11 +105,11 @@ class RenderAsStateTest { composeRule.setContent { Providers(UiSavedStateRegistryAmbient provides savedStateRegistry) { rendering = renderAsStateImpl( - workflow, - props = Unit, - onOutput = {}, - diagnosticListener = null, - snapshotKey = SNAPSHOT_KEY + workflow, + props = Unit, + onOutput = {}, + diagnosticListener = null, + snapshotKey = SNAPSHOT_KEY ).value } } @@ -136,11 +136,11 @@ class RenderAsStateTest { composeRule.setContent { Providers(UiSavedStateRegistryAmbient provides savedStateRegistry) { rendering = renderAsStateImpl( - workflow, - props = Unit, - onOutput = {}, - diagnosticListener = null, - snapshotKey = "workflow-snapshot" + workflow, + props = Unit, + onOutput = {}, + diagnosticListener = null, + snapshotKey = "workflow-snapshot" ).value } } @@ -194,7 +194,7 @@ class RenderAsStateTest { // Seems to be a problem accessing Workflow.stateful. private class SnapshottingWorkflow : - StatefulWorkflow() { + StatefulWorkflow() { data class SnapshottedRendering( val string: String, @@ -211,8 +211,8 @@ class RenderAsStateTest { state: String, context: RenderContext ) = SnapshottedRendering( - string = state, - updateString = { newString -> context.actionSink.send(updateString(newString)) } + string = state, + updateString = { newString -> context.actionSink.send(updateString(newString)) } ) override fun snapshotState(state: String): Snapshot = diff --git a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/internal/ViewFactoriesTest.kt b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/internal/ViewFactoriesTest.kt index 92328e7fd1..abfb507072 100644 --- a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/internal/ViewFactoriesTest.kt +++ b/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/internal/ViewFactoriesTest.kt @@ -48,12 +48,12 @@ class ViewFactoriesTest { @Test fun WorkflowRendering_wrapsFactoryWithRoot_whenAlreadyInComposition() { val viewEnvironment = ViewEnvironment(ViewRegistry(TestFactory)) - .withCompositionRoot { content -> - Column { - BasicText("one") - content() - } + .withCompositionRoot { content -> + Column { + BasicText("one") + content() } + } composeRule.setContent { WorkflowRendering(TestRendering("two"), viewEnvironment) diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactory.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactory.kt index 9f49a80210..95433884be 100644 --- a/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactory.kt +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactory.kt @@ -108,8 +108,8 @@ internal class ComposeViewFactory( val state = mutableStateOf(Pair(initialRendering, initialViewEnvironment)) composeContainer.bindShowRendering( - initialRendering, - initialViewEnvironment + initialRendering, + initialViewEnvironment ) { rendering, environment -> state.value = Pair(rendering, environment) } @@ -126,8 +126,8 @@ internal class ComposeViewFactory( // Update the state whenever a new rendering is emitted. // This lambda will be executed synchronously before bindShowRendering returns. composeContainer.bindShowRendering( - initialRendering, - initialViewEnvironment + initialRendering, + initialViewEnvironment ) { rendering, environment -> // Entry point to the world of Compose. composeContainer.setContent(parentComposition) { diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeWorkflow.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeWorkflow.kt index 726bc5e7c9..6d17a63a6c 100644 --- a/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeWorkflow.kt +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeWorkflow.kt @@ -38,7 +38,7 @@ import com.squareup.workflow.ui.compose.internal.ComposeWorkflowImpl * management. */ abstract class ComposeWorkflow : - Workflow { + Workflow { /** * Renders [props] using Compose. This function will be called to update the UI whenever the diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/RenderAsState.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/RenderAsState.kt index b275b9fbcc..f94f08f8c6 100644 --- a/core-compose/src/main/java/com/squareup/workflow/ui/compose/RenderAsState.kt +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/RenderAsState.kt @@ -191,9 +191,9 @@ private class WorkflowState( // This can be a StateFlow once coroutines is upgraded to 1.3.6. private val propsChannel = Channel(capacity = Channel.CONFLATED) - .apply { offer(initialProps) } + .apply { offer(initialProps) } val propsFlow = propsChannel.consumeAsFlow() - .distinctUntilChanged() + .distinctUntilChanged() // The value is guaranteed to be set before returning, so this cast is fine. @Suppress("UNCHECKED_CAST") @@ -205,14 +205,14 @@ private class WorkflowState( session.diagnosticListener = diagnosticListener session.outputs.onEach { outputRef.value!!.invoke(it) } - .launchIn(this) + .launchIn(this) session.renderingsAndSnapshots - .onEach { (rendering, snapshot) -> - renderingState.value = rendering - snapshotState.value = snapshot - } - .launchIn(this) + .onEach { (rendering, snapshot) -> + renderingState.value = rendering + snapshotState.value = snapshot + } + .launchIn(this) } } @@ -234,6 +234,6 @@ private object SnapshotSaver : Saver { override fun restore(value: ByteArray): Snapshot? { return value.takeUnless { it.isEmpty() } - ?.let { bytes -> Snapshot.of(ByteString.of(*bytes)) } + ?.let { bytes -> Snapshot.of(ByteString.of(*bytes)) } } } diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/WorkflowContainer.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/WorkflowContainer.kt index 9b82e67178..22edc2c864 100644 --- a/core-compose/src/main/java/com/squareup/workflow/ui/compose/WorkflowContainer.kt +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/WorkflowContainer.kt @@ -14,8 +14,8 @@ * limitations under the License. */ @file:Suppress( - "FunctionNaming", - "NOTHING_TO_INLINE" + "FunctionNaming", + "NOTHING_TO_INLINE" ) package com.squareup.workflow.ui.compose diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ViewFactories.kt b/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ViewFactories.kt index 1576e4ff76..00d15ff42e 100644 --- a/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ViewFactories.kt +++ b/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ViewFactories.kt @@ -165,7 +165,7 @@ private class HostView(context: Context) : FrameLayout(context) { // BuildView must call bindShowRendering, which will call showRendering. @Suppress("UNCHECKED_CAST") view = (viewFactory as ViewFactory) - .buildView(rendering, viewEnvironment, context, this) + .buildView(rendering, viewEnvironment, context, this) check(view!!.getRendering() != null) { "View.bindShowRendering should have been called for $this, typically by the " + diff --git a/samples/build.gradle.kts b/samples/build.gradle.kts index 94ab35bf7c..d5f143cbe0 100644 --- a/samples/build.gradle.kts +++ b/samples/build.gradle.kts @@ -1,5 +1,3 @@ - - /* * Copyright 2020 Square Inc. * diff --git a/samples/src/androidTest/java/com/squareup/sample/hellocomposebinding/HelloBindingTest.kt b/samples/src/androidTest/java/com/squareup/sample/hellocomposebinding/HelloBindingTest.kt index 74f90bd7ed..8359c3cc97 100644 --- a/samples/src/androidTest/java/com/squareup/sample/hellocomposebinding/HelloBindingTest.kt +++ b/samples/src/androidTest/java/com/squareup/sample/hellocomposebinding/HelloBindingTest.kt @@ -32,12 +32,12 @@ class HelloBindingTest { @Test fun togglesBetweenStates() { composeRule.onNodeWithText("Hello") - .assertIsDisplayed() - .performClick() + .assertIsDisplayed() + .performClick() composeRule.onNodeWithText("Goodbye") - .assertIsDisplayed() - .performClick() + .assertIsDisplayed() + .performClick() composeRule.onNodeWithText("Hello") - .assertIsDisplayed() + .assertIsDisplayed() } } diff --git a/samples/src/androidTest/java/com/squareup/sample/hellocomposerendering/HelloComposeRenderingTest.kt b/samples/src/androidTest/java/com/squareup/sample/hellocomposerendering/HelloComposeRenderingTest.kt index 44b2048e23..c3a5b3373a 100644 --- a/samples/src/androidTest/java/com/squareup/sample/hellocomposerendering/HelloComposeRenderingTest.kt +++ b/samples/src/androidTest/java/com/squareup/sample/hellocomposerendering/HelloComposeRenderingTest.kt @@ -32,12 +32,12 @@ class HelloComposeRenderingTest { @Test fun togglesBetweenStates() { composeRule.onNodeWithText("Hello") - .assertIsDisplayed() - .performClick() + .assertIsDisplayed() + .performClick() composeRule.onNodeWithText("Goodbye") - .assertIsDisplayed() - .performClick() + .assertIsDisplayed() + .performClick() composeRule.onNodeWithText("Hello") - .assertIsDisplayed() + .assertIsDisplayed() } } diff --git a/samples/src/androidTest/java/com/squareup/sample/launcher/SampleLauncherTest.kt b/samples/src/androidTest/java/com/squareup/sample/launcher/SampleLauncherTest.kt index 699ba80a8a..12f7e450c4 100644 --- a/samples/src/androidTest/java/com/squareup/sample/launcher/SampleLauncherTest.kt +++ b/samples/src/androidTest/java/com/squareup/sample/launcher/SampleLauncherTest.kt @@ -40,7 +40,7 @@ class SampleLauncherTest { samples.forEach { sample -> try { composeRule.onNodeWithText(sample.description, useUnmergedTree = true) - .performClick() + .performClick() pressBack() } catch (e: Throwable) { throw AssertionError("Failed to launch sample ${sample.name}", e) diff --git a/samples/src/androidTest/java/com/squareup/sample/nestedrenderings/NestedRenderingsTest.kt b/samples/src/androidTest/java/com/squareup/sample/nestedrenderings/NestedRenderingsTest.kt index ffa1073733..c24dce9b29 100644 --- a/samples/src/androidTest/java/com/squareup/sample/nestedrenderings/NestedRenderingsTest.kt +++ b/samples/src/androidTest/java/com/squareup/sample/nestedrenderings/NestedRenderingsTest.kt @@ -38,15 +38,15 @@ class NestedRenderingsTest { @Test fun childrenAreAddedAndRemoved() { composeRule.onNodeWithText(ADD_BUTTON_TEXT) - .assertIsDisplayed() - .performClick() + .assertIsDisplayed() + .performClick() composeRule.onAllNodesWithText(ADD_BUTTON_TEXT) - .assertCountEquals(2) - .forEach { it.performClick() } + .assertCountEquals(2) + .forEach { it.performClick() } composeRule.onAllNodesWithText(ADD_BUTTON_TEXT) - .assertCountEquals(4) + .assertCountEquals(4) resetAll() composeRule.onAllNodesWithText(ADD_BUTTON_TEXT).assertCountEquals(1) diff --git a/samples/src/main/java/com/squareup/sample/hellocompose/App.kt b/samples/src/main/java/com/squareup/sample/hellocompose/App.kt index eeeeb076ba..8b54d4efd2 100644 --- a/samples/src/main/java/com/squareup/sample/hellocompose/App.kt +++ b/samples/src/main/java/com/squareup/sample/hellocompose/App.kt @@ -34,13 +34,13 @@ private val viewEnvironment = ViewEnvironment(viewRegistry) @Composable fun App() { MaterialTheme { WorkflowContainer( - HelloWorkflow, viewEnvironment, - modifier = Modifier.border( - shape = RoundedCornerShape(10.dp), - width = 10.dp, - color = Color.Magenta - ), - diagnosticListener = SimpleLoggingDiagnosticListener() + HelloWorkflow, viewEnvironment, + modifier = Modifier.border( + shape = RoundedCornerShape(10.dp), + width = 10.dp, + color = Color.Magenta + ), + diagnosticListener = SimpleLoggingDiagnosticListener() ) } } diff --git a/samples/src/main/java/com/squareup/sample/hellocompose/HelloBinding.kt b/samples/src/main/java/com/squareup/sample/hellocompose/HelloBinding.kt index 3a83df292b..7b7dd6b8d0 100644 --- a/samples/src/main/java/com/squareup/sample/hellocompose/HelloBinding.kt +++ b/samples/src/main/java/com/squareup/sample/hellocompose/HelloBinding.kt @@ -26,10 +26,10 @@ import com.squareup.workflow.ui.compose.composedViewFactory val HelloBinding = composedViewFactory { rendering, _ -> Text( - rendering.message, - modifier = Modifier - .clickable(onClick = rendering.onClick) - .fillMaxSize() - .wrapContentSize(Alignment.Center) + rendering.message, + modifier = Modifier + .clickable(onClick = rendering.onClick) + .fillMaxSize() + .wrapContentSize(Alignment.Center) ) } diff --git a/samples/src/main/java/com/squareup/sample/hellocompose/HelloWorkflow.kt b/samples/src/main/java/com/squareup/sample/hellocompose/HelloWorkflow.kt index 84f4bd651e..46dfbfdec3 100644 --- a/samples/src/main/java/com/squareup/sample/hellocompose/HelloWorkflow.kt +++ b/samples/src/main/java/com/squareup/sample/hellocompose/HelloWorkflow.kt @@ -49,15 +49,15 @@ object HelloWorkflow : StatefulWorkflow() { props: Unit, snapshot: Snapshot? ): State = snapshot?.bytes?.parse { source -> if (source.readInt() == 1) Hello else Goodbye } - ?: Hello + ?: Hello override fun render( props: Unit, state: State, context: RenderContext ): Rendering = Rendering( - message = state.name, - onClick = { context.actionSink.send(helloAction) } + message = state.name, + onClick = { context.actionSink.send(helloAction) } ) override fun snapshotState(state: State): Snapshot = Snapshot.of(if (state == Hello) 1 else 0) diff --git a/samples/src/main/java/com/squareup/sample/hellocomposebinding/HelloBinding.kt b/samples/src/main/java/com/squareup/sample/hellocomposebinding/HelloBinding.kt index cf0b5f11b1..9119e0b504 100644 --- a/samples/src/main/java/com/squareup/sample/hellocomposebinding/HelloBinding.kt +++ b/samples/src/main/java/com/squareup/sample/hellocomposebinding/HelloBinding.kt @@ -29,10 +29,10 @@ import com.squareup.workflow.ui.compose.tooling.preview val HelloBinding = composedViewFactory { rendering, _ -> Text( - rendering.message, - modifier = Modifier.fillMaxSize() - .clickable(onClick = rendering.onClick) - .wrapContentSize(Alignment.Center) + rendering.message, + modifier = Modifier.fillMaxSize() + .clickable(onClick = rendering.onClick) + .wrapContentSize(Alignment.Center) ) } diff --git a/samples/src/main/java/com/squareup/sample/hellocomposebinding/HelloBindingActivity.kt b/samples/src/main/java/com/squareup/sample/hellocomposebinding/HelloBindingActivity.kt index 0a76132539..798e7a5eb2 100644 --- a/samples/src/main/java/com/squareup/sample/hellocomposebinding/HelloBindingActivity.kt +++ b/samples/src/main/java/com/squareup/sample/hellocomposebinding/HelloBindingActivity.kt @@ -35,8 +35,8 @@ class HelloBindingActivity : AppCompatActivity() { super.onCreate(savedInstanceState) setContentWorkflow(containerHints) { WorkflowRunner.Config( - HelloWorkflow, - diagnosticListener = SimpleLoggingDiagnosticListener() + HelloWorkflow, + diagnosticListener = SimpleLoggingDiagnosticListener() ) } } diff --git a/samples/src/main/java/com/squareup/sample/hellocomposebinding/HelloWorkflow.kt b/samples/src/main/java/com/squareup/sample/hellocomposebinding/HelloWorkflow.kt index 42a7e8bc0b..cafa517d24 100644 --- a/samples/src/main/java/com/squareup/sample/hellocomposebinding/HelloWorkflow.kt +++ b/samples/src/main/java/com/squareup/sample/hellocomposebinding/HelloWorkflow.kt @@ -49,7 +49,7 @@ object HelloWorkflow : StatefulWorkflow() { props: Unit, snapshot: Snapshot? ): State = snapshot?.bytes?.parse { source -> if (source.readInt() == 1) Hello else Goodbye } - ?: Hello + ?: Hello override fun render( props: Unit, @@ -57,8 +57,8 @@ object HelloWorkflow : StatefulWorkflow() { context: RenderContext ): Rendering { return Rendering( - message = state.name, - onClick = { context.actionSink.send(helloAction) } + message = state.name, + onClick = { context.actionSink.send(helloAction) } ) } diff --git a/samples/src/main/java/com/squareup/sample/hellocomposerendering/HelloComposeRenderingActivity.kt b/samples/src/main/java/com/squareup/sample/hellocomposerendering/HelloComposeRenderingActivity.kt index 2877316da1..5c6e08319d 100644 --- a/samples/src/main/java/com/squareup/sample/hellocomposerendering/HelloComposeRenderingActivity.kt +++ b/samples/src/main/java/com/squareup/sample/hellocomposerendering/HelloComposeRenderingActivity.kt @@ -30,8 +30,8 @@ class HelloComposeRenderingActivity : AppCompatActivity() { super.onCreate(savedInstanceState) setContentWorkflow(viewRegistry) { WorkflowRunner.Config( - HelloWorkflow, - diagnosticListener = SimpleLoggingDiagnosticListener() + HelloWorkflow, + diagnosticListener = SimpleLoggingDiagnosticListener() ) } } diff --git a/samples/src/main/java/com/squareup/sample/hellocomposerendering/HelloRenderingWorkflow.kt b/samples/src/main/java/com/squareup/sample/hellocomposerendering/HelloRenderingWorkflow.kt index 0e8d2326f8..8d7b794c73 100644 --- a/samples/src/main/java/com/squareup/sample/hellocomposerendering/HelloRenderingWorkflow.kt +++ b/samples/src/main/java/com/squareup/sample/hellocomposerendering/HelloRenderingWorkflow.kt @@ -46,11 +46,11 @@ object HelloRenderingWorkflow : ComposeWorkflow() { ) { MaterialTheme { Text( - props, - modifier = Modifier - .clickable(onClick = { outputSink.send(Toggle) }) - .fillMaxSize() - .wrapContentSize(Alignment.Center) + props, + modifier = Modifier + .clickable(onClick = { outputSink.send(Toggle) }) + .fillMaxSize() + .wrapContentSize(Alignment.Center) ) } } diff --git a/samples/src/main/java/com/squareup/sample/hellocomposerendering/HelloWorkflow.kt b/samples/src/main/java/com/squareup/sample/hellocomposerendering/HelloWorkflow.kt index 62f7cfba3b..8958af5891 100644 --- a/samples/src/main/java/com/squareup/sample/hellocomposerendering/HelloWorkflow.kt +++ b/samples/src/main/java/com/squareup/sample/hellocomposerendering/HelloWorkflow.kt @@ -44,7 +44,7 @@ object HelloWorkflow : StatefulWorkflow( props: Unit, snapshot: Snapshot? ): State = snapshot?.bytes?.parse { source -> if (source.readInt() == 1) Hello else Goodbye } - ?: Hello + ?: Hello override fun render( props: Unit, diff --git a/samples/src/main/java/com/squareup/sample/launcher/SampleLauncherApp.kt b/samples/src/main/java/com/squareup/sample/launcher/SampleLauncherApp.kt index b7930ad2a1..6800aae5c9 100644 --- a/samples/src/main/java/com/squareup/sample/launcher/SampleLauncherApp.kt +++ b/samples/src/main/java/com/squareup/sample/launcher/SampleLauncherApp.kt @@ -55,11 +55,11 @@ import com.squareup.sample.R.string @Composable fun SampleLauncherApp() { MaterialTheme(colors = darkColors()) { Scaffold( - topBar = { - TopAppBar(title = { - Text(stringResource(string.app_name)) - }) - } + topBar = { + TopAppBar(title = { + Text(stringResource(string.app_name)) + }) + } ) { LazyColumnFor(samples) { sample -> SampleItem(sample) @@ -82,12 +82,12 @@ import com.squareup.sample.R.string val globalBounds = remember { Ref() } ListItem( - text = { Text(sample.name) }, - secondaryText = { Text(sample.description) }, - singleLineSecondaryText = false, - // Animate the activities as scaling up from where the preview is drawn. - icon = { SamplePreview(sample) { globalBounds.value = it.globalBounds } }, - modifier = Modifier.clickable { launchSample(sample, rootView, globalBounds.value) } + text = { Text(sample.name) }, + secondaryText = { Text(sample.description) }, + singleLineSecondaryText = false, + // Animate the activities as scaling up from where the preview is drawn. + icon = { SamplePreview(sample) { globalBounds.value = it.globalBounds } }, + modifier = Modifier.clickable { launchSample(sample, rootView, globalBounds.value) } ) } @@ -107,26 +107,26 @@ import com.squareup.sample.R.string // the measurements here otherwise the rest of the UI will think the previews are full-size even // though they're graphically scaled down. Box( - modifier = Modifier - .height(previewHeight) - .aspectRatio(screenRatio) - .onGloballyPositioned(onPreviewCoordinates) + modifier = Modifier + .height(previewHeight) + .aspectRatio(screenRatio) + .onGloballyPositioned(onPreviewCoordinates) ) { // Preview the samples with a light theme, since that's what most of them use. MaterialTheme(lightColors()) { Surface { Box( - modifier = Modifier - // Disable touch input, since this preview isn't meant to be interactive. - .rawPressStartGestureFilter( - enabled = true, executionPass = Initial, onPressStart = {} - ) - // Measure/layout the child at full screen size, and then just scale the pixels - // down. This way all the text and other density-dependent things get scaled - // correctly too. - .height(configuration.screenHeightDp.dp) - .width(configuration.screenWidthDp.dp) - .drawLayer(scaleX = scale, scaleY = scale) + modifier = Modifier + // Disable touch input, since this preview isn't meant to be interactive. + .rawPressStartGestureFilter( + enabled = true, executionPass = Initial, onPressStart = {} + ) + // Measure/layout the child at full screen size, and then just scale the pixels + // down. This way all the text and other density-dependent things get scaled + // correctly too. + .height(configuration.screenHeightDp.dp) + .width(configuration.screenWidthDp.dp) + .drawLayer(scaleX = scale, scaleY = scale) ) { sample.preview() } @@ -144,11 +144,11 @@ private fun launchSample( val intent = Intent(context, sample.activityClass.java) val options: Bundle? = sourceBounds?.let { makeScaleUpAnimation( - rootView, - it.left.toInt(), - it.top.toInt(), - it.width.toInt(), - it.height.toInt() + rootView, + it.left.toInt(), + it.top.toInt(), + it.width.toInt(), + it.height.toInt() ).toBundle() } startActivity(context, intent, options) diff --git a/samples/src/main/java/com/squareup/sample/launcher/Samples.kt b/samples/src/main/java/com/squareup/sample/launcher/Samples.kt index b2ae7a9933..80f6617bc8 100644 --- a/samples/src/main/java/com/squareup/sample/launcher/Samples.kt +++ b/samples/src/main/java/com/squareup/sample/launcher/Samples.kt @@ -32,26 +32,26 @@ import com.squareup.sample.textinput.TextInputAppPreview import kotlin.reflect.KClass val samples = listOf( - Sample( - "Hello Compose Binding", HelloBindingActivity::class, - "Creates a ViewFactory using bindCompose." - ) { DrawHelloRenderingPreview() }, - Sample( - "Hello Compose Rendering", HelloComposeRenderingActivity::class, - "Uses ComposeWorkflow to create a workflow that draws itself." - ) { HelloRenderingWorkflowPreview() }, - Sample( - "Hello Compose", HelloComposeActivity::class, - "A pure Compose app that launches its root Workflow from inside Compose." - ) { App() }, - Sample( - "Nested Renderings", NestedRenderingsActivity::class, - "Demonstrates recursive view factories using both Compose and legacy view factories." - ) { RecursiveViewFactoryPreview() }, - Sample( - "Text Input", TextInputActivity::class, - "Demonstrates a workflow that drives a TextField." - ) { TextInputAppPreview() } + Sample( + "Hello Compose Binding", HelloBindingActivity::class, + "Creates a ViewFactory using bindCompose." + ) { DrawHelloRenderingPreview() }, + Sample( + "Hello Compose Rendering", HelloComposeRenderingActivity::class, + "Uses ComposeWorkflow to create a workflow that draws itself." + ) { HelloRenderingWorkflowPreview() }, + Sample( + "Hello Compose", HelloComposeActivity::class, + "A pure Compose app that launches its root Workflow from inside Compose." + ) { App() }, + Sample( + "Nested Renderings", NestedRenderingsActivity::class, + "Demonstrates recursive view factories using both Compose and legacy view factories." + ) { RecursiveViewFactoryPreview() }, + Sample( + "Text Input", TextInputActivity::class, + "Demonstrates a workflow that drives a TextField." + ) { TextInputAppPreview() } ) data class Sample( diff --git a/samples/src/main/java/com/squareup/sample/nestedrenderings/LegacyRunner.kt b/samples/src/main/java/com/squareup/sample/nestedrenderings/LegacyRunner.kt index 0bd6995986..e898a467b5 100644 --- a/samples/src/main/java/com/squareup/sample/nestedrenderings/LegacyRunner.kt +++ b/samples/src/main/java/com/squareup/sample/nestedrenderings/LegacyRunner.kt @@ -40,14 +40,14 @@ class LegacyRunner(private val binding: LegacyViewBinding) : LayoutRunner by bind( - LegacyViewBinding::inflate, ::LegacyRunner + LegacyViewBinding::inflate, ::LegacyRunner ) } @Preview(widthDp = 200, heightDp = 150, showBackground = true) @Composable private fun LegacyRunnerPreview() { LegacyRunner.preview( - rendering = LegacyRendering("child"), - placeholderModifier = Modifier.fillMaxSize() + rendering = LegacyRendering("child"), + placeholderModifier = Modifier.fillMaxSize() ) } diff --git a/samples/src/main/java/com/squareup/sample/nestedrenderings/NestedRenderingsActivity.kt b/samples/src/main/java/com/squareup/sample/nestedrenderings/NestedRenderingsActivity.kt index e331e05425..c624c2e500 100644 --- a/samples/src/main/java/com/squareup/sample/nestedrenderings/NestedRenderingsActivity.kt +++ b/samples/src/main/java/com/squareup/sample/nestedrenderings/NestedRenderingsActivity.kt @@ -27,8 +27,8 @@ import com.squareup.workflow.ui.compose.withCompositionRoot import com.squareup.workflow.ui.setContentWorkflow private val viewRegistry = ViewRegistry( - RecursiveViewFactory, - LegacyRunner + RecursiveViewFactory, + LegacyRunner ) private val viewEnvironment = ViewEnvironment(viewRegistry).withCompositionRoot { content -> @@ -40,8 +40,8 @@ class NestedRenderingsActivity : AppCompatActivity() { super.onCreate(savedInstanceState) setContentWorkflow(viewEnvironment) { WorkflowRunner.Config( - RecursiveWorkflow, - diagnosticListener = SimpleLoggingDiagnosticListener() + RecursiveWorkflow, + diagnosticListener = SimpleLoggingDiagnosticListener() ) } } diff --git a/samples/src/main/java/com/squareup/sample/nestedrenderings/RecursiveViewFactory.kt b/samples/src/main/java/com/squareup/sample/nestedrenderings/RecursiveViewFactory.kt index 4216ad97ce..e6a47c5ccc 100644 --- a/samples/src/main/java/com/squareup/sample/nestedrenderings/RecursiveViewFactory.kt +++ b/samples/src/main/java/com/squareup/sample/nestedrenderings/RecursiveViewFactory.kt @@ -58,25 +58,25 @@ val RecursiveViewFactory = composedViewFactory { rendering, viewEnvir val color = BackgroundColorAmbient.current val childColor = remember(color) { color.copy(alpha = .9f) - .compositeOver(Color.Black) + .compositeOver(Color.Black) } Card(backgroundColor = color) { Column( - Modifier.padding(dimensionResource(R.dimen.recursive_padding)) - .fillMaxSize(), - horizontalAlignment = CenterHorizontally + Modifier.padding(dimensionResource(R.dimen.recursive_padding)) + .fillMaxSize(), + horizontalAlignment = CenterHorizontally ) { Providers(BackgroundColorAmbient provides childColor) { Children( - rendering.children, viewEnvironment, - // Pass a weight so that the column fills all the space not occupied by the buttons. - modifier = Modifier.weight(1f, fill = true) + rendering.children, viewEnvironment, + // Pass a weight so that the column fills all the space not occupied by the buttons. + modifier = Modifier.weight(1f, fill = true) ) } Buttons( - onAdd = rendering.onAddChildClicked, - onReset = rendering.onResetClicked + onAdd = rendering.onAddChildClicked, + onReset = rendering.onResetClicked ) } } @@ -86,16 +86,16 @@ val RecursiveViewFactory = composedViewFactory { rendering, viewEnvir @Composable fun RecursiveViewFactoryPreview() { Providers(BackgroundColorAmbient provides Color.Green) { RecursiveViewFactory.preview( - Rendering( - children = listOf( - "foo", - Rendering( - children = listOf("bar"), - onAddChildClicked = {}, onResetClicked = {} - ) - ), onAddChildClicked = {}, onResetClicked = {} - ), - placeholderModifier = Modifier.fillMaxSize() + Rendering( + children = listOf( + "foo", + Rendering( + children = listOf("bar"), + onAddChildClicked = {}, onResetClicked = {} + ) + ), onAddChildClicked = {}, onResetClicked = {} + ), + placeholderModifier = Modifier.fillMaxSize() ) } } @@ -106,18 +106,18 @@ val RecursiveViewFactory = composedViewFactory { rendering, viewEnvir modifier: Modifier ) { Column( - modifier = modifier, - verticalArrangement = SpaceEvenly, - horizontalAlignment = CenterHorizontally + modifier = modifier, + verticalArrangement = SpaceEvenly, + horizontalAlignment = CenterHorizontally ) { children.forEach { childRendering -> WorkflowRendering( - childRendering, - // Pass a weight so all children are partitioned evenly within the total column space. - // Without the weight, each child is the full size of the parent. - viewEnvironment, - modifier = Modifier.weight(1f, true) - .padding(dimensionResource(R.dimen.recursive_padding)) + childRendering, + // Pass a weight so all children are partitioned evenly within the total column space. + // Without the weight, each child is the full size of the parent. + viewEnvironment, + modifier = Modifier.weight(1f, true) + .padding(dimensionResource(R.dimen.recursive_padding)) ) } } @@ -130,9 +130,9 @@ val RecursiveViewFactory = composedViewFactory { rendering, viewEnvir ) { // Use a FlowRow so the buttons will wrap when the parent is too narrow. FlowRow( - mainAxisSize = Expand, - mainAxisAlignment = MainAxisAlignment.SpaceEvenly, - crossAxisSpacing = dimensionResource(R.dimen.recursive_padding) + mainAxisSize = Expand, + mainAxisAlignment = MainAxisAlignment.SpaceEvenly, + crossAxisSpacing = dimensionResource(R.dimen.recursive_padding) ) { Button(onClick = onAdd) { Text("Add Child") diff --git a/samples/src/main/java/com/squareup/sample/nestedrenderings/RecursiveWorkflow.kt b/samples/src/main/java/com/squareup/sample/nestedrenderings/RecursiveWorkflow.kt index c8780445b2..adfa18b98b 100644 --- a/samples/src/main/java/com/squareup/sample/nestedrenderings/RecursiveWorkflow.kt +++ b/samples/src/main/java/com/squareup/sample/nestedrenderings/RecursiveWorkflow.kt @@ -65,12 +65,12 @@ object RecursiveWorkflow : StatefulWorkflow() { context: RenderContext ): Rendering { return Rendering( - children = List(state.children) { i -> - val child = context.renderChild(RecursiveWorkflow, key = i.toString()) - if (i % 2 == 0) child else LegacyRendering(child) - }, - onAddChildClicked = { context.actionSink.send(addChild()) }, - onResetClicked = { context.actionSink.send(reset()) } + children = List(state.children) { i -> + val child = context.renderChild(RecursiveWorkflow, key = i.toString()) + if (i % 2 == 0) child else LegacyRendering(child) + }, + onAddChildClicked = { context.actionSink.send(addChild()) }, + onResetClicked = { context.actionSink.send(reset()) } ) } diff --git a/samples/src/main/java/com/squareup/sample/textinput/App.kt b/samples/src/main/java/com/squareup/sample/textinput/App.kt index 9ffa0baa71..5a0b21cbcd 100644 --- a/samples/src/main/java/com/squareup/sample/textinput/App.kt +++ b/samples/src/main/java/com/squareup/sample/textinput/App.kt @@ -29,8 +29,8 @@ private val viewEnvironment = ViewEnvironment(viewRegistry) @Composable fun TextInputApp() { MaterialTheme { WorkflowContainer( - TextInputWorkflow, viewEnvironment, - diagnosticListener = SimpleLoggingDiagnosticListener() + TextInputWorkflow, viewEnvironment, + diagnosticListener = SimpleLoggingDiagnosticListener() ) } } diff --git a/samples/src/main/java/com/squareup/sample/textinput/TextInputViewFactory.kt b/samples/src/main/java/com/squareup/sample/textinput/TextInputViewFactory.kt index f3bc5463e8..e1c10a1da7 100644 --- a/samples/src/main/java/com/squareup/sample/textinput/TextInputViewFactory.kt +++ b/samples/src/main/java/com/squareup/sample/textinput/TextInputViewFactory.kt @@ -37,18 +37,18 @@ import com.squareup.workflow.ui.compose.tooling.preview @OptIn(ExperimentalFocus::class) val TextInputViewFactory = composedViewFactory { rendering, _ -> Column( - modifier = Modifier - .fillMaxSize() - .wrapContentSize() - .animateContentSize(clip = false), - horizontalAlignment = Alignment.CenterHorizontally + modifier = Modifier + .fillMaxSize() + .wrapContentSize() + .animateContentSize(clip = false), + horizontalAlignment = Alignment.CenterHorizontally ) { Text(text = rendering.text) OutlinedTextField( - label = {}, - placeholder = { Text("Enter some text") }, - value = rendering.text, - onValueChange = rendering.onTextChanged + label = {}, + placeholder = { Text("Enter some text") }, + value = rendering.text, + onValueChange = rendering.onTextChanged ) Spacer(modifier = Modifier.height(8.dp)) Button(onClick = rendering.onSwapText) { @@ -60,8 +60,8 @@ val TextInputViewFactory = composedViewFactory { rendering, _ -> @Preview(showBackground = true) @Composable private fun TextInputViewFactoryPreview() { TextInputViewFactory.preview(Rendering( - text = "Hello world", - onTextChanged = {}, - onSwapText = {} + text = "Hello world", + onTextChanged = {}, + onSwapText = {} )) } diff --git a/samples/src/main/java/com/squareup/sample/textinput/TextInputWorkflow.kt b/samples/src/main/java/com/squareup/sample/textinput/TextInputWorkflow.kt index 4616f20790..0468dfb360 100644 --- a/samples/src/main/java/com/squareup/sample/textinput/TextInputWorkflow.kt +++ b/samples/src/main/java/com/squareup/sample/textinput/TextInputWorkflow.kt @@ -58,9 +58,9 @@ object TextInputWorkflow : StatefulWorkflow() { state: State, context: RenderContext ): Rendering = Rendering( - text = if (state.showingTextA) state.textA else state.textB, - onTextChanged = { context.actionSink.send(changeText(it)) }, - onSwapText = { context.actionSink.send(swapText) } + text = if (state.showingTextA) state.textA else state.textB, + onTextChanged = { context.actionSink.send(changeText(it)) }, + onSwapText = { context.actionSink.send(swapText) } ) override fun snapshotState(state: State): Snapshot = Snapshot.EMPTY diff --git a/settings.gradle.kts b/settings.gradle.kts index 76e8e1e28a..829e1a9302 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -16,7 +16,7 @@ rootProject.name = "workflow-compose" include( - ":compose-tooling", - ":core-compose", - ":samples" + ":compose-tooling", + ":core-compose", + ":samples" ) From c131e4e17561a966c272e5436126a4bad76b2c56 Mon Sep 17 00:00:00 2001 From: Zach Klippenstein Date: Thu, 4 Feb 2021 12:47:00 -0800 Subject: [PATCH 65/67] Replace individual UI Systems team members with the ui-systems-android team in CODEOWNERS. --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index b47b2cc66b..68146478b6 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,3 +1,3 @@ # These owners will be the default owners for everything in # the repo, unless a later match takes precedence. -* @square/mobile-foundation-android @helios175 @wardellbagby @patrickyin +* @square/mobile-foundation-android @square/ui-systems-android @wardellbagby From 11893e4b599c529e00d14266f6ba4ee001e875a7 Mon Sep 17 00:00:00 2001 From: Zach Klippenstein Date: Thu, 4 Feb 2021 12:14:20 -0800 Subject: [PATCH 66/67] Move everything into a compose subdirectory to prep for importing into the main workflow-kotlin repo. --- .../workflows/{kotlin.yml => compose-kotlin.yml} | 12 ++++++++++++ .../.buildscript}/android-sample-app.gradle | 0 .../.buildscript}/android-ui-tests.gradle | 0 .../.buildscript}/binary-validation.gradle | 0 .../.buildscript}/configure-android-defaults.gradle | 0 .../.buildscript}/configure-compose.gradle | 0 .../.buildscript}/configure-maven-publish.gradle | 0 .editorconfig => compose/.editorconfig | 0 .gitignore => compose/.gitignore | 0 {.idea => compose/.idea}/dictionaries/workflow.xml | 2 +- {.idea => compose/.idea}/misc.xml | 0 CHANGELOG.md => compose/CHANGELOG.md | 0 CODE_OF_CONDUCT.md => compose/CODE_OF_CONDUCT.md | 0 CONTRIBUTING.md => compose/CONTRIBUTING.md | 0 LICENSE => compose/LICENSE | 0 README.md => compose/README.md | 0 RELEASING.md => compose/RELEASING.md | 0 build.gradle.kts => compose/build.gradle.kts | 0 {buildSrc => compose/buildSrc}/build.gradle.kts | 0 .../buildSrc}/src/main/java/Dependencies.kt | 0 .../compose-tooling}/api/compose-tooling.api | 0 .../compose-tooling}/build.gradle.kts | 0 .../compose-tooling}/gradle.properties | 0 .../compose/tooling/PreviewComposeWorkflowTest.kt | 0 .../ui/compose/tooling/PreviewViewFactoryTest.kt | 0 .../compose-tooling}/src/main/AndroidManifest.xml | 0 .../workflow/ui/compose/tooling/ComposeWorkflows.kt | 0 .../ui/compose/tooling/PlaceholderViewFactory.kt | 0 .../ui/compose/tooling/PreviewViewEnvironment.kt | 0 .../workflow/ui/compose/tooling/ViewFactories.kt | 0 .../core-compose}/api/core-compose.api | 0 .../core-compose}/build.gradle.kts | 0 .../core-compose}/gradle.properties | 0 .../src/androidTest/AndroidManifest.xml | 0 .../workflow/ui/compose/ComposeViewFactoryTest.kt | 0 .../workflow/ui/compose/CompositionRootTest.kt | 0 .../workflow/ui/compose/RenderAsStateTest.kt | 0 .../workflow/ui/compose/ViewEnvironmentsTest.kt | 0 .../workflow/ui/compose/WorkflowContainerTest.kt | 0 .../ui/compose/internal/ViewFactoriesTest.kt | 0 .../core-compose}/src/main/AndroidManifest.xml | 0 .../workflow/ui/compose/ComposeRendering.kt | 0 .../workflow/ui/compose/ComposeViewFactory.kt | 0 .../squareup/workflow/ui/compose/ComposeWorkflow.kt | 0 .../squareup/workflow/ui/compose/CompositionRoot.kt | 0 .../squareup/workflow/ui/compose/RenderAsState.kt | 0 .../workflow/ui/compose/ViewEnvironments.kt | 0 .../workflow/ui/compose/WorkflowContainer.kt | 0 .../ui/compose/internal/ComposeWorkflowImpl.kt | 0 .../ui/compose/internal/ParentComposition.kt | 0 .../workflow/ui/compose/internal/ViewFactories.kt | 0 .../workflow/ui/compose/internal/ViewRegistries.kt | 0 detekt.yml => compose/detekt.yml | 0 gradle.properties => compose/gradle.properties | 0 .../gradle}/wrapper/gradle-wrapper.jar | Bin .../gradle}/wrapper/gradle-wrapper.properties | 0 gradlew => compose/gradlew | 0 gradlew.bat => compose/gradlew.bat | 0 {samples => compose/samples}/build.gradle.kts | 0 .../sample/hellocomposebinding/HelloBindingTest.kt | 0 .../HelloComposeRenderingTest.kt | 0 .../squareup/sample/launcher/SampleLauncherTest.kt | 0 .../sample/nestedrenderings/NestedRenderingsTest.kt | 0 .../samples}/src/main/AndroidManifest.xml | 0 .../java/com/squareup/sample/hellocompose/App.kt | 0 .../squareup/sample/hellocompose/HelloBinding.kt | 0 .../sample/hellocompose/HelloComposeActivity.kt | 0 .../squareup/sample/hellocompose/HelloWorkflow.kt | 0 .../sample/hellocomposebinding/HelloBinding.kt | 0 .../hellocomposebinding/HelloBindingActivity.kt | 0 .../sample/hellocomposebinding/HelloWorkflow.kt | 0 .../HelloComposeRenderingActivity.kt | 0 .../hellocomposerendering/HelloRenderingWorkflow.kt | 0 .../sample/hellocomposerendering/HelloWorkflow.kt | 0 .../sample/launcher/SampleLauncherActivity.kt | 0 .../squareup/sample/launcher/SampleLauncherApp.kt | 0 .../java/com/squareup/sample/launcher/Samples.kt | 0 .../sample/nestedrenderings/LegacyRunner.kt | 0 .../nestedrenderings/NestedRenderingsActivity.kt | 0 .../sample/nestedrenderings/RecursiveViewFactory.kt | 0 .../sample/nestedrenderings/RecursiveWorkflow.kt | 0 .../main/java/com/squareup/sample/textinput/App.kt | 0 .../squareup/sample/textinput/TextInputActivity.kt | 0 .../sample/textinput/TextInputViewFactory.kt | 0 .../squareup/sample/textinput/TextInputWorkflow.kt | 0 .../samples}/src/main/res/layout/legacy_view.xml | 0 .../samples}/src/main/res/values/dimens.xml | 0 .../samples}/src/main/res/values/strings.xml | 0 .../samples}/src/main/res/values/styles.xml | 0 settings.gradle.kts => compose/settings.gradle.kts | 0 90 files changed, 13 insertions(+), 1 deletion(-) rename .github/workflows/{kotlin.yml => compose-kotlin.yml} (94%) rename {.buildscript => compose/.buildscript}/android-sample-app.gradle (100%) rename {.buildscript => compose/.buildscript}/android-ui-tests.gradle (100%) rename {.buildscript => compose/.buildscript}/binary-validation.gradle (100%) rename {.buildscript => compose/.buildscript}/configure-android-defaults.gradle (100%) rename {.buildscript => compose/.buildscript}/configure-compose.gradle (100%) rename {.buildscript => compose/.buildscript}/configure-maven-publish.gradle (100%) rename .editorconfig => compose/.editorconfig (100%) rename .gitignore => compose/.gitignore (100%) rename {.idea => compose/.idea}/dictionaries/workflow.xml (95%) rename {.idea => compose/.idea}/misc.xml (100%) rename CHANGELOG.md => compose/CHANGELOG.md (100%) rename CODE_OF_CONDUCT.md => compose/CODE_OF_CONDUCT.md (100%) rename CONTRIBUTING.md => compose/CONTRIBUTING.md (100%) rename LICENSE => compose/LICENSE (100%) rename README.md => compose/README.md (100%) rename RELEASING.md => compose/RELEASING.md (100%) rename build.gradle.kts => compose/build.gradle.kts (100%) rename {buildSrc => compose/buildSrc}/build.gradle.kts (100%) rename {buildSrc => compose/buildSrc}/src/main/java/Dependencies.kt (100%) rename {compose-tooling => compose/compose-tooling}/api/compose-tooling.api (100%) rename {compose-tooling => compose/compose-tooling}/build.gradle.kts (100%) rename {compose-tooling => compose/compose-tooling}/gradle.properties (100%) rename {compose-tooling => compose/compose-tooling}/src/androidTest/java/com/squareup/workflow/ui/compose/tooling/PreviewComposeWorkflowTest.kt (100%) rename {compose-tooling => compose/compose-tooling}/src/androidTest/java/com/squareup/workflow/ui/compose/tooling/PreviewViewFactoryTest.kt (100%) rename {compose-tooling => compose/compose-tooling}/src/main/AndroidManifest.xml (100%) rename {compose-tooling => compose/compose-tooling}/src/main/java/com/squareup/workflow/ui/compose/tooling/ComposeWorkflows.kt (100%) rename {compose-tooling => compose/compose-tooling}/src/main/java/com/squareup/workflow/ui/compose/tooling/PlaceholderViewFactory.kt (100%) rename {compose-tooling => compose/compose-tooling}/src/main/java/com/squareup/workflow/ui/compose/tooling/PreviewViewEnvironment.kt (100%) rename {compose-tooling => compose/compose-tooling}/src/main/java/com/squareup/workflow/ui/compose/tooling/ViewFactories.kt (100%) rename {core-compose => compose/core-compose}/api/core-compose.api (100%) rename {core-compose => compose/core-compose}/build.gradle.kts (100%) rename {core-compose => compose/core-compose}/gradle.properties (100%) rename {core-compose => compose/core-compose}/src/androidTest/AndroidManifest.xml (100%) rename {core-compose => compose/core-compose}/src/androidTest/java/com/squareup/workflow/ui/compose/ComposeViewFactoryTest.kt (100%) rename {core-compose => compose/core-compose}/src/androidTest/java/com/squareup/workflow/ui/compose/CompositionRootTest.kt (100%) rename {core-compose => compose/core-compose}/src/androidTest/java/com/squareup/workflow/ui/compose/RenderAsStateTest.kt (100%) rename {core-compose => compose/core-compose}/src/androidTest/java/com/squareup/workflow/ui/compose/ViewEnvironmentsTest.kt (100%) rename {core-compose => compose/core-compose}/src/androidTest/java/com/squareup/workflow/ui/compose/WorkflowContainerTest.kt (100%) rename {core-compose => compose/core-compose}/src/androidTest/java/com/squareup/workflow/ui/compose/internal/ViewFactoriesTest.kt (100%) rename {core-compose => compose/core-compose}/src/main/AndroidManifest.xml (100%) rename {core-compose => compose/core-compose}/src/main/java/com/squareup/workflow/ui/compose/ComposeRendering.kt (100%) rename {core-compose => compose/core-compose}/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactory.kt (100%) rename {core-compose => compose/core-compose}/src/main/java/com/squareup/workflow/ui/compose/ComposeWorkflow.kt (100%) rename {core-compose => compose/core-compose}/src/main/java/com/squareup/workflow/ui/compose/CompositionRoot.kt (100%) rename {core-compose => compose/core-compose}/src/main/java/com/squareup/workflow/ui/compose/RenderAsState.kt (100%) rename {core-compose => compose/core-compose}/src/main/java/com/squareup/workflow/ui/compose/ViewEnvironments.kt (100%) rename {core-compose => compose/core-compose}/src/main/java/com/squareup/workflow/ui/compose/WorkflowContainer.kt (100%) rename {core-compose => compose/core-compose}/src/main/java/com/squareup/workflow/ui/compose/internal/ComposeWorkflowImpl.kt (100%) rename {core-compose => compose/core-compose}/src/main/java/com/squareup/workflow/ui/compose/internal/ParentComposition.kt (100%) rename {core-compose => compose/core-compose}/src/main/java/com/squareup/workflow/ui/compose/internal/ViewFactories.kt (100%) rename {core-compose => compose/core-compose}/src/main/java/com/squareup/workflow/ui/compose/internal/ViewRegistries.kt (100%) rename detekt.yml => compose/detekt.yml (100%) rename gradle.properties => compose/gradle.properties (100%) rename {gradle => compose/gradle}/wrapper/gradle-wrapper.jar (100%) rename {gradle => compose/gradle}/wrapper/gradle-wrapper.properties (100%) rename gradlew => compose/gradlew (100%) rename gradlew.bat => compose/gradlew.bat (100%) rename {samples => compose/samples}/build.gradle.kts (100%) rename {samples => compose/samples}/src/androidTest/java/com/squareup/sample/hellocomposebinding/HelloBindingTest.kt (100%) rename {samples => compose/samples}/src/androidTest/java/com/squareup/sample/hellocomposerendering/HelloComposeRenderingTest.kt (100%) rename {samples => compose/samples}/src/androidTest/java/com/squareup/sample/launcher/SampleLauncherTest.kt (100%) rename {samples => compose/samples}/src/androidTest/java/com/squareup/sample/nestedrenderings/NestedRenderingsTest.kt (100%) rename {samples => compose/samples}/src/main/AndroidManifest.xml (100%) rename {samples => compose/samples}/src/main/java/com/squareup/sample/hellocompose/App.kt (100%) rename {samples => compose/samples}/src/main/java/com/squareup/sample/hellocompose/HelloBinding.kt (100%) rename {samples => compose/samples}/src/main/java/com/squareup/sample/hellocompose/HelloComposeActivity.kt (100%) rename {samples => compose/samples}/src/main/java/com/squareup/sample/hellocompose/HelloWorkflow.kt (100%) rename {samples => compose/samples}/src/main/java/com/squareup/sample/hellocomposebinding/HelloBinding.kt (100%) rename {samples => compose/samples}/src/main/java/com/squareup/sample/hellocomposebinding/HelloBindingActivity.kt (100%) rename {samples => compose/samples}/src/main/java/com/squareup/sample/hellocomposebinding/HelloWorkflow.kt (100%) rename {samples => compose/samples}/src/main/java/com/squareup/sample/hellocomposerendering/HelloComposeRenderingActivity.kt (100%) rename {samples => compose/samples}/src/main/java/com/squareup/sample/hellocomposerendering/HelloRenderingWorkflow.kt (100%) rename {samples => compose/samples}/src/main/java/com/squareup/sample/hellocomposerendering/HelloWorkflow.kt (100%) rename {samples => compose/samples}/src/main/java/com/squareup/sample/launcher/SampleLauncherActivity.kt (100%) rename {samples => compose/samples}/src/main/java/com/squareup/sample/launcher/SampleLauncherApp.kt (100%) rename {samples => compose/samples}/src/main/java/com/squareup/sample/launcher/Samples.kt (100%) rename {samples => compose/samples}/src/main/java/com/squareup/sample/nestedrenderings/LegacyRunner.kt (100%) rename {samples => compose/samples}/src/main/java/com/squareup/sample/nestedrenderings/NestedRenderingsActivity.kt (100%) rename {samples => compose/samples}/src/main/java/com/squareup/sample/nestedrenderings/RecursiveViewFactory.kt (100%) rename {samples => compose/samples}/src/main/java/com/squareup/sample/nestedrenderings/RecursiveWorkflow.kt (100%) rename {samples => compose/samples}/src/main/java/com/squareup/sample/textinput/App.kt (100%) rename {samples => compose/samples}/src/main/java/com/squareup/sample/textinput/TextInputActivity.kt (100%) rename {samples => compose/samples}/src/main/java/com/squareup/sample/textinput/TextInputViewFactory.kt (100%) rename {samples => compose/samples}/src/main/java/com/squareup/sample/textinput/TextInputWorkflow.kt (100%) rename {samples => compose/samples}/src/main/res/layout/legacy_view.xml (100%) rename {samples => compose/samples}/src/main/res/values/dimens.xml (100%) rename {samples => compose/samples}/src/main/res/values/strings.xml (100%) rename {samples => compose/samples}/src/main/res/values/styles.xml (100%) rename settings.gradle.kts => compose/settings.gradle.kts (100%) diff --git a/.github/workflows/kotlin.yml b/.github/workflows/compose-kotlin.yml similarity index 94% rename from .github/workflows/kotlin.yml rename to .github/workflows/compose-kotlin.yml index 5c59f82d10..265c3381f2 100644 --- a/.github/workflows/kotlin.yml +++ b/.github/workflows/compose-kotlin.yml @@ -2,12 +2,22 @@ name: Kotlin CI on: push: + paths: + - 'compose/**' + - '.github/workflows/compose-kotlin.yml' branches: [main] pull_request: + paths: + - 'compose/**' + - '.github/workflows/compose-kotlin.yml' env: GRADLE_HOME: ${{ github.workspace }}/gradle-home +defaults: + run: + working-directory: compose + jobs: assemble: name: Assemble @@ -126,6 +136,8 @@ jobs: with: api-level: ${{ matrix.api-level }} arch: x86_64 + # This task doesn't use the run default specified at the top of the file. + working-directory: compose script: ./gradlew connectedCheck --build-cache --no-daemon --stacktrace --gradle-user-home "$GRADLE_HOME" - name: Upload results uses: actions/upload-artifact@v2 diff --git a/.buildscript/android-sample-app.gradle b/compose/.buildscript/android-sample-app.gradle similarity index 100% rename from .buildscript/android-sample-app.gradle rename to compose/.buildscript/android-sample-app.gradle diff --git a/.buildscript/android-ui-tests.gradle b/compose/.buildscript/android-ui-tests.gradle similarity index 100% rename from .buildscript/android-ui-tests.gradle rename to compose/.buildscript/android-ui-tests.gradle diff --git a/.buildscript/binary-validation.gradle b/compose/.buildscript/binary-validation.gradle similarity index 100% rename from .buildscript/binary-validation.gradle rename to compose/.buildscript/binary-validation.gradle diff --git a/.buildscript/configure-android-defaults.gradle b/compose/.buildscript/configure-android-defaults.gradle similarity index 100% rename from .buildscript/configure-android-defaults.gradle rename to compose/.buildscript/configure-android-defaults.gradle diff --git a/.buildscript/configure-compose.gradle b/compose/.buildscript/configure-compose.gradle similarity index 100% rename from .buildscript/configure-compose.gradle rename to compose/.buildscript/configure-compose.gradle diff --git a/.buildscript/configure-maven-publish.gradle b/compose/.buildscript/configure-maven-publish.gradle similarity index 100% rename from .buildscript/configure-maven-publish.gradle rename to compose/.buildscript/configure-maven-publish.gradle diff --git a/.editorconfig b/compose/.editorconfig similarity index 100% rename from .editorconfig rename to compose/.editorconfig diff --git a/.gitignore b/compose/.gitignore similarity index 100% rename from .gitignore rename to compose/.gitignore diff --git a/.idea/dictionaries/workflow.xml b/compose/.idea/dictionaries/workflow.xml similarity index 95% rename from .idea/dictionaries/workflow.xml rename to compose/.idea/dictionaries/workflow.xml index f424539ec1..c2cb4ea13a 100644 --- a/.idea/dictionaries/workflow.xml +++ b/compose/.idea/dictionaries/workflow.xml @@ -11,4 +11,4 @@ workflows - \ No newline at end of file + diff --git a/.idea/misc.xml b/compose/.idea/misc.xml similarity index 100% rename from .idea/misc.xml rename to compose/.idea/misc.xml diff --git a/CHANGELOG.md b/compose/CHANGELOG.md similarity index 100% rename from CHANGELOG.md rename to compose/CHANGELOG.md diff --git a/CODE_OF_CONDUCT.md b/compose/CODE_OF_CONDUCT.md similarity index 100% rename from CODE_OF_CONDUCT.md rename to compose/CODE_OF_CONDUCT.md diff --git a/CONTRIBUTING.md b/compose/CONTRIBUTING.md similarity index 100% rename from CONTRIBUTING.md rename to compose/CONTRIBUTING.md diff --git a/LICENSE b/compose/LICENSE similarity index 100% rename from LICENSE rename to compose/LICENSE diff --git a/README.md b/compose/README.md similarity index 100% rename from README.md rename to compose/README.md diff --git a/RELEASING.md b/compose/RELEASING.md similarity index 100% rename from RELEASING.md rename to compose/RELEASING.md diff --git a/build.gradle.kts b/compose/build.gradle.kts similarity index 100% rename from build.gradle.kts rename to compose/build.gradle.kts diff --git a/buildSrc/build.gradle.kts b/compose/buildSrc/build.gradle.kts similarity index 100% rename from buildSrc/build.gradle.kts rename to compose/buildSrc/build.gradle.kts diff --git a/buildSrc/src/main/java/Dependencies.kt b/compose/buildSrc/src/main/java/Dependencies.kt similarity index 100% rename from buildSrc/src/main/java/Dependencies.kt rename to compose/buildSrc/src/main/java/Dependencies.kt diff --git a/compose-tooling/api/compose-tooling.api b/compose/compose-tooling/api/compose-tooling.api similarity index 100% rename from compose-tooling/api/compose-tooling.api rename to compose/compose-tooling/api/compose-tooling.api diff --git a/compose-tooling/build.gradle.kts b/compose/compose-tooling/build.gradle.kts similarity index 100% rename from compose-tooling/build.gradle.kts rename to compose/compose-tooling/build.gradle.kts diff --git a/compose-tooling/gradle.properties b/compose/compose-tooling/gradle.properties similarity index 100% rename from compose-tooling/gradle.properties rename to compose/compose-tooling/gradle.properties diff --git a/compose-tooling/src/androidTest/java/com/squareup/workflow/ui/compose/tooling/PreviewComposeWorkflowTest.kt b/compose/compose-tooling/src/androidTest/java/com/squareup/workflow/ui/compose/tooling/PreviewComposeWorkflowTest.kt similarity index 100% rename from compose-tooling/src/androidTest/java/com/squareup/workflow/ui/compose/tooling/PreviewComposeWorkflowTest.kt rename to compose/compose-tooling/src/androidTest/java/com/squareup/workflow/ui/compose/tooling/PreviewComposeWorkflowTest.kt diff --git a/compose-tooling/src/androidTest/java/com/squareup/workflow/ui/compose/tooling/PreviewViewFactoryTest.kt b/compose/compose-tooling/src/androidTest/java/com/squareup/workflow/ui/compose/tooling/PreviewViewFactoryTest.kt similarity index 100% rename from compose-tooling/src/androidTest/java/com/squareup/workflow/ui/compose/tooling/PreviewViewFactoryTest.kt rename to compose/compose-tooling/src/androidTest/java/com/squareup/workflow/ui/compose/tooling/PreviewViewFactoryTest.kt diff --git a/compose-tooling/src/main/AndroidManifest.xml b/compose/compose-tooling/src/main/AndroidManifest.xml similarity index 100% rename from compose-tooling/src/main/AndroidManifest.xml rename to compose/compose-tooling/src/main/AndroidManifest.xml diff --git a/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/ComposeWorkflows.kt b/compose/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/ComposeWorkflows.kt similarity index 100% rename from compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/ComposeWorkflows.kt rename to compose/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/ComposeWorkflows.kt diff --git a/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/PlaceholderViewFactory.kt b/compose/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/PlaceholderViewFactory.kt similarity index 100% rename from compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/PlaceholderViewFactory.kt rename to compose/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/PlaceholderViewFactory.kt diff --git a/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/PreviewViewEnvironment.kt b/compose/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/PreviewViewEnvironment.kt similarity index 100% rename from compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/PreviewViewEnvironment.kt rename to compose/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/PreviewViewEnvironment.kt diff --git a/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/ViewFactories.kt b/compose/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/ViewFactories.kt similarity index 100% rename from compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/ViewFactories.kt rename to compose/compose-tooling/src/main/java/com/squareup/workflow/ui/compose/tooling/ViewFactories.kt diff --git a/core-compose/api/core-compose.api b/compose/core-compose/api/core-compose.api similarity index 100% rename from core-compose/api/core-compose.api rename to compose/core-compose/api/core-compose.api diff --git a/core-compose/build.gradle.kts b/compose/core-compose/build.gradle.kts similarity index 100% rename from core-compose/build.gradle.kts rename to compose/core-compose/build.gradle.kts diff --git a/core-compose/gradle.properties b/compose/core-compose/gradle.properties similarity index 100% rename from core-compose/gradle.properties rename to compose/core-compose/gradle.properties diff --git a/core-compose/src/androidTest/AndroidManifest.xml b/compose/core-compose/src/androidTest/AndroidManifest.xml similarity index 100% rename from core-compose/src/androidTest/AndroidManifest.xml rename to compose/core-compose/src/androidTest/AndroidManifest.xml diff --git a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/ComposeViewFactoryTest.kt b/compose/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/ComposeViewFactoryTest.kt similarity index 100% rename from core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/ComposeViewFactoryTest.kt rename to compose/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/ComposeViewFactoryTest.kt diff --git a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/CompositionRootTest.kt b/compose/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/CompositionRootTest.kt similarity index 100% rename from core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/CompositionRootTest.kt rename to compose/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/CompositionRootTest.kt diff --git a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/RenderAsStateTest.kt b/compose/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/RenderAsStateTest.kt similarity index 100% rename from core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/RenderAsStateTest.kt rename to compose/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/RenderAsStateTest.kt diff --git a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/ViewEnvironmentsTest.kt b/compose/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/ViewEnvironmentsTest.kt similarity index 100% rename from core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/ViewEnvironmentsTest.kt rename to compose/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/ViewEnvironmentsTest.kt diff --git a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/WorkflowContainerTest.kt b/compose/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/WorkflowContainerTest.kt similarity index 100% rename from core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/WorkflowContainerTest.kt rename to compose/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/WorkflowContainerTest.kt diff --git a/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/internal/ViewFactoriesTest.kt b/compose/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/internal/ViewFactoriesTest.kt similarity index 100% rename from core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/internal/ViewFactoriesTest.kt rename to compose/core-compose/src/androidTest/java/com/squareup/workflow/ui/compose/internal/ViewFactoriesTest.kt diff --git a/core-compose/src/main/AndroidManifest.xml b/compose/core-compose/src/main/AndroidManifest.xml similarity index 100% rename from core-compose/src/main/AndroidManifest.xml rename to compose/core-compose/src/main/AndroidManifest.xml diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeRendering.kt b/compose/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeRendering.kt similarity index 100% rename from core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeRendering.kt rename to compose/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeRendering.kt diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactory.kt b/compose/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactory.kt similarity index 100% rename from core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactory.kt rename to compose/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeViewFactory.kt diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeWorkflow.kt b/compose/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeWorkflow.kt similarity index 100% rename from core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeWorkflow.kt rename to compose/core-compose/src/main/java/com/squareup/workflow/ui/compose/ComposeWorkflow.kt diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/CompositionRoot.kt b/compose/core-compose/src/main/java/com/squareup/workflow/ui/compose/CompositionRoot.kt similarity index 100% rename from core-compose/src/main/java/com/squareup/workflow/ui/compose/CompositionRoot.kt rename to compose/core-compose/src/main/java/com/squareup/workflow/ui/compose/CompositionRoot.kt diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/RenderAsState.kt b/compose/core-compose/src/main/java/com/squareup/workflow/ui/compose/RenderAsState.kt similarity index 100% rename from core-compose/src/main/java/com/squareup/workflow/ui/compose/RenderAsState.kt rename to compose/core-compose/src/main/java/com/squareup/workflow/ui/compose/RenderAsState.kt diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/ViewEnvironments.kt b/compose/core-compose/src/main/java/com/squareup/workflow/ui/compose/ViewEnvironments.kt similarity index 100% rename from core-compose/src/main/java/com/squareup/workflow/ui/compose/ViewEnvironments.kt rename to compose/core-compose/src/main/java/com/squareup/workflow/ui/compose/ViewEnvironments.kt diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/WorkflowContainer.kt b/compose/core-compose/src/main/java/com/squareup/workflow/ui/compose/WorkflowContainer.kt similarity index 100% rename from core-compose/src/main/java/com/squareup/workflow/ui/compose/WorkflowContainer.kt rename to compose/core-compose/src/main/java/com/squareup/workflow/ui/compose/WorkflowContainer.kt diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ComposeWorkflowImpl.kt b/compose/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ComposeWorkflowImpl.kt similarity index 100% rename from core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ComposeWorkflowImpl.kt rename to compose/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ComposeWorkflowImpl.kt diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ParentComposition.kt b/compose/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ParentComposition.kt similarity index 100% rename from core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ParentComposition.kt rename to compose/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ParentComposition.kt diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ViewFactories.kt b/compose/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ViewFactories.kt similarity index 100% rename from core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ViewFactories.kt rename to compose/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ViewFactories.kt diff --git a/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ViewRegistries.kt b/compose/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ViewRegistries.kt similarity index 100% rename from core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ViewRegistries.kt rename to compose/core-compose/src/main/java/com/squareup/workflow/ui/compose/internal/ViewRegistries.kt diff --git a/detekt.yml b/compose/detekt.yml similarity index 100% rename from detekt.yml rename to compose/detekt.yml diff --git a/gradle.properties b/compose/gradle.properties similarity index 100% rename from gradle.properties rename to compose/gradle.properties diff --git a/gradle/wrapper/gradle-wrapper.jar b/compose/gradle/wrapper/gradle-wrapper.jar similarity index 100% rename from gradle/wrapper/gradle-wrapper.jar rename to compose/gradle/wrapper/gradle-wrapper.jar diff --git a/gradle/wrapper/gradle-wrapper.properties b/compose/gradle/wrapper/gradle-wrapper.properties similarity index 100% rename from gradle/wrapper/gradle-wrapper.properties rename to compose/gradle/wrapper/gradle-wrapper.properties diff --git a/gradlew b/compose/gradlew similarity index 100% rename from gradlew rename to compose/gradlew diff --git a/gradlew.bat b/compose/gradlew.bat similarity index 100% rename from gradlew.bat rename to compose/gradlew.bat diff --git a/samples/build.gradle.kts b/compose/samples/build.gradle.kts similarity index 100% rename from samples/build.gradle.kts rename to compose/samples/build.gradle.kts diff --git a/samples/src/androidTest/java/com/squareup/sample/hellocomposebinding/HelloBindingTest.kt b/compose/samples/src/androidTest/java/com/squareup/sample/hellocomposebinding/HelloBindingTest.kt similarity index 100% rename from samples/src/androidTest/java/com/squareup/sample/hellocomposebinding/HelloBindingTest.kt rename to compose/samples/src/androidTest/java/com/squareup/sample/hellocomposebinding/HelloBindingTest.kt diff --git a/samples/src/androidTest/java/com/squareup/sample/hellocomposerendering/HelloComposeRenderingTest.kt b/compose/samples/src/androidTest/java/com/squareup/sample/hellocomposerendering/HelloComposeRenderingTest.kt similarity index 100% rename from samples/src/androidTest/java/com/squareup/sample/hellocomposerendering/HelloComposeRenderingTest.kt rename to compose/samples/src/androidTest/java/com/squareup/sample/hellocomposerendering/HelloComposeRenderingTest.kt diff --git a/samples/src/androidTest/java/com/squareup/sample/launcher/SampleLauncherTest.kt b/compose/samples/src/androidTest/java/com/squareup/sample/launcher/SampleLauncherTest.kt similarity index 100% rename from samples/src/androidTest/java/com/squareup/sample/launcher/SampleLauncherTest.kt rename to compose/samples/src/androidTest/java/com/squareup/sample/launcher/SampleLauncherTest.kt diff --git a/samples/src/androidTest/java/com/squareup/sample/nestedrenderings/NestedRenderingsTest.kt b/compose/samples/src/androidTest/java/com/squareup/sample/nestedrenderings/NestedRenderingsTest.kt similarity index 100% rename from samples/src/androidTest/java/com/squareup/sample/nestedrenderings/NestedRenderingsTest.kt rename to compose/samples/src/androidTest/java/com/squareup/sample/nestedrenderings/NestedRenderingsTest.kt diff --git a/samples/src/main/AndroidManifest.xml b/compose/samples/src/main/AndroidManifest.xml similarity index 100% rename from samples/src/main/AndroidManifest.xml rename to compose/samples/src/main/AndroidManifest.xml diff --git a/samples/src/main/java/com/squareup/sample/hellocompose/App.kt b/compose/samples/src/main/java/com/squareup/sample/hellocompose/App.kt similarity index 100% rename from samples/src/main/java/com/squareup/sample/hellocompose/App.kt rename to compose/samples/src/main/java/com/squareup/sample/hellocompose/App.kt diff --git a/samples/src/main/java/com/squareup/sample/hellocompose/HelloBinding.kt b/compose/samples/src/main/java/com/squareup/sample/hellocompose/HelloBinding.kt similarity index 100% rename from samples/src/main/java/com/squareup/sample/hellocompose/HelloBinding.kt rename to compose/samples/src/main/java/com/squareup/sample/hellocompose/HelloBinding.kt diff --git a/samples/src/main/java/com/squareup/sample/hellocompose/HelloComposeActivity.kt b/compose/samples/src/main/java/com/squareup/sample/hellocompose/HelloComposeActivity.kt similarity index 100% rename from samples/src/main/java/com/squareup/sample/hellocompose/HelloComposeActivity.kt rename to compose/samples/src/main/java/com/squareup/sample/hellocompose/HelloComposeActivity.kt diff --git a/samples/src/main/java/com/squareup/sample/hellocompose/HelloWorkflow.kt b/compose/samples/src/main/java/com/squareup/sample/hellocompose/HelloWorkflow.kt similarity index 100% rename from samples/src/main/java/com/squareup/sample/hellocompose/HelloWorkflow.kt rename to compose/samples/src/main/java/com/squareup/sample/hellocompose/HelloWorkflow.kt diff --git a/samples/src/main/java/com/squareup/sample/hellocomposebinding/HelloBinding.kt b/compose/samples/src/main/java/com/squareup/sample/hellocomposebinding/HelloBinding.kt similarity index 100% rename from samples/src/main/java/com/squareup/sample/hellocomposebinding/HelloBinding.kt rename to compose/samples/src/main/java/com/squareup/sample/hellocomposebinding/HelloBinding.kt diff --git a/samples/src/main/java/com/squareup/sample/hellocomposebinding/HelloBindingActivity.kt b/compose/samples/src/main/java/com/squareup/sample/hellocomposebinding/HelloBindingActivity.kt similarity index 100% rename from samples/src/main/java/com/squareup/sample/hellocomposebinding/HelloBindingActivity.kt rename to compose/samples/src/main/java/com/squareup/sample/hellocomposebinding/HelloBindingActivity.kt diff --git a/samples/src/main/java/com/squareup/sample/hellocomposebinding/HelloWorkflow.kt b/compose/samples/src/main/java/com/squareup/sample/hellocomposebinding/HelloWorkflow.kt similarity index 100% rename from samples/src/main/java/com/squareup/sample/hellocomposebinding/HelloWorkflow.kt rename to compose/samples/src/main/java/com/squareup/sample/hellocomposebinding/HelloWorkflow.kt diff --git a/samples/src/main/java/com/squareup/sample/hellocomposerendering/HelloComposeRenderingActivity.kt b/compose/samples/src/main/java/com/squareup/sample/hellocomposerendering/HelloComposeRenderingActivity.kt similarity index 100% rename from samples/src/main/java/com/squareup/sample/hellocomposerendering/HelloComposeRenderingActivity.kt rename to compose/samples/src/main/java/com/squareup/sample/hellocomposerendering/HelloComposeRenderingActivity.kt diff --git a/samples/src/main/java/com/squareup/sample/hellocomposerendering/HelloRenderingWorkflow.kt b/compose/samples/src/main/java/com/squareup/sample/hellocomposerendering/HelloRenderingWorkflow.kt similarity index 100% rename from samples/src/main/java/com/squareup/sample/hellocomposerendering/HelloRenderingWorkflow.kt rename to compose/samples/src/main/java/com/squareup/sample/hellocomposerendering/HelloRenderingWorkflow.kt diff --git a/samples/src/main/java/com/squareup/sample/hellocomposerendering/HelloWorkflow.kt b/compose/samples/src/main/java/com/squareup/sample/hellocomposerendering/HelloWorkflow.kt similarity index 100% rename from samples/src/main/java/com/squareup/sample/hellocomposerendering/HelloWorkflow.kt rename to compose/samples/src/main/java/com/squareup/sample/hellocomposerendering/HelloWorkflow.kt diff --git a/samples/src/main/java/com/squareup/sample/launcher/SampleLauncherActivity.kt b/compose/samples/src/main/java/com/squareup/sample/launcher/SampleLauncherActivity.kt similarity index 100% rename from samples/src/main/java/com/squareup/sample/launcher/SampleLauncherActivity.kt rename to compose/samples/src/main/java/com/squareup/sample/launcher/SampleLauncherActivity.kt diff --git a/samples/src/main/java/com/squareup/sample/launcher/SampleLauncherApp.kt b/compose/samples/src/main/java/com/squareup/sample/launcher/SampleLauncherApp.kt similarity index 100% rename from samples/src/main/java/com/squareup/sample/launcher/SampleLauncherApp.kt rename to compose/samples/src/main/java/com/squareup/sample/launcher/SampleLauncherApp.kt diff --git a/samples/src/main/java/com/squareup/sample/launcher/Samples.kt b/compose/samples/src/main/java/com/squareup/sample/launcher/Samples.kt similarity index 100% rename from samples/src/main/java/com/squareup/sample/launcher/Samples.kt rename to compose/samples/src/main/java/com/squareup/sample/launcher/Samples.kt diff --git a/samples/src/main/java/com/squareup/sample/nestedrenderings/LegacyRunner.kt b/compose/samples/src/main/java/com/squareup/sample/nestedrenderings/LegacyRunner.kt similarity index 100% rename from samples/src/main/java/com/squareup/sample/nestedrenderings/LegacyRunner.kt rename to compose/samples/src/main/java/com/squareup/sample/nestedrenderings/LegacyRunner.kt diff --git a/samples/src/main/java/com/squareup/sample/nestedrenderings/NestedRenderingsActivity.kt b/compose/samples/src/main/java/com/squareup/sample/nestedrenderings/NestedRenderingsActivity.kt similarity index 100% rename from samples/src/main/java/com/squareup/sample/nestedrenderings/NestedRenderingsActivity.kt rename to compose/samples/src/main/java/com/squareup/sample/nestedrenderings/NestedRenderingsActivity.kt diff --git a/samples/src/main/java/com/squareup/sample/nestedrenderings/RecursiveViewFactory.kt b/compose/samples/src/main/java/com/squareup/sample/nestedrenderings/RecursiveViewFactory.kt similarity index 100% rename from samples/src/main/java/com/squareup/sample/nestedrenderings/RecursiveViewFactory.kt rename to compose/samples/src/main/java/com/squareup/sample/nestedrenderings/RecursiveViewFactory.kt diff --git a/samples/src/main/java/com/squareup/sample/nestedrenderings/RecursiveWorkflow.kt b/compose/samples/src/main/java/com/squareup/sample/nestedrenderings/RecursiveWorkflow.kt similarity index 100% rename from samples/src/main/java/com/squareup/sample/nestedrenderings/RecursiveWorkflow.kt rename to compose/samples/src/main/java/com/squareup/sample/nestedrenderings/RecursiveWorkflow.kt diff --git a/samples/src/main/java/com/squareup/sample/textinput/App.kt b/compose/samples/src/main/java/com/squareup/sample/textinput/App.kt similarity index 100% rename from samples/src/main/java/com/squareup/sample/textinput/App.kt rename to compose/samples/src/main/java/com/squareup/sample/textinput/App.kt diff --git a/samples/src/main/java/com/squareup/sample/textinput/TextInputActivity.kt b/compose/samples/src/main/java/com/squareup/sample/textinput/TextInputActivity.kt similarity index 100% rename from samples/src/main/java/com/squareup/sample/textinput/TextInputActivity.kt rename to compose/samples/src/main/java/com/squareup/sample/textinput/TextInputActivity.kt diff --git a/samples/src/main/java/com/squareup/sample/textinput/TextInputViewFactory.kt b/compose/samples/src/main/java/com/squareup/sample/textinput/TextInputViewFactory.kt similarity index 100% rename from samples/src/main/java/com/squareup/sample/textinput/TextInputViewFactory.kt rename to compose/samples/src/main/java/com/squareup/sample/textinput/TextInputViewFactory.kt diff --git a/samples/src/main/java/com/squareup/sample/textinput/TextInputWorkflow.kt b/compose/samples/src/main/java/com/squareup/sample/textinput/TextInputWorkflow.kt similarity index 100% rename from samples/src/main/java/com/squareup/sample/textinput/TextInputWorkflow.kt rename to compose/samples/src/main/java/com/squareup/sample/textinput/TextInputWorkflow.kt diff --git a/samples/src/main/res/layout/legacy_view.xml b/compose/samples/src/main/res/layout/legacy_view.xml similarity index 100% rename from samples/src/main/res/layout/legacy_view.xml rename to compose/samples/src/main/res/layout/legacy_view.xml diff --git a/samples/src/main/res/values/dimens.xml b/compose/samples/src/main/res/values/dimens.xml similarity index 100% rename from samples/src/main/res/values/dimens.xml rename to compose/samples/src/main/res/values/dimens.xml diff --git a/samples/src/main/res/values/strings.xml b/compose/samples/src/main/res/values/strings.xml similarity index 100% rename from samples/src/main/res/values/strings.xml rename to compose/samples/src/main/res/values/strings.xml diff --git a/samples/src/main/res/values/styles.xml b/compose/samples/src/main/res/values/styles.xml similarity index 100% rename from samples/src/main/res/values/styles.xml rename to compose/samples/src/main/res/values/styles.xml diff --git a/settings.gradle.kts b/compose/settings.gradle.kts similarity index 100% rename from settings.gradle.kts rename to compose/settings.gradle.kts From 6a9257c2899af25233d7ad367f11c6c1adc3daab Mon Sep 17 00:00:00 2001 From: Zach Klippenstein Date: Thu, 4 Feb 2021 13:38:26 -0800 Subject: [PATCH 67/67] Exclude compose directory from some build checks where it has its own. --- .github/CODEOWNERS | 5 +++-- .github/workflows/compose-kotlin.yml | 5 +---- .github/workflows/kotlin.yml | 2 ++ lint_docs.sh | 1 + 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index a047c38d59..4df9c5ac03 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -2,5 +2,6 @@ # the repo, unless a later match takes precedence. * @square/mobile-foundation-android zach-klippenstein -# Compose integration -compose/* @square/mobile-foundation-android @square/ui-systems-android @wardellbagby +# Any files under the compose directory in the repo root should +# be reviewed by UI Systems as well. +/compose/ @square/mobile-foundation-android @square/ui-systems-android @wardellbagby diff --git a/.github/workflows/compose-kotlin.yml b/.github/workflows/compose-kotlin.yml index 265c3381f2..c78e557ff0 100644 --- a/.github/workflows/compose-kotlin.yml +++ b/.github/workflows/compose-kotlin.yml @@ -1,10 +1,7 @@ -name: Kotlin CI +name: Compose CI on: push: - paths: - - 'compose/**' - - '.github/workflows/compose-kotlin.yml' branches: [main] pull_request: paths: diff --git a/.github/workflows/kotlin.yml b/.github/workflows/kotlin.yml index a5502ad031..70a3e5aa74 100644 --- a/.github/workflows/kotlin.yml +++ b/.github/workflows/kotlin.yml @@ -8,6 +8,8 @@ on: paths-ignore: # Don't build the entire app when just changing tutorials, which have their own workflow. - 'samples/tutorial/**' + # The compose integration has its own workflow. + - 'compose/**' jobs: dokka: diff --git a/lint_docs.sh b/lint_docs.sh index 5ce9d469cc..c4025e26e3 100755 --- a/lint_docs.sh +++ b/lint_docs.sh @@ -17,6 +17,7 @@ find . \ -not -name 'CHANGELOG.md' \ -not -path './.github/*' \ -not -path $TUTORIALS_DIR/'*' \ + -not -path './compose/*' \ | xargs mdl --style $STYLE --ignore-front-matter \ find $TUTORIALS_DIR \