├── .editorconfig ├── .github ├── CODEOWNERS ├── pull_request_template.md └── workflows │ ├── android.yml │ ├── publish-snapshot.yml │ └── publish.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ ├── kotlin │ └── io │ │ └── getstream │ │ └── avatarviewdemo │ │ ├── MainActivity.kt │ │ └── Samples.kt │ └── res │ ├── drawable-v24 │ └── ic_launcher_foreground.xml │ ├── drawable │ ├── ic_launcher_background.xml │ └── stream.png │ ├── layout │ └── activity_main.xml │ ├── mipmap-anydpi-v26 │ ├── ic_launcher.xml │ └── ic_launcher_round.xml │ ├── mipmap-hdpi │ ├── ic_launcher.webp │ └── ic_launcher_round.webp │ ├── mipmap-mdpi │ ├── ic_launcher.webp │ └── ic_launcher_round.webp │ ├── mipmap-xhdpi │ ├── ic_launcher.webp │ └── ic_launcher_round.webp │ ├── mipmap-xxhdpi │ ├── ic_launcher.webp │ └── ic_launcher_round.webp │ ├── mipmap-xxxhdpi │ ├── ic_launcher.webp │ └── ic_launcher_round.webp │ ├── values-night │ └── themes.xml │ └── values │ ├── colors.xml │ ├── strings.xml │ └── themes.xml ├── avatarview-coil ├── .gitignore ├── api │ └── avatarview-coil.api ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ └── kotlin │ └── io │ └── getstream │ └── avatarview │ └── coil │ ├── Avatar.kt │ ├── AvatarBitmapFactory.kt │ ├── AvatarCoil.kt │ ├── AvatarFetcher.kt │ ├── AvatarImageLoaderFactory.kt │ ├── AvatarImageLoaderInternal.kt │ ├── AvatarViewExtension.kt │ └── ImageHeadersProvider.kt ├── avatarview-glide ├── .gitignore ├── README.md ├── api │ └── avatarview-glide.api ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ └── kotlin │ └── io │ └── getstream │ └── avatarview │ └── glide │ ├── AvatarBitmapLoader.kt │ ├── AvatarResult.kt │ └── AvatarViewExtension.kt ├── avatarview-stream-integration ├── .gitignore ├── api │ └── avatarview-stream-integration.api ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ ├── kotlin │ └── io │ │ └── getstream │ │ └── avatarview │ │ └── stream │ │ └── integration │ │ ├── AvatarViewStreamIntegration.kt │ │ └── StreamAvatarBitmapFactory.kt │ └── res │ └── values │ └── colors.xml ├── avatarview ├── .gitignore ├── api │ └── avatarview.api ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ ├── kotlin │ └── io │ │ └── getstream │ │ └── avatarview │ │ ├── AvatarBitmapCombiner.kt │ │ ├── AvatarShape.kt │ │ ├── AvatarView.kt │ │ ├── IndicatorPosition.kt │ │ └── internal │ │ ├── Extensions.kt │ │ ├── InternalAvatarViewApi.kt │ │ ├── Properties.kt │ │ └── ViewProperty.kt │ └── res │ └── values │ └── attrs_avatar_view.xml ├── build.gradle ├── buildSrc ├── build.gradle.kts └── src │ └── main │ └── kotlin │ └── io │ └── getstream │ └── avatarview │ ├── Configuration.kt │ └── Dependencies.kt ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── preview ├── dokka-avatarview.svg ├── preview0.png ├── preview1.png ├── preview10.png ├── preview2.png ├── preview3.png ├── preview4.png ├── preview5.png ├── preview6.png ├── preview7.gif ├── preview8.png └── preview9.png ├── scripts ├── publish-module.gradle └── publish-root.gradle ├── settings.gradle └── spotless ├── copyright.kt ├── copyright.xml └── spotless.gradle /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | [*] 3 | # Most of the standard properties are supported 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_size = 4 7 | indent_style = space 8 | max_line_length = 100 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Lines starting with '#' are comments. 2 | # Each line is a file pattern followed by one or more owners. 3 | 4 | # More details are here: https://help.github.com/articles/about-codeowners/ 5 | 6 | # The '*' pattern is global owners. 7 | # Not adding in this PR, but I'd like to try adding a global owner set with the entire team. 8 | # One interpretation of their docs is that global owners are added only if not removed 9 | # by a more local rule. 10 | 11 | # Order is important. The last matching pattern has the most precedence. 12 | # The folders are ordered as follows: 13 | 14 | # In each subsection folders are ordered first by depth, then alphabetically. 15 | # This should make it easy to add new rules without breaking existing ones. 16 | * @skydoves -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ### 🎯 Goal 2 | Describe the big picture of your changes here to communicate to the maintainers why we should accept this pull request. If it fixes a bug or resolves a feature request, be sure to link to that issue. 3 | 4 | ### 🛠 Implementation details 5 | Describe the implementation details for this Pull Request. 6 | 7 | ### ✍️ Explain examples 8 | Explain examples with code for this updates. 9 | 10 | ### Preparing a pull request for review 11 | Ensure your change is properly formatted by running: 12 | 13 | ```gradle 14 | $ ./gradlew spotlessApply 15 | ``` 16 | 17 | Please correct any failures before requesting a review. -------------------------------------------------------------------------------- /.github/workflows/android.yml: -------------------------------------------------------------------------------- 1 | name: Android CI 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | 15 | - name: set up JDK 16 | uses: actions/setup-java@v1 17 | with: 18 | java-version: 11 19 | 20 | - name: Cache Gradle and wrapper 21 | uses: actions/cache@v2 22 | with: 23 | path: | 24 | ~/.gradle/caches 25 | ~/.gradle/wrapper 26 | key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }} 27 | restore-keys: | 28 | ${{ runner.os }}-gradle- 29 | - name: Make Gradle executable 30 | run: chmod +x ./gradlew 31 | 32 | - name: Build with Gradle 33 | run: ./gradlew build -------------------------------------------------------------------------------- /.github/workflows/publish-snapshot.yml: -------------------------------------------------------------------------------- 1 | name: Publish Snapshot builds 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | publish: 10 | name: Snapshot build and publish 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Check out code 14 | uses: actions/checkout@v2 15 | - name: Set up JDK 11 16 | uses: actions/setup-java@v2 17 | with: 18 | distribution: adopt 19 | java-version: 11 20 | - name: Release build 21 | run: ./gradlew assemble --scan 22 | - name: Source jar and dokka 23 | run: ./gradlew androidSourcesJar javadocJar --scan 24 | - name: Publish to MavenCentral 25 | run: ./gradlew publishReleasePublicationToSonatypeRepository --scan 26 | env: 27 | OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }} 28 | OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }} 29 | SIGNING_KEY_ID: ${{ secrets.SIGNING_KEY_ID }} 30 | SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }} 31 | SIGNING_KEY: ${{ secrets.SIGNING_KEY }} 32 | SONATYPE_STAGING_PROFILE_ID: ${{ secrets.SONATYPE_STAGING_PROFILE_ID }} 33 | SNAPSHOT: true 34 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | release: 5 | types: [released] 6 | 7 | jobs: 8 | publish: 9 | name: Release build and publish 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Check out code 13 | uses: actions/checkout@v2 14 | - name: Set up JDK 11 15 | uses: actions/setup-java@v2 16 | with: 17 | distribution: adopt 18 | java-version: 11 19 | - name: Release build 20 | run: ./gradlew assemble --scan 21 | - name: Source jar and dokka 22 | run: ./gradlew androidSourcesJar javadocJar --scan 23 | - name: Publish to MavenCentral 24 | run: ./gradlew publishReleasePublicationToSonatypeRepository --max-workers 1 closeAndReleaseSonatypeStagingRepository --scan 25 | env: 26 | OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }} 27 | OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }} 28 | SIGNING_KEY_ID: ${{ secrets.SIGNING_KEY_ID }} 29 | SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }} 30 | SIGNING_KEY: ${{ secrets.SIGNING_KEY }} 31 | SONATYPE_STAGING_PROFILE_ID: ${{ secrets.SONATYPE_STAGING_PROFILE_ID }} 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the ART/Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | out/ 15 | 16 | # Gradle files 17 | /.idea 18 | .gradle/ 19 | build/ 20 | 21 | # Local configuration file (sdk path, etc) 22 | local.properties 23 | 24 | # Proguard folder generated by Eclipse 25 | proguard/ 26 | 27 | # Log Files 28 | *.log 29 | 30 | # Android Studio Navigation editor temp files 31 | .navigation/ 32 | 33 | # Android Studio captures folder 34 | captures/ 35 | 36 | # Intellij 37 | *.iml 38 | .idea/workspace.xml 39 | .idea/tasks.xml 40 | .idea/gradle.xml 41 | .idea/dictionaries 42 | .idea/libraries 43 | app/.idea/ 44 | 45 | # Mac 46 | *.DS_Store 47 | 48 | # Keystore files 49 | *.jks 50 | 51 | # External native build folder generated in Android Studio 2.2 and later 52 | .externalNativeBuild 53 | 54 | # Google Services (e.g. APIs or Firebase) 55 | google-services.json 56 | 57 | # Temporary API docs 58 | docs/api 59 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | GetStream. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## How to contribute 2 | We'd love to accept your patches and contributions to this project. There are just a few small guidelines you need to follow. 3 | 4 | ## Preparing a pull request for review 5 | Ensure your change is properly formatted by running: 6 | 7 | ```gradle 8 | ./gradlew spotlessApply 9 | ``` 10 | 11 | Then dump binary API of this library that is public in sense of Kotlin visibilities and ensures that the public binary API wasn't changed in a way that make this change binary incompatible. 12 | 13 | ```gradle 14 | ./gradlew apiDump 15 | ``` 16 | 17 | Please correct any failures before requesting a review. 18 | 19 | ## Code reviews 20 | All submissions, including submissions by project members, require review. We use GitHub pull requests for this purpose. Consult [GitHub Help](https://docs.github.com/en/github/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-pull-requests) for more information on using pull requests. 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | import io.getstream.avatarview.Configuration 2 | import io.getstream.avatarview.Dependencies 3 | 4 | plugins { 5 | id 'com.android.application' 6 | id 'org.jetbrains.kotlin.android' 7 | } 8 | 9 | android { 10 | compileSdkVersion Configuration.compileSdk 11 | defaultConfig { 12 | applicationId "io.getstream.avatarviewdemo" 13 | minSdkVersion Configuration.minSdk 14 | targetSdkVersion Configuration.targetSdk 15 | versionCode Configuration.versionCode 16 | versionName Configuration.versionName 17 | } 18 | 19 | compileOptions { 20 | sourceCompatibility JavaVersion.VERSION_11 21 | targetCompatibility JavaVersion.VERSION_11 22 | } 23 | 24 | kotlinOptions { 25 | jvmTarget = "11" 26 | } 27 | 28 | buildFeatures { 29 | viewBinding true 30 | dataBinding true 31 | } 32 | 33 | lintOptions { 34 | abortOnError false 35 | } 36 | } 37 | 38 | dependencies { 39 | implementation Dependencies.material 40 | implementation project(":avatarview-coil") 41 | implementation project(":avatarview-glide") 42 | implementation project(":avatarview-stream-integration") 43 | } 44 | 45 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 20 | 21 | 22 | 29 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /app/src/main/kotlin/io/getstream/avatarviewdemo/MainActivity.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Stream.IO, Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.getstream.avatarviewdemo 18 | 19 | import android.os.Bundle 20 | import androidx.appcompat.app.AppCompatActivity 21 | import coil.transform.RoundedCornersTransformation 22 | import io.getstream.avatarview.coil.loadImage 23 | import io.getstream.avatarviewdemo.Samples.cats 24 | import io.getstream.avatarviewdemo.databinding.ActivityMainBinding 25 | 26 | class MainActivity : AppCompatActivity() { 27 | 28 | override fun onCreate(savedInstanceState: Bundle?) { 29 | super.onCreate(savedInstanceState) 30 | 31 | val binding = ActivityMainBinding.inflate(layoutInflater) 32 | setContentView(binding.root) 33 | 34 | with(binding) { 35 | avatarView1.loadImage(cats.take(1)) 36 | 37 | avatarView2.loadImage( 38 | cats.take(2) 39 | ) { 40 | crossfade(true) 41 | crossfade(300) 42 | lifecycle(this@MainActivity) 43 | } 44 | 45 | avatarView3.loadImage( 46 | cats.take(3) 47 | ) { 48 | crossfade(true) 49 | crossfade(400) 50 | lifecycle(this@MainActivity) 51 | } 52 | 53 | avatarView4.loadImage( 54 | cats.take(4) 55 | ) { 56 | crossfade(true) 57 | crossfade(400) 58 | lifecycle(this@MainActivity) 59 | } 60 | 61 | avatarView5.loadImage( 62 | cats.take(1) 63 | ) { 64 | crossfade(true) 65 | crossfade(400) 66 | lifecycle(this@MainActivity) 67 | } 68 | 69 | avatarView6.loadImage( 70 | cats.take(2) 71 | ) { 72 | crossfade(true) 73 | crossfade(400) 74 | lifecycle(this@MainActivity) 75 | } 76 | 77 | avatarView7.loadImage( 78 | cats.take(3) 79 | ) { 80 | crossfade(true) 81 | crossfade(400) 82 | lifecycle(this@MainActivity) 83 | transformations( 84 | RoundedCornersTransformation(36f) 85 | ) 86 | } 87 | 88 | avatarView8.loadImage( 89 | cats.take(4) 90 | ) { 91 | crossfade(true) 92 | crossfade(400) 93 | lifecycle(this@MainActivity) 94 | } 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /app/src/main/kotlin/io/getstream/avatarviewdemo/Samples.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Stream.IO, Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.getstream.avatarviewdemo 18 | 19 | object Samples { 20 | 21 | val cats: List 22 | get() = listOf( 23 | "https://swiftype-ss.imgix.net/https%3A%2F%2Fcdn.petcarerx.com%2FLPPE%2Fimages%2Farticlethumbs%2FFluffy-Cats-Small.jpg?ixlib=rb-1.1.0&h=320&fit=clip&dpr=2.0&s=c81a75f749ea4ed736b7607100cb52cc.png", 24 | "https://images.ctfassets.net/cnu0m8re1exe/1GxSYi0mQSp9xJ5svaWkVO/d151a93af61918c234c3049e0d6393e1/93347270_cat-1151519_1280.jpg?fm=jpg&fl=progressive&w=660&h=433&fit=fill", 25 | "https://img.webmd.com/dtmcms/live/webmd/consumer_assets/site_images/article_thumbnails/other/cat_relaxing_on_patio_other/1800x1200_cat_relaxing_on_patio_other.jpg", 26 | "https://post.healthline.com/wp-content/uploads/2020/08/cat-thumb2-732x415.jpg", 27 | ).shuffled() 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 23 | 24 | 25 | 31 | 34 | 37 | 38 | 39 | 40 | 46 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 22 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 175 | 180 | 185 | 186 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/stream.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/avatarview-android/d0bb30467a12c1efae00e41eb260bc2f1f3b4f38/app/src/main/res/drawable/stream.png -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 20 | 21 | 22 | 23 | 24 | 25 | 29 | 30 | 52 | 53 | 76 | 77 | 100 | 101 | 119 | 120 | 144 | 145 | 165 | 166 | 187 | 188 | 209 | 210 | 211 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/avatarview-android/d0bb30467a12c1efae00e41eb260bc2f1f3b4f38/app/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/avatarview-android/d0bb30467a12c1efae00e41eb260bc2f1f3b4f38/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/avatarview-android/d0bb30467a12c1efae00e41eb260bc2f1f3b4f38/app/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/avatarview-android/d0bb30467a12c1efae00e41eb260bc2f1f3b4f38/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/avatarview-android/d0bb30467a12c1efae00e41eb260bc2f1f3b4f38/app/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/avatarview-android/d0bb30467a12c1efae00e41eb260bc2f1f3b4f38/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/avatarview-android/d0bb30467a12c1efae00e41eb260bc2f1f3b4f38/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/avatarview-android/d0bb30467a12c1efae00e41eb260bc2f1f3b4f38/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/avatarview-android/d0bb30467a12c1efae00e41eb260bc2f1f3b4f38/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/avatarview-android/d0bb30467a12c1efae00e41eb260bc2f1f3b4f38/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 32 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | #212121 19 | #005FFF 20 | #005FFF 21 | #005FFF 22 | #FF03DAC5 23 | #FF018786 24 | #FFFFFFFF 25 | #57A8D8 26 | #FBC02D 27 | #FFD600 28 | #FBC02D 29 | #FFA000 30 | #FFA726 31 | #FF6D00 32 | #81C784 33 | #388E3C 34 | #81D4fA 35 | #0091EA 36 | #FF000000 37 | #FF0000 38 | #FF7F00 39 | #7FFF00 40 | #00FF00 41 | #00FF7F 42 | #00FFFF 43 | #007FFF 44 | #0000FF 45 | #7F00FF 46 | #FF00FF 47 | 48 | 49 | @color/md_yellow_100 50 | @color/md_orange_100 51 | @color/md_green_100 52 | @color/md_blue_100 53 | 54 | 55 | 56 | @color/red 57 | @color/orange 58 | @color/yellow 59 | @color/chartreuse 60 | @color/green 61 | @color/spring_green 62 | @color/cyan 63 | @color/azure 64 | @color/blue 65 | @color/violet 66 | @color/magenta 67 | 68 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | AvatarView 19 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 32 | -------------------------------------------------------------------------------- /avatarview-coil/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /avatarview-coil/api/avatarview-coil.api: -------------------------------------------------------------------------------- 1 | public final class io/getstream/avatarview/coil/Avatar { 2 | public fun (Ljava/util/List;IILandroid/graphics/drawable/Drawable;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;)V 3 | public final fun component1 ()Ljava/util/List; 4 | public final fun component2 ()I 5 | public final fun component3 ()I 6 | public final fun component4 ()Landroid/graphics/drawable/Drawable; 7 | public final fun component5 ()Lkotlin/jvm/functions/Function2; 8 | public final fun component6 ()Lkotlin/jvm/functions/Function2; 9 | public final fun copy (Ljava/util/List;IILandroid/graphics/drawable/Drawable;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;)Lio/getstream/avatarview/coil/Avatar; 10 | public static synthetic fun copy$default (Lio/getstream/avatarview/coil/Avatar;Ljava/util/List;IILandroid/graphics/drawable/Drawable;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lio/getstream/avatarview/coil/Avatar; 11 | public fun equals (Ljava/lang/Object;)Z 12 | public final fun getAvatarBorderWidth ()I 13 | public final fun getData ()Ljava/util/List; 14 | public final fun getErrorPlaceholder ()Landroid/graphics/drawable/Drawable; 15 | public final fun getMaxSectionSize ()I 16 | public final fun getOnError ()Lkotlin/jvm/functions/Function2; 17 | public final fun getOnSuccess ()Lkotlin/jvm/functions/Function2; 18 | public final fun getTag (Ljava/lang/String;)Ljava/lang/Object; 19 | public fun hashCode ()I 20 | public final fun setTagIfAbsent (Ljava/lang/String;Ljava/lang/Object;)V 21 | public fun toString ()Ljava/lang/String; 22 | } 23 | 24 | public class io/getstream/avatarview/coil/AvatarBitmapFactory { 25 | public fun (Landroid/content/Context;)V 26 | public fun avatarBitmapKey (Lio/getstream/avatarview/coil/Avatar;)Ljava/lang/String; 27 | public fun createAvatarBitmaps (Lio/getstream/avatarview/coil/Avatar;ILkotlin/coroutines/Continuation;)Ljava/lang/Object; 28 | public fun loadAvatarBitmap (Ljava/lang/Object;Lio/getstream/avatarview/coil/Avatar;ILkotlin/coroutines/Continuation;)Ljava/lang/Object; 29 | public fun loadAvatarBitmapBlocking (Ljava/lang/Object;Lio/getstream/avatarview/coil/Avatar;I)Landroid/graphics/Bitmap; 30 | public fun loadAvatarPlaceholderBitmap (Ljava/lang/Object;Lio/getstream/avatarview/coil/Avatar;ILkotlin/coroutines/Continuation;)Ljava/lang/Object; 31 | public fun loadAvatarPlaceholderBitmapBlocking (Ljava/lang/Object;Lio/getstream/avatarview/coil/Avatar;I)Landroid/graphics/Bitmap; 32 | } 33 | 34 | public final class io/getstream/avatarview/coil/AvatarCoil { 35 | public static final field INSTANCE Lio/getstream/avatarview/coil/AvatarCoil; 36 | public final fun getAvatarBitmapFactory (Landroid/content/Context;)Lio/getstream/avatarview/coil/AvatarBitmapFactory; 37 | public final fun getImageHeadersProvider ()Lio/getstream/avatarview/coil/ImageHeadersProvider; 38 | public final fun setAvatarBitmapFactory (Lio/getstream/avatarview/coil/AvatarBitmapFactory;)V 39 | public final fun setImageHeadersProvider (Lio/getstream/avatarview/coil/ImageHeadersProvider;)V 40 | public final fun setImageLoader (Lcoil/ImageLoaderFactory;)V 41 | } 42 | 43 | public final class io/getstream/avatarview/coil/AvatarImageLoaderFactory : coil/ImageLoaderFactory { 44 | public fun (Landroid/content/Context;Lkotlin/jvm/functions/Function1;)V 45 | public synthetic fun (Landroid/content/Context;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V 46 | public fun newImageLoader ()Lcoil/ImageLoader; 47 | } 48 | 49 | public final class io/getstream/avatarview/coil/AvatarImageLoaderInternal { 50 | public static final field INSTANCE Lio/getstream/avatarview/coil/AvatarImageLoaderInternal; 51 | public final synthetic fun loadAsBitmap (Landroid/content/Context;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; 52 | public static synthetic fun loadAsBitmap$default (Lio/getstream/avatarview/coil/AvatarImageLoaderInternal;Landroid/content/Context;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; 53 | } 54 | 55 | public final class io/getstream/avatarview/coil/AvatarViewExtension { 56 | public static final synthetic fun loadImage (Lio/getstream/avatarview/AvatarView;Ljava/lang/Object;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;)V 57 | public static final synthetic fun loadImage (Lio/getstream/avatarview/AvatarView;Ljava/util/List;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;)V 58 | public static final synthetic fun loadImage (Lio/getstream/avatarview/AvatarView;[Ljava/lang/Object;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;)V 59 | public static synthetic fun loadImage$default (Lio/getstream/avatarview/AvatarView;Ljava/lang/Object;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V 60 | public static synthetic fun loadImage$default (Lio/getstream/avatarview/AvatarView;Ljava/util/List;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V 61 | public static synthetic fun loadImage$default (Lio/getstream/avatarview/AvatarView;[Ljava/lang/Object;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V 62 | } 63 | 64 | public abstract interface class io/getstream/avatarview/coil/ImageHeadersProvider { 65 | public abstract fun getImageRequestHeaders ()Ljava/util/Map; 66 | } 67 | 68 | -------------------------------------------------------------------------------- /avatarview-coil/build.gradle: -------------------------------------------------------------------------------- 1 | import io.getstream.avatarview.Configuration 2 | import io.getstream.avatarview.Dependencies 3 | 4 | plugins { 5 | id 'com.android.library' 6 | id 'org.jetbrains.kotlin.android' 7 | id 'org.jetbrains.dokka' 8 | id 'binary-compatibility-validator' 9 | } 10 | 11 | ext { 12 | PUBLISH_GROUP_ID = Configuration.artifactGroup 13 | if (snapshot) { 14 | PUBLISH_VERSION = Configuration.snapshotVersionName 15 | } else { 16 | PUBLISH_VERSION = Configuration.versionName 17 | } 18 | PUBLISH_ARTIFACT_ID = 'avatarview-coil' 19 | } 20 | 21 | apply from: "${rootDir}/scripts/publish-module.gradle" 22 | 23 | android { 24 | compileSdkVersion Configuration.compileSdk 25 | defaultConfig { 26 | minSdkVersion Configuration.minSdk 27 | targetSdkVersion Configuration.targetSdk 28 | } 29 | 30 | buildFeatures { 31 | buildConfig false 32 | } 33 | 34 | compileOptions { 35 | sourceCompatibility JavaVersion.VERSION_1_8 36 | targetCompatibility JavaVersion.VERSION_1_8 37 | } 38 | 39 | kotlinOptions { 40 | jvmTarget = '1.8' 41 | } 42 | 43 | lintOptions { 44 | abortOnError false 45 | } 46 | } 47 | 48 | tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { 49 | kotlinOptions.freeCompilerArgs += [ 50 | "-Xexplicit-api=strict", 51 | "-Xopt-in=io.getstream.avatarview.internal.InternalAvatarViewApi", 52 | ] 53 | } 54 | 55 | apiValidation { 56 | nonPublicMarkers += ["kotlin.PublishedApi"] 57 | } 58 | 59 | dependencies { 60 | api project(":avatarview") 61 | api Dependencies.coil 62 | api Dependencies.coilGif 63 | 64 | implementation Dependencies.androidxAppcompat 65 | implementation Dependencies.coroutines 66 | implementation Dependencies.okhttp 67 | } 68 | -------------------------------------------------------------------------------- /avatarview-coil/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | -------------------------------------------------------------------------------- /avatarview-coil/src/main/kotlin/io/getstream/avatarview/coil/Avatar.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Stream.IO, Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.getstream.avatarview.coil 18 | 19 | import android.graphics.Bitmap 20 | import android.graphics.drawable.Drawable 21 | import android.net.Uri 22 | import androidx.annotation.DrawableRes 23 | import coil.request.ErrorResult 24 | import coil.request.ImageRequest 25 | import coil.request.SuccessResult 26 | import okhttp3.HttpUrl 27 | import java.io.File 28 | 29 | /** 30 | * A data transfer model for transferring image data to [AvatarFetcher]. 31 | * This model will be fetched by [AvatarFetcher] when we request image loading by using this type to Coil. 32 | * 33 | * The default supported data types are: 34 | * - [String] (mapped to a [Uri]) 35 | * - [Uri] ("android.resource", "content", "file", "http", and "https" schemes only) 36 | * - [HttpUrl] 37 | * - [File] 38 | * - [DrawableRes] 39 | * - [Drawable] 40 | * - [Bitmap] 41 | * 42 | * @param data A list of image data. 43 | * @param maxSectionSize The maximum section size of the avatar when loading multiple images. 44 | * @param avatarBorderWidth The border width of AvatarView. 45 | * @param errorPlaceholder An error placeholder that should be shown when request failed. 46 | */ 47 | public data class Avatar( 48 | 49 | /** A list of data to be requested. */ 50 | val data: List, 51 | 52 | /** The maximum size of the sections. */ 53 | val maxSectionSize: Int, 54 | 55 | /** The border width of AvatarView. */ 56 | val avatarBorderWidth: Int, 57 | 58 | /** An error placeholder that should be shown when request failed. */ 59 | val errorPlaceholder: Drawable?, 60 | 61 | /** A lambda function will be executed when loading succeeds. */ 62 | val onSuccess: (request: ImageRequest, result: SuccessResult) -> Unit, 63 | 64 | /** A lambda function will be executed when loading failed. */ 65 | val onError: (request: ImageRequest, result: ErrorResult) -> Unit, 66 | ) { 67 | private val bagOfTags: MutableMap = mutableMapOf() 68 | 69 | /** 70 | * Sets a tag associated with this avatar and a key. 71 | * If the given [newValue] was already set for the given key, this calls do nothing, 72 | * the given [newValue] would be ignored. 73 | * 74 | * @param key A new key to set a tag associated with this avatar. 75 | * @param newValue A new value to be set on the bag. 76 | */ 77 | @Suppress("UNCHECKED_CAST") 78 | public fun setTagIfAbsent(key: String, newValue: T) { 79 | synchronized(bagOfTags) { 80 | val previous = bagOfTags[key] as? T 81 | if (previous == null) { 82 | bagOfTags[key] = newValue 83 | } 84 | } 85 | } 86 | 87 | /** 88 | * Returns the tag associated with this avatar using the given [key]. 89 | * 90 | * @param key A new key to get a tag associated with this avatar. 91 | * 92 | * @return A tag in the bag. 93 | */ 94 | @Suppress("UNCHECKED_CAST") 95 | public fun getTag(key: String): T? { 96 | synchronized(bagOfTags) { 97 | return bagOfTags[key] as? T 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /avatarview-coil/src/main/kotlin/io/getstream/avatarview/coil/AvatarBitmapFactory.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Stream.IO, Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.getstream.avatarview.coil 18 | 19 | import android.content.Context 20 | import android.graphics.Bitmap 21 | import androidx.annotation.Px 22 | import coil.ImageLoader 23 | import io.getstream.avatarview.AvatarBitmapCombiner 24 | import kotlinx.coroutines.Dispatchers 25 | import kotlinx.coroutines.withContext 26 | 27 | /** 28 | * A Bitmap factory to create avatar bitmaps. 29 | */ 30 | public open class AvatarBitmapFactory(private val context: Context) { 31 | 32 | /** 33 | * Creates a Bitmap with the image request [data] to represent the avatar in a suspending operation. 34 | * 35 | * @param data An image request model. 36 | * @param avatar An [Avatar] data model which includes request model and avatar styles. 37 | * @param avatarSize A specified avatar size. 38 | * 39 | * @return The loaded bitmap or null if the loading failed (e.g. network issues). 40 | */ 41 | @JvmSynthetic 42 | internal suspend fun createAvatarBitmapInternal( 43 | data: Any?, 44 | avatar: Avatar, 45 | @Px avatarSize: Int 46 | ): Bitmap? { 47 | val customBitmap = withContext(Dispatchers.IO) { 48 | loadAvatarBitmapBlocking(data, avatar, avatarSize) 49 | } 50 | 51 | val bitmap = customBitmap ?: loadAvatarBitmap(data, avatar, avatarSize) 52 | if (bitmap != null) { 53 | return bitmap 54 | } 55 | 56 | val customPlaceholderBitmap = withContext(Dispatchers.IO) { 57 | loadAvatarPlaceholderBitmapBlocking(data, avatar, avatarSize) 58 | } 59 | 60 | return customPlaceholderBitmap ?: loadAvatarPlaceholderBitmap(data, avatar, avatarSize) 61 | } 62 | 63 | /** 64 | * Creates a Bitmap to represent an avatar image. 65 | * 66 | * This method takes precedence over [loadAvatarBitmap] if both are implemented. 67 | * 68 | * Override this method only if you can't provide a suspending implementation, otherwise 69 | * override [loadAvatarBitmap] instead. 70 | * 71 | * @param data An image request model. 72 | * @param avatar An [Avatar] data model which includes request model and avatar styles. 73 | * @param avatarSize A specified avatar size. 74 | * 75 | * @return The loaded bitmap or null if the loading failed (e.g. network issues). 76 | */ 77 | public open fun loadAvatarBitmapBlocking( 78 | data: Any?, 79 | avatar: Avatar, 80 | @Px avatarSize: Int 81 | ): Bitmap? { 82 | return null 83 | } 84 | 85 | /** 86 | * Loads a Bitmap with the image request [data] to represent the avatar in a suspending operation. 87 | * 88 | * This method requests images by using the [ImageLoader] on an IO coroutines scope. 89 | * The [loadAvatarBitmapBlocking] method takes precedence over this one if both are implemented. 90 | * Prefer implementing this method if possible. 91 | * 92 | * @param data An image request model. 93 | * @param avatar An [Avatar] data model which includes request model and avatar styles. 94 | * @param avatarSize A specified avatar size. 95 | * 96 | * @return The loaded bitmap or null if the loading failed (e.g. network issues). 97 | */ 98 | public open suspend fun loadAvatarBitmap( 99 | data: Any?, 100 | avatar: Avatar, 101 | @Px avatarSize: Int 102 | ): Bitmap? { 103 | return AvatarImageLoaderInternal.loadAsBitmap( 104 | context = context, 105 | data = data, 106 | onSuccess = avatar.onSuccess, 107 | onError = avatar.onError 108 | ) 109 | } 110 | 111 | /** 112 | * Loads a Bitmap with the [avatar] to represent the placeholder of the avatar 113 | * in a suspending operation. This method will be executed if the previous image request failed. 114 | * 115 | * This method takes precedence over [loadAvatarPlaceholderBitmap] if both are implemented. 116 | * 117 | * Override this method only if you can't provide a suspending implementation, otherwise 118 | * override [loadAvatarPlaceholderBitmap] instead. 119 | * 120 | * @param data An image request model. 121 | * @param avatar An [Avatar] data model which includes request model and avatar styles. 122 | * @param avatarSize A specified avatar size. 123 | */ 124 | public open fun loadAvatarPlaceholderBitmapBlocking( 125 | data: Any?, 126 | avatar: Avatar, 127 | @Px avatarSize: Int 128 | ): Bitmap? { 129 | return null 130 | } 131 | 132 | /** 133 | * Loads a Bitmap with the [avatar] to represent the placeholder of the avatar 134 | * in a suspending operation. This method will be executed if the previous image request failed. 135 | * 136 | * The [loadAvatarPlaceholderBitmapBlocking] method takes precedence over this one if both are implemented. 137 | * Prefer implementing this method if possible. 138 | * 139 | * @param data An image request model. 140 | * @param avatar An [Avatar] data model which includes request model and avatar styles. 141 | * @param avatarSize A specified avatar size. 142 | */ 143 | public open suspend fun loadAvatarPlaceholderBitmap( 144 | data: Any?, 145 | avatar: Avatar, 146 | @Px avatarSize: Int 147 | ): Bitmap? { 148 | return null 149 | } 150 | 151 | /** 152 | * Creates a combined avatar Bitmap with the data model [avatar] and the specified [avatarSize] 153 | * to represent the avatar image, in a suspending operation. 154 | * 155 | * @param avatar An [Avatar] data model which includes request model and avatar styles. 156 | * @param avatarSize A specified avatar size. 157 | * 158 | * @return The combined bitmap or null if the loading failed (e.g. network issues). 159 | */ 160 | public open suspend fun createAvatarBitmaps( 161 | avatar: Avatar, 162 | @Px avatarSize: Int, 163 | ): Bitmap? { 164 | return avatar.data.take(avatar.maxSectionSize) 165 | .map { createAvatarBitmapInternal(it, avatar, avatarSize) } 166 | .takeIf { it.isNotEmpty() } 167 | ?.let { 168 | AvatarBitmapCombiner.combine( 169 | bitmaps = it, 170 | size = avatarSize, 171 | maxSectionSize = avatar.maxSectionSize, 172 | errorPlaceholder = avatar.errorPlaceholder 173 | ) 174 | } 175 | } 176 | 177 | /** Returns cache key for caching the avatar Bitmap image on memory. */ 178 | public open fun avatarBitmapKey(avatar: Avatar): String? = "${avatar.data}" 179 | } 180 | -------------------------------------------------------------------------------- /avatarview-coil/src/main/kotlin/io/getstream/avatarview/coil/AvatarCoil.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Stream.IO, Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.getstream.avatarview.coil 18 | 19 | import android.annotation.SuppressLint 20 | import android.content.Context 21 | import android.graphics.drawable.BitmapDrawable 22 | import coil.ImageLoader 23 | import coil.ImageLoaderFactory 24 | import io.getstream.avatarview.coil.AvatarCoil.imageLoader 25 | import io.getstream.avatarview.coil.AvatarCoil.setImageLoader 26 | 27 | /** 28 | * AvatarCoil provides a [ImageLoader], [AvatarBitmapFactory], and [ImageHeadersProvider] that can be 29 | * fully customized for loading avatar image: 30 | * 31 | * - [imageLoader] be used to load [Avatar] payload internally. You can customize with your 32 | * own [ImageLoaderFactory] or [AvatarImageLoaderFactory] by using [setImageLoader] function. 33 | * 34 | * - [AvatarBitmapFactory] will creates avatar bitmaps when [AvatarFetcher] fetches 35 | * the [Avatar] payload successfully. The loaded bitmaps will be operated by the factory and they will be 36 | * loaded as [BitmapDrawable] to the [io.getstream.avatarview.AvatarView]. 37 | * 38 | * - [ImageHeadersProvider] be used to provide image header. If you're using your own CDN, 39 | * you can set the [AvatarCoil.imageHeadersProvider] to load image data with your own header. 40 | */ 41 | public object AvatarCoil { 42 | 43 | private var imageLoader: ImageLoader? = null 44 | private var imageLoaderFactory: ImageLoaderFactory? = null 45 | 46 | /** 47 | * Sets a [ImageLoaderFactory] to provide your own [ImageLoader]. 48 | * 49 | * @param factory An [ImageLoader] factory. 50 | */ 51 | @Synchronized 52 | public fun setImageLoader(factory: ImageLoaderFactory) { 53 | imageLoaderFactory = factory 54 | imageLoader = null 55 | } 56 | 57 | /** Returns an [imageLoader] or [newImageLoader]. */ 58 | @PublishedApi 59 | internal fun imageLoader(context: Context): ImageLoader = imageLoader ?: newImageLoader(context) 60 | 61 | /** Returns an [imageLoader] or a new [imageLoader] from the [imageLoaderFactory]. */ 62 | @Synchronized 63 | private fun newImageLoader(context: Context): ImageLoader { 64 | imageLoader?.let { return it } 65 | 66 | val imageLoaderFactory = imageLoaderFactory ?: newImageLoaderFactory(context) 67 | return imageLoaderFactory.newImageLoader().apply { 68 | imageLoader = this 69 | } 70 | } 71 | 72 | /** Creates a new default instance of the [ImageLoaderFactory]. */ 73 | private fun newImageLoaderFactory(context: Context): ImageLoaderFactory { 74 | return AvatarImageLoaderFactory(context).apply { 75 | imageLoaderFactory = this 76 | } 77 | } 78 | 79 | /** Returns an [imageLoader] to load avatar images. */ 80 | @PublishedApi 81 | internal inline val Context.avatarImageLoader: ImageLoader 82 | get() = imageLoader(this) 83 | 84 | /** An avatar bitmap factory to create custom avatar bitmaps. */ 85 | @SuppressLint("StaticFieldLeak") 86 | private var avatarBitmapFactory: AvatarBitmapFactory? = null 87 | 88 | /** Returns an [AvatarBitmapFactory]. */ 89 | public fun getAvatarBitmapFactory(context: Context): AvatarBitmapFactory { 90 | return avatarBitmapFactory ?: synchronized(this) { 91 | avatarBitmapFactory ?: AvatarBitmapFactory(context.applicationContext).also { 92 | avatarBitmapFactory = it 93 | } 94 | } 95 | } 96 | 97 | /** Sets a custom [AvatarBitmapFactory]. */ 98 | @Synchronized 99 | public fun setAvatarBitmapFactory(avatarBitmapFactory: AvatarBitmapFactory?) { 100 | this.avatarBitmapFactory = avatarBitmapFactory 101 | } 102 | 103 | /** Provides a custom image header. */ 104 | public var imageHeadersProvider: ImageHeadersProvider = DefaultImageHeadersProvider 105 | } 106 | -------------------------------------------------------------------------------- /avatarview-coil/src/main/kotlin/io/getstream/avatarview/coil/AvatarFetcher.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Stream.IO, Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.getstream.avatarview.coil 18 | 19 | import android.content.Context 20 | import android.graphics.drawable.BitmapDrawable 21 | import coil.ImageLoader 22 | import coil.decode.DataSource 23 | import coil.fetch.DrawableResult 24 | import coil.fetch.Fetcher 25 | import coil.request.Options 26 | import coil.size.pxOrElse 27 | 28 | /** 29 | * An image request fetcher of [Avatar] data type. 30 | * 31 | * This fetcher will create a Bitmap using the [Avatar] data in the coroutines scope. 32 | */ 33 | internal class AvatarFetcher constructor( 34 | private val context: Context 35 | ) : Fetcher.Factory { 36 | 37 | override fun create(data: Avatar, options: Options, imageLoader: ImageLoader): Fetcher { 38 | val targetSize = options.size.width.pxOrElse { 0 } 39 | val resources = options.context.resources 40 | return Fetcher { 41 | DrawableResult( 42 | BitmapDrawable( 43 | resources, 44 | AvatarCoil.getAvatarBitmapFactory(context).createAvatarBitmaps( 45 | data, 46 | targetSize - data.avatarBorderWidth * 2 47 | ) 48 | ), 49 | false, 50 | DataSource.MEMORY 51 | ) 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /avatarview-coil/src/main/kotlin/io/getstream/avatarview/coil/AvatarImageLoaderFactory.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Stream.IO, Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.getstream.avatarview.coil 18 | 19 | import android.content.Context 20 | import android.os.Build 21 | import coil.ImageLoader 22 | import coil.ImageLoaderFactory 23 | import coil.decode.GifDecoder 24 | import coil.decode.ImageDecoderDecoder 25 | 26 | /** 27 | * An [ImageLoader] factory to provide an instance of the [ImageLoader]. 28 | * 29 | * AvatarImageLoaderFactory creates a default [ImageLoader] that has 30 | * caching strategy with OkHttp, image decoder (supports GIFs), and [AvatarFetcher]. 31 | */ 32 | public class AvatarImageLoaderFactory( 33 | private val context: Context, 34 | private val builder: ImageLoader.Builder.() -> Unit = {} 35 | ) : ImageLoaderFactory { 36 | 37 | /** Creates a new [ImageLoader] to load [Avatar] image data. */ 38 | override fun newImageLoader(): ImageLoader { 39 | return ImageLoader.Builder(context) 40 | .allowHardware(false) 41 | .crossfade(true) 42 | .components { 43 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { 44 | add(ImageDecoderDecoder.Factory()) 45 | } else { 46 | add(GifDecoder.Factory()) 47 | } 48 | add(AvatarFetcher(context)) 49 | } 50 | .apply(builder) 51 | .build() 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /avatarview-coil/src/main/kotlin/io/getstream/avatarview/coil/AvatarImageLoaderInternal.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Stream.IO, Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.getstream.avatarview.coil 18 | 19 | import android.content.Context 20 | import android.graphics.Bitmap 21 | import android.graphics.drawable.BitmapDrawable 22 | import coil.load 23 | import coil.request.ErrorResult 24 | import coil.request.ImageRequest 25 | import coil.request.SuccessResult 26 | import coil.transform.CircleCropTransformation 27 | import coil.transform.Transformation 28 | import io.getstream.avatarview.AvatarView 29 | import io.getstream.avatarview.coil.AvatarCoil.avatarImageLoader 30 | import io.getstream.avatarview.internal.InternalAvatarViewApi 31 | import kotlinx.coroutines.Dispatchers 32 | import kotlinx.coroutines.withContext 33 | import okhttp3.Headers.Companion.toHeaders 34 | 35 | /** An internal image loader to request image data with Coil. */ 36 | @InternalAvatarViewApi 37 | public object AvatarImageLoaderInternal { 38 | 39 | /** 40 | * Request an image [data] and loads it as a Bitmap in a suspending operation. 41 | * 42 | * If you're using your own CDN, you can set the [AvatarCoil.imageHeadersProvider] to load 43 | * image data. 44 | * 45 | * @param context A context to build [ImageRequest]. 46 | * @param data An image [data]. 47 | * @param onSuccess A lambda function will be executed when loading succeeds. 48 | * @param onError A lambda function will be executed when loading failed. 49 | * 50 | * @return The loaded bitmap or null if the loading failed (e.g. network issues). 51 | */ 52 | @JvmSynthetic 53 | public suspend fun loadAsBitmap( 54 | context: Context, 55 | data: Any?, 56 | onSuccess: (request: ImageRequest, result: SuccessResult) -> Unit = { _, _ -> }, 57 | onError: (request: ImageRequest, result: ErrorResult) -> Unit = { _, _ -> }, 58 | ): Bitmap? = withContext(Dispatchers.IO) { 59 | val imageResult = context.avatarImageLoader.execute( 60 | ImageRequest.Builder(context) 61 | .headers(AvatarCoil.imageHeadersProvider.getImageRequestHeaders().toHeaders()) 62 | .listener(onSuccess = onSuccess, onError = onError) 63 | .data(data) 64 | .build() 65 | ) 66 | (imageResult.drawable as? BitmapDrawable)?.bitmap 67 | } 68 | 69 | /** 70 | * Loads an image [data] to the [target] AvatarView with [transformation]. 71 | * 72 | * @param target A target [AvatarView] to load image data. 73 | * @param data An image data to be loaded. 74 | * @param transformation A [Transformation] to transform loaded images. 75 | * @param onStart A lambda function will be executed when start requesting. 76 | * @param onComplete A lambda function will be executed when finish loading. 77 | * @param builder A receiver to be applied with the [ImageRequest.Builder]. 78 | */ 79 | @JvmSynthetic 80 | @PublishedApi 81 | internal fun load( 82 | target: AvatarView, 83 | data: Any?, 84 | transformation: Transformation = CircleCropTransformation(), 85 | onStart: () -> Unit, 86 | onComplete: () -> Unit, 87 | builder: ImageRequest.Builder.() -> Unit 88 | ) { 89 | val context = target.context 90 | target.load(data, context.avatarImageLoader) { 91 | headers(AvatarCoil.imageHeadersProvider.getImageRequestHeaders().toHeaders()) 92 | transformations(transformation) 93 | listener( 94 | onStart = { onStart() }, 95 | onCancel = { onComplete() }, 96 | onError = { _, _ -> onComplete() }, 97 | onSuccess = { _, _ -> onComplete() }, 98 | ) 99 | apply(builder) 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /avatarview-coil/src/main/kotlin/io/getstream/avatarview/coil/AvatarViewExtension.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Stream.IO, Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | @file:JvmName("AvatarViewExtension") 18 | @file:JvmMultifileClass 19 | 20 | package io.getstream.avatarview.coil 21 | 22 | import coil.request.ErrorResult 23 | import coil.request.ImageRequest 24 | import coil.request.SuccessResult 25 | import coil.transform.CircleCropTransformation 26 | import coil.transform.RoundedCornersTransformation 27 | import coil.transform.Transformation 28 | import io.getstream.avatarview.AvatarShape 29 | import io.getstream.avatarview.AvatarView 30 | 31 | /** 32 | * Loads an image request [data] to the [AvatarView]. 33 | * 34 | * @param data An image data to be loaded. 35 | * @param onStart A lambda function will be executed when start requesting. 36 | * @param onComplete A lambda function will be executed when loading finished. 37 | * @param onSuccess A lambda function will be executed when loading succeeds. 38 | * @param onError A lambda function will be executed when loading failed. 39 | * @param builder A receiver to be applied with the [ImageRequest.Builder]. 40 | */ 41 | @JvmSynthetic 42 | public fun AvatarView.loadImage( 43 | data: Any?, 44 | onStart: () -> Unit = {}, 45 | onComplete: () -> Unit = {}, 46 | onSuccess: (request: ImageRequest, result: SuccessResult) -> Unit = { _, _ -> }, 47 | onError: (request: ImageRequest, result: ErrorResult) -> Unit = { _, _ -> }, 48 | builder: ImageRequest.Builder.() -> Unit = {} 49 | ) { 50 | loadPlaceholder() 51 | AvatarImageLoaderInternal.load( 52 | target = this, 53 | data = Avatar( 54 | data = listOf(data), 55 | maxSectionSize = maxSectionSize, 56 | avatarBorderWidth = avatarBorderWidth, 57 | errorPlaceholder = errorPlaceholder, 58 | onSuccess = onSuccess, 59 | onError = onError 60 | ), 61 | transformation = transformation, 62 | onStart = onStart, 63 | onComplete = onComplete, 64 | builder = builder, 65 | ) 66 | } 67 | 68 | /** 69 | * Loads a list of image request [data] to the [AvatarView]. 70 | * Up to 4 images will be combined and loaded. 71 | * 72 | * @param data A list of image data to be loaded. 73 | * @param onStart A lambda function will be executed when start requesting. 74 | * @param onComplete A lambda function will be executed when finish loading. 75 | * @param onSuccess A lambda function will be executed when loading succeeds. 76 | * @param onError A lambda function will be executed when loading failed. 77 | * @param builder A receiver to be applied with the [ImageRequest.Builder]. 78 | */ 79 | @JvmSynthetic 80 | public fun AvatarView.loadImage( 81 | data: List, 82 | onStart: () -> Unit = {}, 83 | onComplete: () -> Unit = {}, 84 | onSuccess: (request: ImageRequest, result: SuccessResult) -> Unit = { _, _ -> }, 85 | onError: (request: ImageRequest, result: ErrorResult) -> Unit = { _, _ -> }, 86 | builder: ImageRequest.Builder.() -> Unit = {} 87 | ) { 88 | loadPlaceholder() 89 | AvatarImageLoaderInternal.load( 90 | target = this, 91 | data = Avatar( 92 | data = data, 93 | maxSectionSize = maxSectionSize, 94 | avatarBorderWidth = avatarBorderWidth, 95 | errorPlaceholder = errorPlaceholder, 96 | onSuccess = onSuccess, 97 | onError = onError 98 | ), 99 | transformation = transformation, 100 | onStart = onStart, 101 | onComplete = onComplete, 102 | builder = builder, 103 | ) 104 | } 105 | 106 | /** 107 | * Loads a vararg of image request [data] to the [AvatarView]. 108 | * Up to 4 images will be combined and loaded. 109 | * 110 | * @param data A vararg of image data to be loaded. 111 | * @param onStart A lambda function will be executed when start requesting. 112 | * @param onComplete A lambda function will be executed when finish loading. 113 | * @param onSuccess A lambda function will be executed when loading succeeds. 114 | * @param onError A lambda function will be executed when loading failed. 115 | * @param builder A receiver to be applied with the [ImageRequest.Builder]. 116 | */ 117 | @JvmSynthetic 118 | public fun AvatarView.loadImage( 119 | vararg data: Any?, 120 | onStart: () -> Unit = {}, 121 | onComplete: () -> Unit = {}, 122 | onSuccess: (request: ImageRequest, result: SuccessResult) -> Unit = { _, _ -> }, 123 | onError: (request: ImageRequest, result: ErrorResult) -> Unit = { _, _ -> }, 124 | builder: ImageRequest.Builder.() -> Unit = {} 125 | ) { 126 | loadPlaceholder() 127 | loadImage( 128 | data = data.toList(), 129 | onStart = onStart, 130 | onComplete = onComplete, 131 | onSuccess = onSuccess, 132 | onError = onError, 133 | builder = builder 134 | ) 135 | } 136 | 137 | /** Loads a placeholder with the [AvatarView.placeholder] property. */ 138 | @PublishedApi 139 | internal fun AvatarView.loadPlaceholder() { 140 | placeholder?.let { 141 | AvatarImageLoaderInternal.load( 142 | target = this, 143 | data = it, 144 | transformation = transformation, 145 | onStart = {}, 146 | onComplete = {}, 147 | builder = {} 148 | ) 149 | } 150 | } 151 | 152 | /** Returns a [Transformation] from a [AvatarView]. */ 153 | @PublishedApi 154 | internal val AvatarView.transformation: Transformation 155 | @JvmSynthetic inline get() = when (avatarShape) { 156 | AvatarShape.CIRCLE -> CircleCropTransformation() 157 | AvatarShape.ROUNDED_RECT -> RoundedCornersTransformation(avatarBorderRadius) 158 | } 159 | -------------------------------------------------------------------------------- /avatarview-coil/src/main/kotlin/io/getstream/avatarview/coil/ImageHeadersProvider.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Stream.IO, Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.getstream.avatarview.coil 18 | 19 | /** Provides HTTP headers for image loading requests. */ 20 | public interface ImageHeadersProvider { 21 | public fun getImageRequestHeaders(): Map 22 | } 23 | 24 | /** Provides a default HTTP headers for image loading requests. */ 25 | internal object DefaultImageHeadersProvider : ImageHeadersProvider { 26 | override fun getImageRequestHeaders(): Map = emptyMap() 27 | } 28 | -------------------------------------------------------------------------------- /avatarview-glide/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /avatarview-glide/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 |

AvatarView-Glide


4 | 5 |

6 | AvatarView supports loading profile images with fractional style, borders, indicators, and initials for Android. 7 |


8 | 9 | 10 |

11 | License 12 | API 13 | Build Status 14 | Android Weekly 15 |


16 | 17 | 18 |

19 | 20 | 21 | 22 |

23 | 24 | ## Download 25 | [![Maven Central](https://img.shields.io/maven-central/v/io.getstream/avatarview-glide.svg?label=Maven%20Central)](https://search.maven.org/search?q=g:%22io.getstream%22%20AND%20a:%22stream-chat-android%22) 26 | 27 | ### Gradle 28 | Add the below codes to your **root** `build.gradle` file (not your module build.gradle file). 29 | ```gradle 30 | allprojects { 31 | repositories { 32 | mavenCentral() 33 | } 34 | } 35 | ``` 36 | Next, add the below dependency to your **module**'s `build.gradle` file. 37 | ```gradle 38 | dependencies { 39 | implementation "io.getstream:avatarview-glide:$version_avatarview" 40 | } 41 | ``` 42 | 43 | The `io.getstream.avatarview-glide` dependency includes [Glide](https://github.com/bumptech/glide) to load images internally. So if you're using Glide in your project, please make sure your project is using the same Glide version or exclude Glide dependencies to adapt yours. 44 | 45 | ## Usage 46 | 47 | First, add the following XML namespace inside your XML layout file. 48 | 49 | ```gradle 50 | xmlns:app="http://schemas.android.com/apk/res-auto" 51 | ``` 52 | 53 | ### **AvatarView** in XML layout 54 | 55 | You can customize `AvatarView` in your XML layout by setting attributes. 56 | 57 | ```gradle 58 | 71 | ``` 72 | ### Loading Single Image 73 | 74 | You can load an image on your `AvatarView` by using the `loadImage` method as in the example below: 75 | 76 | ```kotlin 77 | avatarView.loadImage(data) 78 | ``` 79 | 80 | The default supported **data** types are `String`, `Uri`, `HttpUrl` , `File`, `DrawableRes`, `Drawable`, and `Bitmap`.
81 | You can also set a place holder and request listeners as in the example below: 82 | 83 | ```kotlin 84 | avatarView.loadImage( 85 | data = data, 86 | placeholder = drawable, 87 | crossFadeEnabled = true, 88 | requestListener = myRequestListener, 89 | requestOptions = myRequestOptions 90 | ) 91 | ``` 92 | 93 | 94 | 95 | ### Loading Images with Fractional Style 96 | 97 | `AvatarView` supports loading up to four images with the fractional style as in the example below: 98 | 99 | ```kotlin 100 | avatarView.loadImage( 101 | data = listof(url1, url2, url3, url4) 102 | ) 103 | ``` 104 | 105 | We can set the maximum section size of the avatar when we load multiple images by using the `avatarViewMaxSectionSize` attribute as in the exmample below: 106 | 107 | ```xml 108 | app:avatarViewMaxSectionSize="4" 109 | ``` 110 | 111 | The default value is 4, and you can set the fractional styles to your taste. 112 | 113 | 114 | 115 | ### PlaceHolder 116 | 117 | We can set a placeholder to show a placeholder when loading an image as in the example below: 118 | 119 | ```xml 120 | app:avatarViewPlaceholder="@drawable/stream" 121 | ``` 122 | Or we can set a drawable manually on the `AvatarView`. 123 | 124 | ```kotlin 125 | avatarView.placeholder = drawable 126 | ``` 127 | 128 | 129 | 130 | ### ErrorPlaceHolder 131 | 132 | We can set an error placeholder to show a placeholder when the request failed as in the example below: 133 | 134 | ```xml 135 | app:avatarViewErrorPlaceholder="@drawable/stream" 136 | ``` 137 | Or we can set a drawable manually on the `AvatarView`. 138 | 139 | ```kotlin 140 | avatarView.errorPlaceholder = drawable 141 | ``` 142 | 143 | ### Custom RequestBuilder 144 | 145 | You can customize the [RequestBuilder](https://bumptech.github.io/glide/doc/options.html#requestbuilder/) and provide additional information to load an image as in the example below: 146 | 147 | ```kotlin 148 | avatarView.loadImage( 149 | data = cats, 150 | requestBuilder = Glide.with(avatarView4) 151 | .asDrawable() 152 | .override(120, 120) 153 | .centerCrop() 154 | .addListener(object : RequestListener { 155 | override fun onLoadFailed( 156 | e: GlideException?, 157 | model: Any?, 158 | target: Target?, 159 | isFirstResource: Boolean 160 | ): Boolean { 161 | // do something // 162 | return true 163 | } 164 | 165 | override fun onResourceReady( 166 | resource: Drawable?, 167 | model: Any?, 168 | target: Target?, 169 | dataSource: DataSource?, 170 | isFirstResource: Boolean 171 | ): Boolean { 172 | // do something // 173 | return false 174 | } 175 | }) 176 | ) 177 | ``` 178 | 179 | ```xml 180 | Copyright 2021 Stream.IO, Inc. All Rights Reserved. 181 | 182 | Licensed under the Apache License, Version 2.0 (the "License"); 183 | you may not use this file except in compliance with the License. 184 | You may obtain a copy of the License at 185 | 186 | http://www.apache.org/licenses/LICENSE-2.0 187 | 188 | Unless required by applicable law or agreed to in writing, software 189 | distributed under the License is distributed on an "AS IS" BASIS, 190 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 191 | See the License for the specific language governing permissions and 192 | limitations under the License. 193 | ``` 194 | -------------------------------------------------------------------------------- /avatarview-glide/api/avatarview-glide.api: -------------------------------------------------------------------------------- 1 | public final class io/getstream/avatarview/glide/AvatarResult$Failure : io/getstream/avatarview/glide/AvatarResult { 2 | public fun (Landroid/graphics/drawable/Drawable;)V 3 | public final fun getPlaceholder ()Landroid/graphics/drawable/Drawable; 4 | } 5 | 6 | public final class io/getstream/avatarview/glide/AvatarResult$Success : io/getstream/avatarview/glide/AvatarResult { 7 | public fun (Landroid/graphics/Bitmap;)V 8 | public final fun getBitmap ()Landroid/graphics/Bitmap; 9 | } 10 | 11 | public final class io/getstream/avatarview/glide/AvatarViewExtensionKt { 12 | public static final synthetic fun loadImage (Lio/getstream/avatarview/AvatarView;Ljava/lang/Object;Lcom/bumptech/glide/RequestBuilder;)V 13 | public static final synthetic fun loadImage (Lio/getstream/avatarview/AvatarView;Ljava/lang/Object;ZLcom/bumptech/glide/request/RequestListener;Lkotlin/jvm/functions/Function0;)V 14 | public static final fun loadImage (Lio/getstream/avatarview/AvatarView;Ljava/util/List;Lcom/bumptech/glide/RequestBuilder;)V 15 | public static final fun loadImage (Lio/getstream/avatarview/AvatarView;Ljava/util/List;ZLcom/bumptech/glide/request/RequestListener;Lkotlin/jvm/functions/Function0;)V 16 | public static synthetic fun loadImage$default (Lio/getstream/avatarview/AvatarView;Ljava/lang/Object;ZLcom/bumptech/glide/request/RequestListener;Lkotlin/jvm/functions/Function0;ILjava/lang/Object;)V 17 | public static synthetic fun loadImage$default (Lio/getstream/avatarview/AvatarView;Ljava/util/List;ZLcom/bumptech/glide/request/RequestListener;Lkotlin/jvm/functions/Function0;ILjava/lang/Object;)V 18 | } 19 | 20 | -------------------------------------------------------------------------------- /avatarview-glide/build.gradle: -------------------------------------------------------------------------------- 1 | import io.getstream.avatarview.Configuration 2 | import io.getstream.avatarview.Dependencies 3 | 4 | plugins { 5 | id 'com.android.library' 6 | id 'org.jetbrains.kotlin.android' 7 | id 'org.jetbrains.dokka' 8 | id 'binary-compatibility-validator' 9 | id 'kotlin-kapt' 10 | } 11 | 12 | ext { 13 | PUBLISH_GROUP_ID = Configuration.artifactGroup 14 | if (snapshot) { 15 | PUBLISH_VERSION = Configuration.snapshotVersionName 16 | } else { 17 | PUBLISH_VERSION = Configuration.versionName 18 | } 19 | PUBLISH_ARTIFACT_ID = 'avatarview-glide' 20 | } 21 | 22 | apply from: "${rootDir}/scripts/publish-module.gradle" 23 | 24 | android { 25 | compileSdkVersion Configuration.compileSdk 26 | defaultConfig { 27 | minSdkVersion Configuration.minSdk 28 | targetSdkVersion Configuration.targetSdk 29 | } 30 | 31 | compileOptions { 32 | sourceCompatibility JavaVersion.VERSION_1_8 33 | targetCompatibility JavaVersion.VERSION_1_8 34 | } 35 | 36 | kotlinOptions { 37 | jvmTarget = '1.8' 38 | } 39 | 40 | lintOptions { 41 | abortOnError false 42 | } 43 | } 44 | 45 | tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { 46 | kotlinOptions.freeCompilerArgs += [ 47 | "-Xexplicit-api=strict", 48 | "-Xopt-in=kotlinx.coroutines.ExperimentalCoroutinesApi", 49 | "-Xopt-in=io.getstream.avatarview.internal.InternalAvatarViewApi", 50 | ] 51 | } 52 | 53 | apiValidation { 54 | nonPublicMarkers += ["kotlin.PublishedApi"] 55 | } 56 | 57 | dependencies { 58 | api project(":avatarview") 59 | api Dependencies.glide 60 | 61 | implementation Dependencies.androidxAppcompat 62 | implementation Dependencies.coroutines 63 | } 64 | 65 | -------------------------------------------------------------------------------- /avatarview-glide/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | -------------------------------------------------------------------------------- /avatarview-glide/src/main/kotlin/io/getstream/avatarview/glide/AvatarBitmapLoader.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Stream.IO, Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.getstream.avatarview.glide 18 | 19 | import android.graphics.Bitmap 20 | import android.graphics.drawable.Drawable 21 | import com.bumptech.glide.RequestManager 22 | import com.bumptech.glide.request.target.CustomTarget 23 | import com.bumptech.glide.request.transition.Transition 24 | import io.getstream.avatarview.AvatarBitmapCombiner 25 | import kotlinx.coroutines.Dispatchers 26 | import kotlinx.coroutines.channels.awaitClose 27 | import kotlinx.coroutines.flow.Flow 28 | import kotlinx.coroutines.flow.callbackFlow 29 | import kotlinx.coroutines.withContext 30 | 31 | /** 32 | * A Bitmap loader to emit loaded avatar bitmaps. 33 | */ 34 | @PublishedApi 35 | internal object AvatarBitmapLoader { 36 | 37 | /** 38 | * Load Bitmaps and emit them on callback flow. 39 | * 40 | * @param requestManager A class for managing and starting requests for Glide. 41 | * @param data A list of model to load images. 42 | * @param errorPlaceholder An error placeholder that should be shown when request failed. 43 | * 44 | * @return A flow of [AvatarResult]. 45 | */ 46 | @PublishedApi 47 | internal fun loadBitmaps( 48 | requestManager: RequestManager, 49 | data: List, 50 | errorPlaceholder: Drawable? = null 51 | ): Flow = callbackFlow { 52 | 53 | var avatarViewTarget: CustomTarget? = null 54 | 55 | withContext(Dispatchers.IO) { 56 | data.forEach { endPoint -> 57 | avatarViewTarget = object : CustomTarget() { 58 | override fun onResourceReady( 59 | resource: Bitmap, 60 | transition: Transition? 61 | ) { 62 | trySend(AvatarResult.Success(resource)) 63 | } 64 | 65 | override fun onLoadFailed(errorDrawable: Drawable?) { 66 | super.onLoadFailed(errorDrawable) 67 | 68 | val error = errorPlaceholder ?: errorDrawable ?: let { 69 | trySend(AvatarResult.Failure(null)) 70 | return 71 | } 72 | 73 | AvatarBitmapCombiner.drawableToBitmap(error, error.intrinsicWidth)?.let { 74 | trySend(AvatarResult.Success(it)) 75 | } 76 | } 77 | 78 | override fun onLoadCleared(placeholder: Drawable?) = Unit 79 | }.also { target -> 80 | requestManager 81 | .asBitmap() 82 | .load(endPoint) 83 | .into(target) 84 | } 85 | } 86 | } 87 | 88 | awaitClose { 89 | requestManager.clear(avatarViewTarget) 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /avatarview-glide/src/main/kotlin/io/getstream/avatarview/glide/AvatarResult.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Stream.IO, Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.getstream.avatarview.glide 18 | 19 | import android.graphics.Bitmap 20 | import android.graphics.drawable.Drawable 21 | import com.bumptech.glide.request.target.CustomTarget 22 | 23 | /** 24 | * A result class that encapsulates outcome with a Bitmap from the target request by a [CustomTarget]. 25 | */ 26 | @PublishedApi 27 | internal sealed class AvatarResult { 28 | 29 | /** A result class that encapsulates the successful result using Bitmap data. */ 30 | class Success(val bitmap: Bitmap) : AvatarResult() 31 | 32 | /** A result class that encapsulates the failure result using Drawable data. */ 33 | class Failure(val placeholder: Drawable?) : AvatarResult() 34 | } 35 | -------------------------------------------------------------------------------- /avatarview-glide/src/main/kotlin/io/getstream/avatarview/glide/AvatarViewExtension.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Stream.IO, Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.getstream.avatarview.glide 18 | 19 | import android.graphics.Bitmap 20 | import android.graphics.drawable.Drawable 21 | import com.bumptech.glide.Glide 22 | import com.bumptech.glide.RequestBuilder 23 | import com.bumptech.glide.load.MultiTransformation 24 | import com.bumptech.glide.load.engine.DiskCacheStrategy 25 | import com.bumptech.glide.load.resource.bitmap.CenterCrop 26 | import com.bumptech.glide.load.resource.bitmap.CircleCrop 27 | import com.bumptech.glide.load.resource.bitmap.RoundedCorners 28 | import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withCrossFade 29 | import com.bumptech.glide.request.RequestListener 30 | import com.bumptech.glide.request.RequestOptions 31 | import com.bumptech.glide.request.transition.DrawableCrossFadeFactory 32 | import io.getstream.avatarview.AvatarBitmapCombiner 33 | import io.getstream.avatarview.AvatarShape 34 | import io.getstream.avatarview.AvatarView 35 | import kotlinx.coroutines.cancel 36 | import kotlinx.coroutines.flow.collect 37 | import kotlinx.coroutines.launch 38 | 39 | /** 40 | * Loads an image request [data] to the [AvatarView]. 41 | * 42 | * @param data An image data to be loaded. 43 | * @param crossFadeEnabled Enables crossFade animation when load an image. 44 | * @param requestListener A class for monitoring the status of a request while images load. 45 | * @param requestOptions A receiver to be applied with the [RequestOptions]. 46 | */ 47 | @JvmSynthetic 48 | public inline fun AvatarView.loadImage( 49 | data: Any?, 50 | crossFadeEnabled: Boolean = true, 51 | requestListener: RequestListener? = null, 52 | requestOptions: () -> RequestOptions = { RequestOptions() } 53 | ) { 54 | loadPlaceHolder() 55 | val factory = DrawableCrossFadeFactory.Builder().setCrossFadeEnabled(crossFadeEnabled).build() 56 | Glide.with(this) 57 | .load(data) 58 | .error(errorPlaceholder) 59 | .apply(requestOptions()) 60 | .listener(requestListener) 61 | .transition(withCrossFade(factory)) 62 | .transform(transformation) 63 | .into(this) 64 | } 65 | 66 | /** 67 | * Loads an image request [data] to the [AvatarView]. 68 | * 69 | * @param data An image data to be loaded. 70 | * @param requestBuilder A generic class that can handle setting options and staring loads for generic resource types. 71 | */ 72 | @JvmSynthetic 73 | public fun AvatarView.loadImage( 74 | data: Any?, 75 | requestBuilder: RequestBuilder<*> 76 | ) { 77 | loadPlaceHolder() 78 | requestBuilder 79 | .load(data) 80 | .transform(transformation) 81 | .into(this) 82 | } 83 | 84 | /** 85 | * Loads a list of image request [data] to the [AvatarView]. 86 | * Up to 4 images will be combined and loaded. 87 | * 88 | * @param data A list of image data to be loaded. 89 | * @param requestListener A class for monitoring the status of a request while images load. 90 | * @param requestOptions A receiver to be applied with the [RequestOptions]. 91 | */ 92 | public fun AvatarView.loadImage( 93 | data: List, 94 | crossFadeEnabled: Boolean = true, 95 | requestListener: RequestListener? = null, 96 | requestOptions: () -> RequestOptions = { RequestOptions() } 97 | ) { 98 | loadPlaceHolder() 99 | collectAndCombineBitmaps(data) { sectionedBitmap -> 100 | this@loadImage.loadImage( 101 | data = sectionedBitmap, 102 | crossFadeEnabled = crossFadeEnabled, 103 | requestListener = requestListener, 104 | requestOptions = { requestOptions().diskCacheStrategy(DiskCacheStrategy.NONE) } 105 | ) 106 | } 107 | } 108 | 109 | /** 110 | * Loads a list of image request [data] to the [AvatarView]. 111 | * Up to 4 images will be combined and loaded. 112 | * 113 | * @param data A list of image data to be loaded. 114 | * @param requestBuilder A generic class that can handle setting options and staring loads for generic resource types. 115 | */ 116 | public fun AvatarView.loadImage( 117 | data: List, 118 | requestBuilder: RequestBuilder<*> 119 | ) { 120 | loadPlaceHolder() 121 | collectAndCombineBitmaps(data) { sectionedBitmap -> 122 | this@loadImage.loadImage( 123 | data = sectionedBitmap, 124 | requestBuilder = requestBuilder.diskCacheStrategy(DiskCacheStrategy.NONE) 125 | ) 126 | } 127 | } 128 | 129 | /** 130 | * Collects multiple Bitmap and combines them as one sectioned image. 131 | * 132 | * @param data A list of image data to be loaded. 133 | * @param onLoadImage An executable lambda to load a sectioned image. 134 | */ 135 | @PublishedApi 136 | @JvmSynthetic 137 | internal inline fun AvatarView.collectAndCombineBitmaps( 138 | data: List, 139 | crossinline onLoadImage: AvatarView.(Bitmap) -> Unit 140 | ) { 141 | launch(coroutineContext) { 142 | val avatarResults: ArrayList = arrayListOf() 143 | val avatarResultFlow = AvatarBitmapLoader.loadBitmaps( 144 | requestManager = Glide.with(this@collectAndCombineBitmaps), 145 | data = data, 146 | errorPlaceholder = errorPlaceholder 147 | ) 148 | 149 | avatarResultFlow.collect { 150 | avatarResults.add(it) 151 | 152 | if (avatarResults.size == data.size.coerceAtMost(maxSectionSize)) { 153 | val bitmaps: List = 154 | avatarResults.filterIsInstance() 155 | .map { success -> success.bitmap } 156 | 157 | if (bitmaps.isNotEmpty()) { 158 | val sectionedBitmap = 159 | AvatarBitmapCombiner.combine( 160 | bitmaps = bitmaps, 161 | size = bitmaps.last().width - avatarBorderWidth * 2, 162 | maxSectionSize = maxSectionSize, 163 | errorPlaceholder = errorPlaceholder 164 | ) 165 | 166 | onLoadImage(sectionedBitmap) 167 | } 168 | cancel() 169 | } 170 | } 171 | } 172 | } 173 | 174 | /** Loads a placeholder with the [AvatarView.placeholder] property. */ 175 | @PublishedApi 176 | internal fun AvatarView.loadPlaceHolder() { 177 | placeholder?.let { 178 | Glide.with(context) 179 | .load(it) 180 | .transform(transformation) 181 | .into(this) 182 | } 183 | } 184 | 185 | /** Returns a [MultiTransformation] from a [AvatarView]. */ 186 | @PublishedApi 187 | internal val AvatarView.transformation: MultiTransformation 188 | @JvmSynthetic inline get() = when (avatarShape) { 189 | AvatarShape.CIRCLE -> MultiTransformation(CircleCrop()) 190 | AvatarShape.ROUNDED_RECT -> MultiTransformation( 191 | CenterCrop(), 192 | RoundedCorners(avatarBorderRadius.toInt()) 193 | ) 194 | } 195 | -------------------------------------------------------------------------------- /avatarview-stream-integration/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /avatarview-stream-integration/api/avatarview-stream-integration.api: -------------------------------------------------------------------------------- 1 | public final class io/getstream/avatarview/stream/integration/AvatarViewStreamIntegration { 2 | public static final fun setChannelData (Lio/getstream/avatarview/AvatarView;Lio/getstream/chat/android/client/models/Channel;Landroid/graphics/drawable/Drawable;)V 3 | public static synthetic fun setChannelData$default (Lio/getstream/avatarview/AvatarView;Lio/getstream/chat/android/client/models/Channel;Landroid/graphics/drawable/Drawable;ILjava/lang/Object;)V 4 | public static final fun setUserData (Lio/getstream/avatarview/AvatarView;Lio/getstream/chat/android/client/models/User;Landroid/graphics/drawable/Drawable;)V 5 | public static synthetic fun setUserData$default (Lio/getstream/avatarview/AvatarView;Lio/getstream/chat/android/client/models/User;Landroid/graphics/drawable/Drawable;ILjava/lang/Object;)V 6 | } 7 | 8 | public final class io/getstream/avatarview/stream/integration/StreamAvatarBitmapFactory : io/getstream/avatarview/coil/AvatarBitmapFactory { 9 | public fun (Landroid/content/Context;)V 10 | public fun loadAvatarPlaceholderBitmap (Ljava/lang/Object;Lio/getstream/avatarview/coil/Avatar;ILkotlin/coroutines/Continuation;)Ljava/lang/Object; 11 | } 12 | 13 | -------------------------------------------------------------------------------- /avatarview-stream-integration/build.gradle: -------------------------------------------------------------------------------- 1 | import io.getstream.avatarview.Configuration 2 | import io.getstream.avatarview.Dependencies 3 | 4 | plugins { 5 | id 'com.android.library' 6 | id 'org.jetbrains.kotlin.android' 7 | id 'org.jetbrains.dokka' 8 | id 'binary-compatibility-validator' 9 | } 10 | 11 | ext { 12 | PUBLISH_GROUP_ID = Configuration.artifactGroup 13 | if (snapshot) { 14 | PUBLISH_VERSION = Configuration.snapshotVersionName 15 | } else { 16 | PUBLISH_VERSION = Configuration.versionName 17 | } 18 | PUBLISH_ARTIFACT_ID = 'avatarview-stream-integration' 19 | } 20 | 21 | apply from: "${rootDir}/scripts/publish-module.gradle" 22 | 23 | android { 24 | compileSdkVersion Configuration.compileSdk 25 | defaultConfig { 26 | minSdkVersion Configuration.minSdk 27 | targetSdkVersion Configuration.targetSdk 28 | } 29 | 30 | resourcePrefix 'avatarview' 31 | 32 | buildFeatures { 33 | buildConfig false 34 | } 35 | 36 | compileOptions { 37 | sourceCompatibility JavaVersion.VERSION_1_8 38 | targetCompatibility JavaVersion.VERSION_1_8 39 | } 40 | 41 | kotlinOptions { 42 | jvmTarget = '1.8' 43 | } 44 | 45 | lintOptions { 46 | abortOnError false 47 | } 48 | } 49 | 50 | tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { 51 | kotlinOptions.freeCompilerArgs += [ 52 | "-Xexplicit-api=strict", 53 | "-Xopt-in=io.getstream.avatarview.internal.InternalAvatarViewApi", 54 | ] 55 | } 56 | 57 | apiValidation { 58 | nonPublicMarkers += ["kotlin.PublishedApi"] 59 | } 60 | 61 | dependencies { 62 | api project(":avatarview-coil") 63 | api Dependencies.streamChatSdk 64 | 65 | implementation Dependencies.androidxAppcompat 66 | } 67 | 68 | -------------------------------------------------------------------------------- /avatarview-stream-integration/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | -------------------------------------------------------------------------------- /avatarview-stream-integration/src/main/kotlin/io/getstream/avatarview/stream/integration/AvatarViewStreamIntegration.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Stream.IO, Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | @file:JvmName("AvatarViewStreamIntegration") 18 | @file:JvmMultifileClass 19 | 20 | package io.getstream.avatarview.stream.integration 21 | 22 | import android.graphics.drawable.Drawable 23 | import io.getstream.avatarview.AvatarView 24 | import io.getstream.avatarview.coil.Avatar 25 | import io.getstream.avatarview.coil.AvatarImageLoaderInternal 26 | import io.getstream.avatarview.coil.loadImage 27 | import io.getstream.avatarview.stream.integration.StreamAvatarBitmapFactory.Companion.BAG_AVATAR_INITIALS 28 | import io.getstream.avatarview.stream.integration.StreamAvatarBitmapFactory.Companion.BAG_AVATAR_INITIALS_FONT 29 | import io.getstream.avatarview.stream.integration.StreamAvatarBitmapFactory.Companion.BAG_AVATAR_INITIALS_TEXT_COLOR 30 | import io.getstream.avatarview.stream.integration.StreamAvatarBitmapFactory.Companion.BAG_AVATAR_INITIALS_TEXT_SIZE 31 | import io.getstream.chat.android.client.ChatClient 32 | import io.getstream.chat.android.client.extensions.isAnonymousChannel 33 | import io.getstream.chat.android.client.models.Channel 34 | import io.getstream.chat.android.client.models.User 35 | import io.getstream.chat.android.client.models.initials 36 | import kotlinx.coroutines.launch 37 | 38 | /** 39 | * Sets [User] to the [AvatarView] for loading image and online status. 40 | * 41 | * @param user The [User] model of the Stream SDK. 42 | * @param errorPlaceholder An error placeholder that should be shown when request failed. 43 | */ 44 | public fun AvatarView.setUserData( 45 | user: User, 46 | errorPlaceholder: Drawable? = null 47 | ) { 48 | loadImage( 49 | data = Avatar( 50 | data = listOf(user.model), 51 | maxSectionSize = maxSectionSize, 52 | avatarBorderWidth = avatarBorderWidth, 53 | errorPlaceholder = errorPlaceholder, 54 | onSuccess = { _, _ -> }, 55 | onError = { _, _ -> }, 56 | ).also { 57 | it.putInitialStylesOnBag(this, user) 58 | } 59 | ) 60 | 61 | indicatorEnabled = user.online 62 | } 63 | 64 | /** 65 | * Sets [Channel] to the [AvatarView] for loading images. 66 | * 67 | * @param channel A [Channel] model of the Stream SDK. 68 | * @param errorPlaceholder An error placeholder that should be shown when request failed. 69 | */ 70 | public fun AvatarView.setChannelData(channel: Channel, errorPlaceholder: Drawable? = null) { 71 | val otherUsers = channel.getUsersExcludingCurrent() 72 | if (channel.isAnonymousChannel() && otherUsers.size == 1) { 73 | setUserData(otherUsers.first()) 74 | } else { 75 | launch { 76 | AvatarImageLoaderInternal.loadAsBitmap(context, channel.image)?.also { 77 | loadImage(it) 78 | } ?: loadImage( 79 | data = Avatar( 80 | data = otherUsers.map { it.model }, 81 | maxSectionSize = maxSectionSize, 82 | avatarBorderWidth = avatarBorderWidth, 83 | errorPlaceholder = errorPlaceholder, 84 | onSuccess = { _, _ -> }, 85 | onError = { _, _ -> }, 86 | ).also { 87 | it.putInitialStylesOnBag(this@setChannelData, channel) 88 | } 89 | ) 90 | } 91 | indicatorEnabled = false 92 | } 93 | } 94 | 95 | /** Returns a request model data from a [User]. */ 96 | private val User.model: String 97 | get() = image.takeIf { it.isNotEmpty() } ?: name 98 | 99 | /** 100 | * Puts initials style information associated with [AvatarView] on the bag with the [User]. 101 | * 102 | * @param avatarView The target that will be loaded an avatar image. 103 | * @param user The [User] model of the Stream SDK. 104 | */ 105 | internal fun Avatar.putInitialStylesOnBag(avatarView: AvatarView, user: User) { 106 | avatarView.run { 107 | setTagIfAbsent( 108 | user.model, 109 | mutableMapOf( 110 | BAG_AVATAR_INITIALS to user.initials, 111 | BAG_AVATAR_INITIALS_FONT to avatarInitialsStyle, 112 | BAG_AVATAR_INITIALS_TEXT_COLOR to avatarInitialsTextColor, 113 | BAG_AVATAR_INITIALS_TEXT_SIZE to avatarInitialsTextSize.toFloat() 114 | ) 115 | ) 116 | } 117 | } 118 | 119 | /** 120 | * Puts initials style information associated with [AvatarView] on the bag with the [Channel]. 121 | * 122 | * @param avatarView The target that will be loaded an avatar image. 123 | * @param channel The [Channel] model of the Stream SDK. 124 | */ 125 | internal fun Avatar.putInitialStylesOnBag(avatarView: AvatarView, channel: Channel) { 126 | channel.getUsersExcludingCurrent().forEach { user -> 127 | putInitialStylesOnBag(avatarView, user) 128 | } 129 | } 130 | 131 | /** Returns list of [User] excepts the current user. */ 132 | @PublishedApi 133 | @JvmSynthetic 134 | internal fun Channel.getUsersExcludingCurrent(): List { 135 | val users = members.map { it.user } 136 | val currentUserId = ChatClient.instance().getCurrentUser()?.id 137 | return if (currentUserId != null) { 138 | users.filterNot { it.id == currentUserId } 139 | } else { 140 | users 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /avatarview-stream-integration/src/main/kotlin/io/getstream/avatarview/stream/integration/StreamAvatarBitmapFactory.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Stream.IO, Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.getstream.avatarview.stream.integration 18 | 19 | import android.content.Context 20 | import android.graphics.Bitmap 21 | import android.graphics.Canvas 22 | import android.graphics.Color 23 | import android.graphics.LinearGradient 24 | import android.graphics.Paint 25 | import android.graphics.Shader 26 | import android.graphics.Typeface 27 | import android.util.TypedValue 28 | import androidx.annotation.Px 29 | import io.getstream.avatarview.coil.Avatar 30 | import io.getstream.avatarview.coil.AvatarBitmapFactory 31 | import kotlin.math.abs 32 | import kotlin.math.roundToInt 33 | 34 | /** 35 | * A custom bitmap factory that extends [AvatarBitmapFactory] to implement 36 | * Stream SDK's avatar bitmap style. 37 | */ 38 | public class StreamAvatarBitmapFactory(private val context: Context) : 39 | AvatarBitmapFactory(context) { 40 | 41 | private val gradientBaseColors = 42 | context.resources.getIntArray(R.array.avatarView_gradient_colors) 43 | 44 | override suspend fun loadAvatarPlaceholderBitmap( 45 | data: Any?, 46 | avatar: Avatar, 47 | avatarSize: Int 48 | ): Bitmap { 49 | return createInitialsBitmap(data, avatar, avatarSize) 50 | } 51 | 52 | private fun createInitialsBitmap( 53 | data: Any?, 54 | avatar: Avatar, 55 | @Px avatarSize: Int, 56 | ): Bitmap { 57 | val initials = avatar.getTagFromInitialsMap(data, BAG_AVATAR_INITIALS) ?: "" 58 | return Bitmap.createBitmap(avatarSize, avatarSize, Bitmap.Config.ARGB_8888).apply { 59 | val canvas = Canvas(this) 60 | canvas.drawGradient(initials, avatarSize) 61 | canvas.drawInitials(data, avatar, initials, avatarSize) 62 | } 63 | } 64 | 65 | private fun Canvas.drawGradient(initials: String, avatarSize: Int) { 66 | val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply { 67 | isAntiAlias = true 68 | style = Paint.Style.FILL 69 | shader = createLinearGradientShader(initials, avatarSize) 70 | } 71 | drawRect( 72 | 0f, 73 | 0f, 74 | avatarSize.toFloat(), 75 | avatarSize.toFloat(), 76 | paint 77 | ) 78 | } 79 | 80 | private fun Canvas.drawInitials( 81 | data: Any?, 82 | avatar: Avatar, 83 | initials: String, 84 | @Px avatarSize: Int, 85 | ) { 86 | val textPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { 87 | style = Paint.Style.FILL 88 | textAlign = Paint.Align.CENTER 89 | color = 90 | avatar.getTagFromInitialsMap(data, BAG_AVATAR_INITIALS_TEXT_COLOR) ?: Color.WHITE 91 | textSize = (avatar.getTagFromInitialsMap(data, BAG_AVATAR_INITIALS_TEXT_SIZE) ?: 13f).sp 92 | typeface = Typeface.defaultFromStyle( 93 | avatar.getTagFromInitialsMap(data, BAG_AVATAR_INITIALS_FONT) ?: Typeface.NORMAL 94 | ) 95 | } 96 | drawText( 97 | initials, 98 | avatarSize / 2f, 99 | avatarSize / 2f - (textPaint.ascent() + textPaint.descent()) / 2f, 100 | textPaint 101 | ) 102 | } 103 | 104 | private val Float.sp: Float 105 | get() = TypedValue.applyDimension( 106 | TypedValue.COMPLEX_UNIT_SP, 107 | this, context.resources.displayMetrics 108 | ) 109 | 110 | private fun createLinearGradientShader(initials: String, @Px avatarSize: Int): Shader { 111 | val baseColorIndex = abs(initials.hashCode()) % gradientBaseColors.size 112 | val baseColor = gradientBaseColors[baseColorIndex] 113 | return LinearGradient( 114 | 0f, 115 | 0f, 116 | 0f, 117 | avatarSize.toFloat(), 118 | adjustColorLBrightness(baseColor, GRADIENT_DARKER_COLOR_FACTOR), 119 | adjustColorLBrightness(baseColor, GRADIENT_LIGHTER_COLOR_FACTOR), 120 | Shader.TileMode.CLAMP 121 | ) 122 | } 123 | 124 | private fun adjustColorLBrightness(color: Int, factor: Float): Int { 125 | val a = Color.alpha(color) 126 | val r = (Color.red(color) * factor).roundToInt() 127 | val g = (Color.green(color) * factor).roundToInt() 128 | val b = (Color.blue(color) * factor).roundToInt() 129 | return Color.argb( 130 | a, 131 | r.coerceAtMost(255), 132 | g.coerceAtMost(255), 133 | b.coerceAtMost(255) 134 | ) 135 | } 136 | 137 | @Suppress("UNCHECKED_CAST") 138 | private fun Avatar.getTagFromInitialsMap(data: Any?, key: String): T? { 139 | val map: MutableMap = getTag(data?.toString() ?: "") ?: mutableMapOf() 140 | return map[key] as? T 141 | } 142 | 143 | internal companion object { 144 | private const val GRADIENT_DARKER_COLOR_FACTOR = 1.3f 145 | private const val GRADIENT_LIGHTER_COLOR_FACTOR = 0.7f 146 | 147 | internal const val BAG_AVATAR_INITIALS = "BAG_AVATAR_INITIALS" 148 | internal const val BAG_AVATAR_INITIALS_FONT = "BAG_AVATAR_INITIALS_FONT" 149 | internal const val BAG_AVATAR_INITIALS_TEXT_SIZE = "BAG_AVATAR_INITIALS_TEXT_SIZE" 150 | internal const val BAG_AVATAR_INITIALS_TEXT_COLOR = "BAG_AVATAR_INITIALS_TEXT_COLOR" 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /avatarview-stream-integration/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | #FF8A65 19 | #81C784 20 | #4FC3F7 21 | #A1887F 22 | #FFF176 23 | #E57373 24 | 25 | @color/avatarView_gradient_orange 26 | @color/avatarView_gradient_green 27 | @color/avatarView_gradient_blue 28 | @color/avatarView_gradient_brown 29 | @color/avatarView_gradient_yellow 30 | @color/avatarView_gradient_red 31 | 32 | -------------------------------------------------------------------------------- /avatarview/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /avatarview/api/avatarview.api: -------------------------------------------------------------------------------- 1 | public final class io/getstream/avatarview/AvatarBitmapCombiner { 2 | public static final field INSTANCE Lio/getstream/avatarview/AvatarBitmapCombiner; 3 | public final fun combine (Ljava/util/List;IILandroid/graphics/drawable/Drawable;)Landroid/graphics/Bitmap; 4 | public final fun combine ([Landroid/graphics/Bitmap;IILandroid/graphics/drawable/Drawable;)Landroid/graphics/Bitmap; 5 | public static synthetic fun combine$default (Lio/getstream/avatarview/AvatarBitmapCombiner;Ljava/util/List;IILandroid/graphics/drawable/Drawable;ILjava/lang/Object;)Landroid/graphics/Bitmap; 6 | public static synthetic fun combine$default (Lio/getstream/avatarview/AvatarBitmapCombiner;[Landroid/graphics/Bitmap;IILandroid/graphics/drawable/Drawable;ILjava/lang/Object;)Landroid/graphics/Bitmap; 7 | public final fun drawableToBitmap (Landroid/graphics/drawable/Drawable;I)Landroid/graphics/Bitmap; 8 | } 9 | 10 | public final class io/getstream/avatarview/AvatarShape : java/lang/Enum { 11 | public static final field CIRCLE Lio/getstream/avatarview/AvatarShape; 12 | public static final field ROUNDED_RECT Lio/getstream/avatarview/AvatarShape; 13 | public final fun getValue ()I 14 | public static fun valueOf (Ljava/lang/String;)Lio/getstream/avatarview/AvatarShape; 15 | public static fun values ()[Lio/getstream/avatarview/AvatarShape; 16 | } 17 | 18 | public final class io/getstream/avatarview/AvatarView : androidx/appcompat/widget/AppCompatImageView, kotlinx/coroutines/CoroutineScope { 19 | public fun (Landroid/content/Context;)V 20 | public fun (Landroid/content/Context;Landroid/util/AttributeSet;)V 21 | public fun (Landroid/content/Context;Landroid/util/AttributeSet;I)V 22 | public synthetic fun (Landroid/content/Context;Landroid/util/AttributeSet;IILkotlin/jvm/internal/DefaultConstructorMarker;)V 23 | public final fun getAvatarBorderColor ()I 24 | public final fun getAvatarBorderColorArray ()[I 25 | public final fun getAvatarBorderRadius ()F 26 | public final fun getAvatarBorderWidth ()I 27 | public final fun getAvatarInitials ()Ljava/lang/String; 28 | public final fun getAvatarInitialsBackgroundColor ()I 29 | public final fun getAvatarInitialsStyle ()I 30 | public final fun getAvatarInitialsTextColor ()I 31 | public final fun getAvatarInitialsTextSize ()I 32 | public final fun getAvatarInitialsTextSizeRatio ()F 33 | public final fun getAvatarShape ()Lio/getstream/avatarview/AvatarShape; 34 | public fun getCoroutineContext ()Lkotlin/coroutines/CoroutineContext; 35 | public final fun getErrorPlaceholder ()Landroid/graphics/drawable/Drawable; 36 | public final fun getIndicatorBorderColor ()I 37 | public final fun getIndicatorBorderColorArray ()[I 38 | public final fun getIndicatorBorderSizeCriteria ()F 39 | public final fun getIndicatorColor ()I 40 | public final fun getIndicatorDrawable ()Landroid/graphics/drawable/Drawable; 41 | public final fun getIndicatorEnabled ()Z 42 | public final fun getIndicatorPosition ()Lio/getstream/avatarview/IndicatorPosition; 43 | public final fun getIndicatorSizeCriteria ()F 44 | public final fun getMaxSectionSize ()I 45 | public final fun getPlaceholder ()Landroid/graphics/drawable/Drawable; 46 | public final fun getSupportRtlEnabled ()Z 47 | public final fun setAvatarBorderColor (I)V 48 | public final fun setAvatarBorderColorArray ([I)V 49 | public final fun setAvatarBorderRadius (F)V 50 | public final fun setAvatarBorderWidth (I)V 51 | public final fun setAvatarInitials (Ljava/lang/String;)V 52 | public final fun setAvatarInitialsBackgroundColor (I)V 53 | public final fun setAvatarInitialsStyle (I)V 54 | public final fun setAvatarInitialsTextColor (I)V 55 | public final fun setAvatarInitialsTextSize (I)V 56 | public final fun setAvatarInitialsTextSizeRatio (F)V 57 | public final fun setAvatarShape (Lio/getstream/avatarview/AvatarShape;)V 58 | public final fun setErrorPlaceholder (Landroid/graphics/drawable/Drawable;)V 59 | public final fun setIndicatorBorderColor (I)V 60 | public final fun setIndicatorBorderColorArray ([I)V 61 | public final fun setIndicatorBorderSizeCriteria (F)V 62 | public final fun setIndicatorColor (I)V 63 | public final fun setIndicatorDrawable (Landroid/graphics/drawable/Drawable;)V 64 | public final fun setIndicatorEnabled (Z)V 65 | public final fun setIndicatorPosition (Lio/getstream/avatarview/IndicatorPosition;)V 66 | public final fun setIndicatorRes (I)V 67 | public final fun setIndicatorSizeCriteria (F)V 68 | public final fun setMaxSectionSize (I)V 69 | public final fun setPlaceholder (Landroid/graphics/drawable/Drawable;)V 70 | public final fun setSupportRtlEnabled (Z)V 71 | } 72 | 73 | public final class io/getstream/avatarview/IndicatorPosition : java/lang/Enum { 74 | public static final field BOTTOM_LEFT Lio/getstream/avatarview/IndicatorPosition; 75 | public static final field BOTTOM_RIGHT Lio/getstream/avatarview/IndicatorPosition; 76 | public static final field TOP_LEFT Lio/getstream/avatarview/IndicatorPosition; 77 | public static final field TOP_RIGHT Lio/getstream/avatarview/IndicatorPosition; 78 | public static fun valueOf (Ljava/lang/String;)Lio/getstream/avatarview/IndicatorPosition; 79 | public static fun values ()[Lio/getstream/avatarview/IndicatorPosition; 80 | } 81 | 82 | public abstract interface annotation class io/getstream/avatarview/internal/InternalAvatarViewApi : java/lang/annotation/Annotation { 83 | } 84 | 85 | -------------------------------------------------------------------------------- /avatarview/build.gradle: -------------------------------------------------------------------------------- 1 | import io.getstream.avatarview.Configuration 2 | import io.getstream.avatarview.Dependencies 3 | 4 | plugins { 5 | id 'com.android.library' 6 | id 'org.jetbrains.kotlin.android' 7 | id 'org.jetbrains.dokka' 8 | id 'binary-compatibility-validator' 9 | } 10 | 11 | ext { 12 | PUBLISH_GROUP_ID = Configuration.artifactGroup 13 | if (snapshot) { 14 | PUBLISH_VERSION = Configuration.snapshotVersionName 15 | } else { 16 | PUBLISH_VERSION = Configuration.versionName 17 | } 18 | PUBLISH_ARTIFACT_ID = 'avatarview' 19 | } 20 | 21 | apply from: "${rootDir}/scripts/publish-module.gradle" 22 | 23 | android { 24 | compileSdkVersion Configuration.compileSdk 25 | defaultConfig { 26 | minSdkVersion Configuration.minSdk 27 | targetSdkVersion Configuration.targetSdk 28 | } 29 | 30 | resourcePrefix 'avatarview' 31 | 32 | compileOptions { 33 | sourceCompatibility JavaVersion.VERSION_1_8 34 | targetCompatibility JavaVersion.VERSION_1_8 35 | } 36 | 37 | kotlinOptions { 38 | jvmTarget = '1.8' 39 | } 40 | 41 | lintOptions { 42 | abortOnError false 43 | } 44 | } 45 | 46 | tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { 47 | kotlinOptions.freeCompilerArgs += [ 48 | "-Xexplicit-api=strict", 49 | "-Xopt-in=kotlin.RequiresOptIn", 50 | "-Xopt-in=kotlin.contracts.ExperimentalContracts", 51 | "-Xopt-in=io.getstream.avatarview.internal.InternalAvatarViewApi", 52 | ] 53 | } 54 | 55 | apiValidation { 56 | nonPublicMarkers += ["kotlin.PublishedApi"] 57 | } 58 | 59 | dependencies { 60 | implementation Dependencies.androidxAppcompat 61 | implementation Dependencies.androidxCore 62 | implementation Dependencies.coroutines 63 | } 64 | 65 | -------------------------------------------------------------------------------- /avatarview/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | -------------------------------------------------------------------------------- /avatarview/src/main/kotlin/io/getstream/avatarview/AvatarBitmapCombiner.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Stream.IO, Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.getstream.avatarview 18 | 19 | import android.graphics.Bitmap 20 | import android.graphics.Canvas 21 | import android.graphics.Paint 22 | import android.graphics.Rect 23 | import android.graphics.drawable.BitmapDrawable 24 | import android.graphics.drawable.Drawable 25 | import android.graphics.drawable.VectorDrawable 26 | import android.media.ThumbnailUtils 27 | import androidx.annotation.Px 28 | import androidx.core.graphics.applyCanvas 29 | import io.getstream.avatarview.internal.InternalAvatarViewApi 30 | 31 | /** 32 | * A bitmap combiner to provide segmented style bitmap from a list of bitmaps. 33 | * This combiner supports a maximum of 4 combined bitmaps. 34 | */ 35 | public object AvatarBitmapCombiner { 36 | 37 | private val paint: Paint = Paint(Paint.ANTI_ALIAS_FLAG).apply { 38 | isAntiAlias = true 39 | isFilterBitmap = true 40 | isDither = true 41 | } 42 | 43 | /** 44 | * Combines a list of bitmaps with a specific bitmap size. 45 | * 46 | * @param bitmaps A list of bitmaps to combine. 47 | * @param size A size of the bitmap. 48 | * @param maxSectionSize The maximum size of the sections. 49 | * 50 | * @return A combined bitmap. 51 | */ 52 | public fun combine( 53 | bitmaps: List, 54 | @Px size: Int, 55 | maxSectionSize: Int, 56 | errorPlaceholder: Drawable? = null, 57 | ): Bitmap { 58 | return Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888).applyCanvas { 59 | val sourceRect = Rect(0, 0, size, size) 60 | val mappedBitmaps = bitmaps.mapNotNull { bitmap -> 61 | bitmap ?: drawableToBitmap(errorPlaceholder, size) 62 | } 63 | createAvatarItems(mappedBitmaps, size, maxSectionSize).forEach { 64 | drawBitmap(it.bitmap, sourceRect, it.position, paint) 65 | } 66 | } 67 | } 68 | 69 | /** 70 | * Combines a list of bitmaps with a specific bitmap size. 71 | * 72 | * @param bitmaps A list of bitmaps to combine. 73 | * @param size A size of the bitmap. 74 | * @param maxSectionSize The maximum size of the sections. 75 | * 76 | * @return A combined bitmap. 77 | */ 78 | public fun combine( 79 | vararg bitmaps: Bitmap?, 80 | @Px size: Int, 81 | maxSectionSize: Int, 82 | errorPlaceholder: Drawable? = null, 83 | ): Bitmap { 84 | return combine(bitmaps.toList(), size, maxSectionSize, errorPlaceholder) 85 | } 86 | 87 | /** 88 | * Creates a list of [AvatarItem]s with a specific bitmap size. 89 | * 90 | * @param bitmaps A list of bitmaps to combine. 91 | * @param size A size of the bitmap. 92 | * @param maxSectionSize The maximum size of the sections. 93 | * 94 | * @return A list of [AvatarItem]. 95 | */ 96 | private fun createAvatarItems( 97 | bitmaps: List, 98 | @Px size: Int, 99 | maxSectionSize: Int 100 | ): List { 101 | val avatarBitmaps = bitmaps.take(maxSectionSize) 102 | return when (avatarBitmaps.size) { 103 | 0 -> emptyList() 104 | 1 -> listOf( 105 | avatarBitmaps[0].toAvatarItem(SectionType.FULL_CIRCLE, size) 106 | ) 107 | 2 -> listOf( 108 | avatarBitmaps[0].toAvatarItem(SectionType.LEFT, size), 109 | avatarBitmaps[1].toAvatarItem(SectionType.RIGHT, size) 110 | ) 111 | 3 -> listOf( 112 | avatarBitmaps[0].toAvatarItem(SectionType.TOP_LEFT, size), 113 | avatarBitmaps[1].toAvatarItem(SectionType.TOP_RIGHT, size), 114 | avatarBitmaps[2].toAvatarItem(SectionType.BOTTOM, size) 115 | ) 116 | else -> listOf( 117 | avatarBitmaps[0].toAvatarItem(SectionType.TOP_LEFT, size), 118 | avatarBitmaps[1].toAvatarItem(SectionType.TOP_RIGHT, size), 119 | avatarBitmaps[2].toAvatarItem(SectionType.BOTTOM_LEFT, size), 120 | avatarBitmaps[3].toAvatarItem(SectionType.BOTTOM_RIGHT, size) 121 | ) 122 | } 123 | } 124 | 125 | /** 126 | * Convert a bitmap to an [AvatarItem] by the [SectionType]. 127 | * 128 | * @param sectionType A type of section to determine [AvatarItem]. 129 | * @param size A size of the bitmap. 130 | * 131 | * @return An instance of [AvatarItem]. 132 | * 133 | */ 134 | private fun Bitmap.toAvatarItem(sectionType: SectionType, @Px size: Int): AvatarItem { 135 | return when (sectionType) { 136 | SectionType.FULL_CIRCLE -> { 137 | AvatarItem(scaleCenterCrop(size, size), Rect(0, 0, size, size)) 138 | } 139 | SectionType.LEFT -> { 140 | AvatarItem(scaleCenterCrop(size / 2, size), Rect(0, 0, size, size)) 141 | } 142 | SectionType.RIGHT -> { 143 | AvatarItem( 144 | scaleCenterCrop(size / 2, size), 145 | Rect(size / 2, 0, size + size / 2, size) 146 | ) 147 | } 148 | SectionType.BOTTOM -> { 149 | AvatarItem( 150 | scaleCenterCrop(size, size / 2), 151 | Rect(0, size / 2, size, size + size / 2) 152 | ) 153 | } 154 | SectionType.TOP_LEFT -> { 155 | AvatarItem( 156 | scaleCenterCrop(size, size), 157 | Rect(0, 0, size / 2, size / 2) 158 | ) 159 | } 160 | SectionType.TOP_RIGHT -> { 161 | AvatarItem( 162 | scaleCenterCrop(size, size), 163 | Rect(size / 2, 0, size, size / 2) 164 | ) 165 | } 166 | SectionType.BOTTOM_LEFT -> { 167 | AvatarItem( 168 | scaleCenterCrop(size, size), 169 | Rect(0, size / 2, size / 2, size) 170 | ) 171 | } 172 | SectionType.BOTTOM_RIGHT -> { 173 | AvatarItem( 174 | scaleCenterCrop(size, size), 175 | Rect(size / 2, size / 2, size, size) 176 | ) 177 | } 178 | } 179 | } 180 | 181 | /** 182 | * Creates a centered bitmap of the desired size. 183 | * 184 | * @param newWidth A desired width size. 185 | * @param newHeight A desired width size. 186 | * 187 | * @return A new extracted bitmap. 188 | */ 189 | private fun Bitmap.scaleCenterCrop(@Px newWidth: Int, @Px newHeight: Int): Bitmap { 190 | return ThumbnailUtils.extractThumbnail(this, newWidth, newHeight) 191 | } 192 | 193 | /** 194 | * Creates a Bitmap from a drawable with a specific size specs. 195 | * 196 | * @param drawable A drawable should be created as Bitmap. 197 | * @param size A desired of the new Bitmap. 198 | * 199 | * @return A new created Bitmap. 200 | */ 201 | @InternalAvatarViewApi 202 | public fun drawableToBitmap(drawable: Drawable?, @Px size: Int): Bitmap? = 203 | if (drawable is VectorDrawable) { 204 | drawable.vectorDrawableToBitmap(size) 205 | } else { 206 | when (drawable) { 207 | is BitmapDrawable -> drawable.bitmapDrawableToBitmap(size) 208 | else -> drawable?.toBitmap(size) 209 | } 210 | } 211 | 212 | /** 213 | * Creates a Bitmap from a [VectorDrawable] with a specific size specs. 214 | * 215 | * @param size A desired of the new Bitmap. 216 | * @return A new created Bitmap. 217 | */ 218 | private fun VectorDrawable.vectorDrawableToBitmap(@Px size: Int): Bitmap { 219 | val bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888) 220 | val canvas = Canvas(bitmap) 221 | this.setBounds(0, 0, canvas.width, canvas.height) 222 | this.draw(canvas) 223 | return bitmap 224 | } 225 | 226 | /** 227 | * Creates a Bitmap from a [BitmapDrawable] with a specific size specs. 228 | * 229 | * @param size A desired of the new Bitmap. 230 | * @return A new created Bitmap. 231 | */ 232 | private fun BitmapDrawable.bitmapDrawableToBitmap(@Px size: Int): Bitmap = 233 | Bitmap.createScaledBitmap(bitmap, size, size, false) 234 | 235 | /** 236 | * Creates a Bitmap from a [Bitmap] with a specific size specs. 237 | * 238 | * @param size A desired of the new Bitmap. 239 | * @return A new created Bitmap. 240 | */ 241 | private fun Drawable.toBitmap(@Px size: Int): Bitmap? = 242 | try { 243 | val bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888) 244 | val canvas = Canvas(bitmap) 245 | this.setBounds(0, 0, canvas.width, canvas.height) 246 | this.draw(canvas) 247 | bitmap 248 | } catch (e: Exception) { 249 | e.printStackTrace() 250 | null 251 | } 252 | 253 | /** 254 | * A data holder of a bitmap and positions. 255 | */ 256 | private data class AvatarItem(val bitmap: Bitmap, val position: Rect) 257 | 258 | /** 259 | * A type of sections. 260 | */ 261 | private enum class SectionType { 262 | FULL_CIRCLE, 263 | LEFT, 264 | RIGHT, 265 | BOTTOM, 266 | TOP_LEFT, 267 | TOP_RIGHT, 268 | BOTTOM_LEFT, 269 | BOTTOM_RIGHT 270 | } 271 | } 272 | -------------------------------------------------------------------------------- /avatarview/src/main/kotlin/io/getstream/avatarview/AvatarShape.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Stream.IO, Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.getstream.avatarview 18 | 19 | /** An attribute type of [AvatarView] to determine the shape of the image and border. */ 20 | public enum class AvatarShape(public val value: Int) { 21 | /** Circle cropped image and border. */ 22 | CIRCLE(0), 23 | 24 | /** Round rect cropped image and border. */ 25 | ROUNDED_RECT(1), 26 | } 27 | -------------------------------------------------------------------------------- /avatarview/src/main/kotlin/io/getstream/avatarview/IndicatorPosition.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Stream.IO, Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.getstream.avatarview 18 | 19 | /** 20 | * An attribute type of [AvatarView] to determine position of the indicator. 21 | */ 22 | public enum class IndicatorPosition { 23 | /** Top-Left */ 24 | TOP_LEFT, 25 | 26 | /** Top-Right */ 27 | TOP_RIGHT, 28 | 29 | /** Bottom-Left */ 30 | BOTTOM_LEFT, 31 | 32 | /** Bottom-Right */ 33 | BOTTOM_RIGHT 34 | } 35 | -------------------------------------------------------------------------------- /avatarview/src/main/kotlin/io/getstream/avatarview/internal/Extensions.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Stream.IO, Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.getstream.avatarview.internal 18 | 19 | import android.content.res.Resources 20 | import android.content.res.TypedArray 21 | import android.util.TypedValue 22 | import android.view.View 23 | import androidx.core.view.ViewCompat 24 | import kotlin.contracts.InvocationKind 25 | import kotlin.contracts.contract 26 | import kotlin.math.roundToInt 27 | 28 | /** Returns integer dimensional value from the integer px value. */ 29 | internal val Int.dp: Int 30 | @JvmSynthetic inline get() = TypedValue.applyDimension( 31 | TypedValue.COMPLEX_UNIT_DIP, this.toFloat(), 32 | Resources.getSystem().displayMetrics 33 | ).roundToInt() 34 | 35 | /** Returns enum object the corresponding index. */ 36 | @JvmSynthetic 37 | internal inline fun > TypedArray.getEnum(index: Int, default: T): T { 38 | return getInt(index, -1).let { 39 | if (it >= 0) enumValues()[it] else default 40 | } 41 | } 42 | 43 | /** Returns an array of Int the corresponding resource id. */ 44 | @JvmSynthetic 45 | internal fun TypedArray.getIntArray(resourceId: Int, default: IntArray): IntArray { 46 | return getResourceId(resourceId, -1).let { 47 | if (it >= 0) resources.getIntArray(it) else default 48 | } 49 | } 50 | 51 | /** Extension scope function for [TypedArray]. */ 52 | @JvmSynthetic 53 | internal inline fun TypedArray.use(block: (TypedArray) -> Unit) { 54 | contract { 55 | callsInPlace(block, InvocationKind.EXACTLY_ONCE) 56 | } 57 | block(this) 58 | recycle() 59 | } 60 | 61 | /** Returns parsed initials from a String. */ 62 | internal val String.parseInitials: String 63 | @JvmSynthetic inline get() { 64 | val textList = trim().split(" ") 65 | return when { 66 | textList.size > 1 -> "${textList[0][0]}${textList[1][0]}" 67 | textList[0].length > 1 -> "${textList[0][0]}${textList[0][1]}" 68 | textList[0].isNotEmpty() -> "${textList[0][0]}" 69 | else -> "" 70 | }.uppercase() 71 | } 72 | 73 | /** Returns a view is RTL layout or not. */ 74 | internal val View.isRtlLayout: Boolean 75 | @JvmSynthetic get() = ViewCompat.getLayoutDirection(this) == ViewCompat.LAYOUT_DIRECTION_RTL 76 | 77 | /** Returns an array of Shader positions from the int array. */ 78 | internal val IntArray.arrayPositions: FloatArray 79 | @JvmSynthetic inline get() { 80 | val positions = arrayListOf(0f) 81 | val interval = 1.0f / this.size 82 | for (i in 1 until size) { 83 | positions.add(positions[i - 1] + interval) 84 | } 85 | return positions.toFloatArray() 86 | } 87 | -------------------------------------------------------------------------------- /avatarview/src/main/kotlin/io/getstream/avatarview/internal/InternalAvatarViewApi.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Stream.IO, Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.getstream.avatarview.internal 18 | 19 | @RequiresOptIn( 20 | message = "This is internal API for the AvatarView libraries. Do not depend on this API in your own client code.", 21 | level = RequiresOptIn.Level.ERROR 22 | ) 23 | public annotation class InternalAvatarViewApi 24 | -------------------------------------------------------------------------------- /avatarview/src/main/kotlin/io/getstream/avatarview/internal/Properties.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Stream.IO, Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.getstream.avatarview.internal 18 | 19 | import android.graphics.Color 20 | import androidx.annotation.ColorInt 21 | 22 | /** A definition of the internal blue color. */ 23 | internal val internalBlue: Int 24 | @JvmSynthetic @ColorInt get() = Color.parseColor("#005FFF") 25 | 26 | /** A definition of the internal green color. */ 27 | internal val internalGreen: Int 28 | @JvmSynthetic @ColorInt get() = Color.parseColor("#20E070") 29 | -------------------------------------------------------------------------------- /avatarview/src/main/kotlin/io/getstream/avatarview/internal/ViewProperty.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Stream.IO, Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.getstream.avatarview.internal 18 | 19 | import android.view.View 20 | import kotlin.properties.ReadWriteProperty 21 | import kotlin.reflect.KProperty 22 | 23 | /** 24 | * An extension for properties on View classes to initialize with [ViewPropertyDelegate]. 25 | * 26 | * @param defaultValue A default value for this property. 27 | * 28 | * @return A [ViewPropertyDelegate] which is readable and writable property. 29 | */ 30 | @JvmSynthetic 31 | internal fun View.viewProperty(defaultValue: T): ViewPropertyDelegate { 32 | return ViewPropertyDelegate(defaultValue) { 33 | invalidate() 34 | } 35 | } 36 | 37 | /** 38 | * A delegate class to invalidate View class if the [propertyValue] has been updated by a new value. 39 | * 40 | * @param defaultValue A default value for this property. 41 | * @param invalidator An executable lambda to invalidate [View]. 42 | * 43 | * @return A readable and writable property. 44 | */ 45 | internal class ViewPropertyDelegate( 46 | defaultValue: T, 47 | private val invalidator: () -> Unit 48 | ) : ReadWriteProperty { 49 | 50 | private var propertyValue: T = defaultValue 51 | 52 | override fun getValue(thisRef: Any?, property: KProperty<*>): T { 53 | return propertyValue 54 | } 55 | 56 | override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) { 57 | if (propertyValue != value) { 58 | propertyValue = value 59 | invalidator() 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /avatarview/src/main/res/values/attrs_avatar_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | import io.getstream.avatarview.Dependencies 2 | 3 | apply plugin: 'io.github.gradle-nexus.publish-plugin' 4 | apply plugin: 'org.jetbrains.dokka' 5 | 6 | buildscript { 7 | repositories { 8 | maven { url "https://plugins.gradle.org/m2/" } 9 | google() 10 | mavenCentral() 11 | } 12 | dependencies { 13 | classpath Dependencies.androidGradlePlugin 14 | classpath Dependencies.kotlinGradlePlugin 15 | classpath Dependencies.spotlessGradlePlugin 16 | classpath Dependencies.gradleNexusPublishPlugin 17 | classpath Dependencies.dokka 18 | classpath Dependencies.kotlinBinaryValidator 19 | } 20 | } 21 | 22 | tasks.withType(org.jetbrains.dokka.gradle.DokkaMultiModuleTask).configureEach { 23 | outputDirectory = rootProject.file('docs/api') 24 | failOnWarning = true 25 | } 26 | 27 | subprojects { 28 | apply from: "$rootDir/spotless/spotless.gradle" 29 | } 30 | 31 | task clean(type: Delete) { 32 | delete rootProject.buildDir 33 | } 34 | 35 | apply from: "${rootDir}/scripts/publish-root.gradle" 36 | -------------------------------------------------------------------------------- /buildSrc/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `kotlin-dsl` 3 | } 4 | 5 | repositories { 6 | mavenCentral() 7 | } -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/io/getstream/avatarview/Configuration.kt: -------------------------------------------------------------------------------- 1 | package io.getstream.avatarview 2 | 3 | object Configuration { 4 | const val compileSdk = 33 5 | const val targetSdk = 33 6 | const val minSdk = 21 7 | const val majorVersion = 1 8 | const val minorVersion = 0 9 | const val patchVersion = 7 10 | const val versionName = "$majorVersion.$minorVersion.$patchVersion" 11 | const val versionCode = 8 12 | const val snapshotVersionName = "$majorVersion.$minorVersion.${patchVersion + 1}-SNAPSHOT" 13 | const val artifactGroup = "io.getstream" 14 | } 15 | -------------------------------------------------------------------------------- /buildSrc/src/main/kotlin/io/getstream/avatarview/Dependencies.kt: -------------------------------------------------------------------------------- 1 | package io.getstream.avatarview 2 | 3 | object Versions { 4 | internal const val ANDROID_GRADLE_PLUGIN = "7.3.0" 5 | internal const val ANDROID_GRADLE_SPOTLESS = "6.7.0" 6 | internal const val GRADLE_NEXUS_PUBLISH_PLUGIN = "1.1.0" 7 | internal const val KOTLIN = "1.7.20" 8 | internal const val KOTLIN_GRADLE_DOKKA = "1.7.10" 9 | internal const val KOTLIN_BINARY_VALIDATOR = "0.11.1" 10 | internal const val KOTLIN_COROUTINE = "1.6.4" 11 | 12 | internal const val MATERIAL = "1.6.0" 13 | internal const val ANDROIDX_APPCOMPAT = "1.4.0" 14 | internal const val ANDROIDX_CORE = "1.8.0" 15 | 16 | internal const val OKHTTP = "4.9.2" 17 | internal const val COIL = "2.2.1" 18 | internal const val GLIDE = "4.14.1" 19 | 20 | internal const val STREAM_CHAT_SDK = "5.11.1" 21 | } 22 | 23 | object Dependencies { 24 | const val androidGradlePlugin = 25 | "com.android.tools.build:gradle:${Versions.ANDROID_GRADLE_PLUGIN}" 26 | const val gradleNexusPublishPlugin = 27 | "io.github.gradle-nexus:publish-plugin:${Versions.GRADLE_NEXUS_PUBLISH_PLUGIN}" 28 | const val kotlinGradlePlugin = "org.jetbrains.kotlin:kotlin-gradle-plugin:${Versions.KOTLIN}" 29 | const val spotlessGradlePlugin = 30 | "com.diffplug.spotless:spotless-plugin-gradle:${Versions.ANDROID_GRADLE_SPOTLESS}" 31 | const val dokka = "org.jetbrains.dokka:dokka-gradle-plugin:${Versions.KOTLIN_GRADLE_DOKKA}" 32 | const val kotlinBinaryValidator = 33 | "org.jetbrains.kotlinx:binary-compatibility-validator:${Versions.KOTLIN_BINARY_VALIDATOR}" 34 | const val coroutines = 35 | "org.jetbrains.kotlinx:kotlinx-coroutines-core:${Versions.KOTLIN_COROUTINE}" 36 | 37 | const val material = "com.google.android.material:material:${Versions.MATERIAL}" 38 | const val androidxAppcompat = "androidx.appcompat:appcompat:${Versions.ANDROIDX_APPCOMPAT}" 39 | const val androidxCore = "androidx.core:core-ktx:${Versions.ANDROIDX_CORE}" 40 | 41 | const val okhttp = "com.squareup.okhttp3:okhttp:${Versions.OKHTTP}" 42 | const val coil = "io.coil-kt:coil:${Versions.COIL}" 43 | const val coilGif = "io.coil-kt:coil-gif:${Versions.COIL}" 44 | const val glide = "com.github.bumptech.glide:glide:${Versions.GLIDE}" 45 | 46 | const val streamChatSdk = "io.getstream:stream-chat-android-client:${Versions.STREAM_CHAT_SDK}" 47 | } 48 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # https://docs.gradle.org/current/userguide/build_environment.html#sec:configuring_jvm_memory 3 | org.gradle.jvmargs=-Xmx4g -XX:+HeapDumpOnOutOfMemoryError -XX:+UseParallelGC -XX:MaxMetaspaceSize=512m -Dkotlin.daemon.jvm.options=-XX:MaxMetaspaceSize=1g -Dlint.nullness.ignore-deprecated=true 4 | 5 | # https://docs.gradle.org/current/userguide/build_cache.html 6 | org.gradle.caching=true 7 | 8 | # When configured, Gradle will run in incubating parallel mode. 9 | # This option should only be used with decoupled projects. More details, visit 10 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 11 | org.gradle.parallel=true 12 | 13 | # Configure only necessary projects, useful with multimodule projects 14 | org.gradle.configureondemand=true 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true 19 | # AndroidX package structure to make it clearer which packages are bundled with the 20 | # Android operating system, and which are packaged with your app"s APK 21 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 22 | android.useAndroidX=true 23 | # Automatically convert third-party libraries to use AndroidX 24 | android.enableJetifier=true 25 | # Kotlin code style for this project: "official" or "obsolete": 26 | kotlin.code.style=official 27 | # Removes uneccessary default build features 28 | android.defaults.buildfeatures.aidl=false 29 | android.defaults.buildfeatures.buildconfig=false 30 | android.defaults.buildfeatures.renderscript=false 31 | android.defaults.buildfeatures.resvalues=false 32 | android.defaults.buildfeatures.shaders=false 33 | # Enables namespacing of each library's R class so that its R class includes only the 34 | # resources declared in the library itself and none from the library's dependencies, 35 | # thereby reducing the size of the R class for that library 36 | # https://developer.android.com/studio/releases/gradle-plugin#4.1-nontransitive-r-class 37 | android.nonTransitiveRClass=true 38 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/avatarview-android/d0bb30467a12c1efae00e41eb260bc2f1f3b4f38/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 84 | 85 | APP_NAME="Gradle" 86 | APP_BASE_NAME=${0##*/} 87 | 88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | MAX_FD=$( ulimit -H -n ) || 147 | warn "Could not query maximum file descriptor limit" 148 | esac 149 | case $MAX_FD in #( 150 | '' | soft) :;; #( 151 | *) 152 | ulimit -n "$MAX_FD" || 153 | warn "Could not set maximum file descriptor limit to $MAX_FD" 154 | esac 155 | fi 156 | 157 | # Collect all arguments for the java command, stacking in reverse order: 158 | # * args from the command line 159 | # * the main class name 160 | # * -classpath 161 | # * -D...appname settings 162 | # * --module-path (only if needed) 163 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 164 | 165 | # For Cygwin or MSYS, switch paths to Windows format before running java 166 | if "$cygwin" || "$msys" ; then 167 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 168 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 169 | 170 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 171 | 172 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 173 | for arg do 174 | if 175 | case $arg in #( 176 | -*) false ;; # don't mess with options #( 177 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 178 | [ -e "$t" ] ;; #( 179 | *) false ;; 180 | esac 181 | then 182 | arg=$( cygpath --path --ignore --mixed "$arg" ) 183 | fi 184 | # Roll the args list around exactly as many times as the number of 185 | # args, so each arg winds up back in the position where it started, but 186 | # possibly modified. 187 | # 188 | # NB: a `for` loop captures its iteration list before it begins, so 189 | # changing the positional parameters here affects neither the number of 190 | # iterations, nor the values presented in `arg`. 191 | shift # remove old arg 192 | set -- "$@" "$arg" # push replacement arg 193 | done 194 | fi 195 | 196 | # Collect all arguments for the java command; 197 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 198 | # shell script including quotes and variable substitutions, so put them in 199 | # double quotes to make sure that they get re-expanded; and 200 | # * put everything else in single quotes, so that it's not re-expanded. 201 | 202 | set -- \ 203 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 204 | -classpath "$CLASSPATH" \ 205 | org.gradle.wrapper.GradleWrapperMain \ 206 | "$@" 207 | 208 | # Use "xargs" to parse quoted args. 209 | # 210 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 211 | # 212 | # In Bash we could simply go: 213 | # 214 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 215 | # set -- "${ARGS[@]}" "$@" 216 | # 217 | # but POSIX shell has neither arrays nor command substitution, so instead we 218 | # post-process each arg (as a line of input to sed) to backslash-escape any 219 | # character that might be a shell metacharacter, then use eval to reverse 220 | # that process (while maintaining the separation between arguments), and wrap 221 | # the whole thing up as a single "set" statement. 222 | # 223 | # This will of course break if any of these variables contains a newline or 224 | # an unmatched quote. 225 | # 226 | 227 | eval "set -- $( 228 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 229 | xargs -n1 | 230 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 231 | tr '\n' ' ' 232 | )" '"$@"' 233 | 234 | exec "$JAVACMD" "$@" 235 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /preview/dokka-avatarview.svg: -------------------------------------------------------------------------------- 1 | Dokka: avatarviewDokkaavatarview -------------------------------------------------------------------------------- /preview/preview0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/avatarview-android/d0bb30467a12c1efae00e41eb260bc2f1f3b4f38/preview/preview0.png -------------------------------------------------------------------------------- /preview/preview1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/avatarview-android/d0bb30467a12c1efae00e41eb260bc2f1f3b4f38/preview/preview1.png -------------------------------------------------------------------------------- /preview/preview10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/avatarview-android/d0bb30467a12c1efae00e41eb260bc2f1f3b4f38/preview/preview10.png -------------------------------------------------------------------------------- /preview/preview2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/avatarview-android/d0bb30467a12c1efae00e41eb260bc2f1f3b4f38/preview/preview2.png -------------------------------------------------------------------------------- /preview/preview3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/avatarview-android/d0bb30467a12c1efae00e41eb260bc2f1f3b4f38/preview/preview3.png -------------------------------------------------------------------------------- /preview/preview4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/avatarview-android/d0bb30467a12c1efae00e41eb260bc2f1f3b4f38/preview/preview4.png -------------------------------------------------------------------------------- /preview/preview5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/avatarview-android/d0bb30467a12c1efae00e41eb260bc2f1f3b4f38/preview/preview5.png -------------------------------------------------------------------------------- /preview/preview6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/avatarview-android/d0bb30467a12c1efae00e41eb260bc2f1f3b4f38/preview/preview6.png -------------------------------------------------------------------------------- /preview/preview7.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/avatarview-android/d0bb30467a12c1efae00e41eb260bc2f1f3b4f38/preview/preview7.gif -------------------------------------------------------------------------------- /preview/preview8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/avatarview-android/d0bb30467a12c1efae00e41eb260bc2f1f3b4f38/preview/preview8.png -------------------------------------------------------------------------------- /preview/preview9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/avatarview-android/d0bb30467a12c1efae00e41eb260bc2f1f3b4f38/preview/preview9.png -------------------------------------------------------------------------------- /scripts/publish-module.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'maven-publish' 2 | apply plugin: 'signing' 3 | apply plugin: 'org.jetbrains.dokka' 4 | 5 | task androidSourcesJar(type: Jar) { 6 | archiveClassifier.set('sources') 7 | if (project.plugins.findPlugin("com.android.library")) { 8 | from android.sourceSets.main.java.srcDirs 9 | from android.sourceSets.main.kotlin.srcDirs 10 | } else { 11 | from sourceSets.main.java.srcDirs 12 | from sourceSets.main.kotlin.srcDirs 13 | } 14 | } 15 | 16 | tasks.withType(dokkaHtmlPartial.getClass()).configureEach { 17 | pluginsMapConfiguration.set( 18 | ["org.jetbrains.dokka.base.DokkaBase": """{ "separateInheritedMembers": true}"""] 19 | ) 20 | } 21 | 22 | task javadocJar(type: Jar, dependsOn: dokkaJavadoc) { 23 | archiveClassifier.set('javadoc') 24 | from dokkaJavadoc.outputDirectory 25 | } 26 | 27 | artifacts { 28 | archives androidSourcesJar 29 | archives javadocJar 30 | } 31 | 32 | group = PUBLISH_GROUP_ID 33 | version = PUBLISH_VERSION 34 | 35 | afterEvaluate { 36 | publishing { 37 | publications { 38 | release(MavenPublication) { 39 | groupId PUBLISH_GROUP_ID 40 | artifactId PUBLISH_ARTIFACT_ID 41 | version PUBLISH_VERSION 42 | if (project.plugins.findPlugin("com.android.library")) { 43 | from components.release 44 | } else { 45 | from components.java 46 | } 47 | 48 | artifact androidSourcesJar 49 | artifact javadocJar 50 | 51 | pom { 52 | name = PUBLISH_ARTIFACT_ID 53 | description = 'AvatarView supports segmented style images, borders, indicators, and initials.' 54 | url = 'https://github.com/getstream/avatarview-android' 55 | licenses { 56 | license { 57 | name = 'The Apache Software License, Version 2.0' 58 | url = 'http://www.apache.org/licenses/LICENSE-2.0.txt' 59 | } 60 | } 61 | developers { 62 | developer { 63 | id = 'skydoves' 64 | name = 'Jaewoong Eum' 65 | } 66 | } 67 | scm { 68 | connection = 'scm:git:github.com/getstream/avatarview-android.git' 69 | developerConnection = 'scm:git:ssh://github.com/getstream/avatarview-android.git' 70 | url = 'https://github.com/getstream/avatarview-android/tree/main' 71 | } 72 | } 73 | } 74 | } 75 | } 76 | } 77 | 78 | signing { 79 | useInMemoryPgpKeys( 80 | rootProject.ext["signing.keyId"], 81 | rootProject.ext["signing.key"], 82 | rootProject.ext["signing.password"], 83 | ) 84 | sign publishing.publications 85 | } 86 | -------------------------------------------------------------------------------- /scripts/publish-root.gradle: -------------------------------------------------------------------------------- 1 | import io.getstream.avatarview.Configuration 2 | 3 | // Create variables with empty default values 4 | ext["ossrhUsername"] = '' 5 | ext["ossrhPassword"] = '' 6 | ext["sonatypeStagingProfileId"] = '' 7 | ext["signing.keyId"] = '' 8 | ext["signing.password"] = '' 9 | ext["signing.key"] = '' 10 | ext["snapshot"] = '' 11 | 12 | File secretPropsFile = project.rootProject.file('local.properties') 13 | if (secretPropsFile.exists()) { 14 | // Read local.properties file first if it exists 15 | Properties p = new Properties() 16 | new FileInputStream(secretPropsFile).withCloseable { is -> p.load(is) } 17 | p.each { name, value -> ext[name] = value } 18 | } else { 19 | // Use system environment variables 20 | ext["ossrhUsername"] = System.getenv('OSSRH_USERNAME') 21 | ext["ossrhPassword"] = System.getenv('OSSRH_PASSWORD') 22 | ext["sonatypeStagingProfileId"] = System.getenv('SONATYPE_STAGING_PROFILE_ID') 23 | ext["signing.keyId"] = System.getenv('SIGNING_KEY_ID') 24 | ext["signing.password"] = System.getenv('SIGNING_PASSWORD') 25 | ext["signing.key"] = System.getenv('SIGNING_KEY') 26 | ext["snapshot"] = System.getenv('SNAPSHOT') 27 | } 28 | 29 | if (snapshot) { 30 | ext["rootVersionName"] = Configuration.snapshotVersionName 31 | } else { 32 | ext["rootVersionName"] = Configuration.versionName 33 | } 34 | 35 | // Set up Sonatype repository 36 | nexusPublishing { 37 | repositories { 38 | sonatype { 39 | stagingProfileId = sonatypeStagingProfileId 40 | username = ossrhUsername 41 | password = ossrhPassword 42 | version = rootVersionName 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | google() 5 | mavenCentral() 6 | } 7 | } 8 | dependencyResolutionManagement { 9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 10 | repositories { 11 | google() 12 | mavenCentral() 13 | } 14 | } 15 | rootProject.name = "AvatarView" 16 | include ':app' 17 | include ':avatarview' 18 | include ':avatarview-coil' 19 | include ':avatarview-glide' 20 | include ':avatarview-stream-integration' 21 | -------------------------------------------------------------------------------- /spotless/copyright.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 Stream.IO, Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | -------------------------------------------------------------------------------- /spotless/copyright.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | -------------------------------------------------------------------------------- /spotless/spotless.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: "com.diffplug.spotless" 2 | 3 | spotless { 4 | kotlin { 5 | target "**/*.kt" 6 | targetExclude "**/build/**/*.kt" 7 | ktlint() 8 | licenseHeaderFile "$rootDir/spotless/copyright.kt" 9 | trimTrailingWhitespace() 10 | endWithNewline() 11 | } 12 | 13 | format "xml", { 14 | target "**/*.xml" 15 | targetExclude "**/build/**/*.xml" 16 | licenseHeaderFile "$rootDir/spotless/copyright.xml", "(<[^!?])" 17 | } 18 | } 19 | --------------------------------------------------------------------------------