[
  {
    "path": ".github/CODEOWNERS",
    "content": "\n* @PatilShreyas\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "github: #\npatreon: # Replace with a single Patreon username\nopen_collective: # Replace with a single Open Collective username\nko_fi: # Replace with a single Ko-fi username\ntidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel\ncommunity_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry\nliberapay: # Replace with a single Liberapay username\nissuehunt: PatilShreyas\notechie: # Replace with a single Otechie username\ncustom: ['https://www.paypal.me/PatilShreyas99/', 'https://github.com/sponsors/PatilShreyas/']\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "# To get started with Dependabot version updates, you'll need to specify which\n# package ecosystems to update and where the package manifests are located.\n# Please see the documentation for all configuration options:\n# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates\n\nversion: 2\nupdates:\n  - package-ecosystem: \"gradle\"\n    directory: \"/\"\n    schedule:\n      interval: \"daily\"\n      \n    commit-message:\n      prefix: \"[Update]\"\n"
  },
  {
    "path": ".github/workflows/build.yml",
    "content": "name: Build\non: [push, pull_request]\n\njobs:\n  build:\n    name: Build\n    runs-on: ubuntu-latest\n\n    steps:\n      - uses: actions/checkout@v1\n\n      - name: Set up JDK 17\n        uses: actions/setup-java@v3\n        with:\n          java-version: 17\n          distribution: temurin\n          cache: gradle\n          \n      - name: Grant Permission to Execute\n        run: chmod +x gradlew\n        \n      - name: Build with Gradle\n        uses: gradle/gradle-build-action@v2\n        with:\n          arguments: build koverXmlReport --stacktrace\n        \n      - name: Upload Coverage report to CodeCov\n        uses: codecov/codecov-action@v2\n        with:\n          token: ${{ secrets.CODECOV_TOKEN }}\n          files: permission-flow/build/coverageReport/report.xml\n          flags: unittests\n          fail_ci_if_error: true\n          verbose: true\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: Release\non:\n  workflow_dispatch:\n    inputs:\n      versionName:\n        description: 'Version Name'\n        required: true\n\njobs:\n  release:\n    name: Release\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v2\n\n      - name: Set up JDK 17\n        uses: actions/setup-java@v3\n        with:\n          java-version: 17\n          distribution: temurin\n          cache: gradle\n\n      - name: Grant Permission to Execute Gradle\n        run: chmod +x gradlew\n\n      - name: Build with Gradle\n        uses: gradle/gradle-build-action@v2\n        with:\n          arguments: build dokkaHtmlMultiModule koverHtmlReport\n\n      - name: Publish Library\n        run: |\n          echo \"Publishing library 🚀\"\n          ./gradlew publishAndReleaseToMavenCentral --no-configuration-cache\n        env:\n          ORG_GRADLE_PROJECT_VERSION_NAME: ${{ github.event.inputs.versionName }}\n          ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.GPG_KEY }}\n          ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.GPG_PASSWORD }}\n          ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }}\n          ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_PASSWORD }}\n\n      - name: Create and push tag\n        run: |\n          git config --global user.email \"shreyaspatilg@gmail.com\"\n          git config --global user.name \"$GITHUB_ACTOR\"\n\n          git tag -a $TAG -m \"Release $TAG\"\n          git push origin $TAG\n        env:\n          TAG: v${{ github.event.inputs.versionName }}\n\n      - name: Create Release on GitHub\n        id: create_release\n        uses: actions/create-release@v1\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        with:\n          tag_name: v${{ github.event.inputs.versionName }}\n          release_name: v${{ github.event.inputs.versionName }}\n          draft: true\n          prerelease: false\n          \n      - name: Gather API Documentation and Coverage Report\n        run: |\n          mkdir gh-pages\n          mv README.md gh-pages/README.md\n          mv build/docs gh-pages/docs\n          mv permission-flow/build/coverageReport/html gh-pages/coverageReport\n          \n      - name: Publish Documentation and Coverage Report\n        uses: JamesIves/github-pages-deploy-action@v4.3.3\n        with:\n          branch: gh-pages \n          folder: gh-pages\n"
  },
  {
    "path": ".gitignore",
    "content": "*.iml\n.gradle\n/local.properties\n/.idea/\n.DS_Store\n/build\n*/build/\n/captures\n.externalNativeBuild\n.cxx\nlocal.properties\n.idea\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nWe as members, contributors, and leaders pledge to make participation in our\ncommunity a harassment-free experience for everyone, regardless of age, body\nsize, visible or invisible disability, ethnicity, sex characteristics, gender\nidentity and expression, level of experience, education, socio-economic status,\nnationality, personal appearance, race, religion, or sexual identity\nand orientation.\n\nWe pledge to act and interact in ways that contribute to an open, welcoming,\ndiverse, inclusive, and healthy community.\n\n## Our Standards\n\nExamples of behavior that contributes to a positive environment for our\ncommunity include:\n\n* Demonstrating empathy and kindness toward other people\n* Being respectful of differing opinions, viewpoints, and experiences\n* Giving and gracefully accepting constructive feedback\n* Accepting responsibility and apologizing to those affected by our mistakes,\n  and learning from the experience\n* Focusing on what is best not just for us as individuals, but for the\n  overall community\n\nExamples of unacceptable behavior include:\n\n* The use of sexualized language or imagery, and sexual attention or\n  advances of any kind\n* Trolling, insulting or derogatory comments, and personal or political attacks\n* Public or private harassment\n* Publishing others' private information, such as a physical or email\n  address, without their explicit permission\n* Other conduct which could reasonably be considered inappropriate in a\n  professional setting\n\n## Enforcement Responsibilities\n\nCommunity leaders are responsible for clarifying and enforcing our standards of\nacceptable behavior and will take appropriate and fair corrective action in\nresponse to any behavior that they deem inappropriate, threatening, offensive,\nor harmful.\n\nCommunity leaders have the right and responsibility to remove, edit, or reject\ncomments, commits, code, wiki edits, issues, and other contributions that are\nnot aligned to this Code of Conduct, and will communicate reasons for moderation\ndecisions when appropriate.\n\n## Scope\n\nThis Code of Conduct applies within all community spaces, and also applies when\nan individual is officially representing the community in public spaces.\nExamples of representing our community include using an official e-mail address,\nposting via an official social media account, or acting as an appointed\nrepresentative at an online or offline event.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported to the community leaders responsible for enforcement at\n.\nAll complaints will be reviewed and investigated promptly and fairly.\n\nAll community leaders are obligated to respect the privacy and security of the\nreporter of any incident.\n\n## Enforcement Guidelines\n\nCommunity leaders will follow these Community Impact Guidelines in determining\nthe consequences for any action they deem in violation of this Code of Conduct:\n\n### 1. Correction\n\n**Community Impact**: Use of inappropriate language or other behavior deemed\nunprofessional or unwelcome in the community.\n\n**Consequence**: A private, written warning from community leaders, providing\nclarity around the nature of the violation and an explanation of why the\nbehavior was inappropriate. A public apology may be requested.\n\n### 2. Warning\n\n**Community Impact**: A violation through a single incident or series\nof actions.\n\n**Consequence**: A warning with consequences for continued behavior. No\ninteraction with the people involved, including unsolicited interaction with\nthose enforcing the Code of Conduct, for a specified period of time. This\nincludes avoiding interactions in community spaces as well as external channels\nlike social media. Violating these terms may lead to a temporary or\npermanent ban.\n\n### 3. Temporary Ban\n\n**Community Impact**: A serious violation of community standards, including\nsustained inappropriate behavior.\n\n**Consequence**: A temporary ban from any sort of interaction or public\ncommunication with the community for a specified period of time. No public or\nprivate interaction with the people involved, including unsolicited interaction\nwith those enforcing the Code of Conduct, is allowed during this period.\nViolating these terms may lead to a permanent ban.\n\n### 4. Permanent Ban\n\n**Community Impact**: Demonstrating a pattern of violation of community\nstandards, including sustained inappropriate behavior,  harassment of an\nindividual, or aggression toward or disparagement of classes of individuals.\n\n**Consequence**: A permanent ban from any sort of public interaction within\nthe community.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage],\nversion 2.0, available at\nhttps://www.contributor-covenant.org/version/2/0/code_of_conduct.html.\n\nCommunity Impact Guidelines were inspired by [Mozilla's code of conduct\nenforcement ladder](https://github.com/mozilla/diversity).\n\n[homepage]: https://www.contributor-covenant.org\n\nFor answers to common questions about this code of conduct, see the FAQ at\nhttps://www.contributor-covenant.org/faq. Translations are available at\nhttps://www.contributor-covenant.org/translations.\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "## Feeling Awesome! Thanks for thinking about this.\n\nYou can contribute us by filing issues, bugs and PRs. You can also take a look at active issues and fix them.\n\nIf you want to discuss on something then feel free to present your opinions, views or any other relevant comment on [discussions](https://github.com/PatilShreyas/permission-flow-android/discussions). \n\n### Code contribution\n\n- Open issue regarding proposed change.\n- If your proposed change is approved, Fork this repo and do changes.\n- Open PR against latest *development* branch. Add nice description in PR.\n- You're done!\n\n### Code contribution checklist\n\n- New code addition/deletion should not break existing flow of a system.\n- All tests should be passed.\n- Verify `./gradlew build` is passing before raising a PR.\n- Reformat code with Spotless `./gradlew spotlessApply` before raising a PR.\n"
  },
  {
    "path": "LICENSE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright 2022 Shreyas Patil\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "README.md",
    "content": "# Permission Flow for Android\n\nKnow about real-time state of a Android app Permissions with Kotlin Flow APIs. _Made with ❤️ for\nAndroid Developers_.\n\n[![Build](https://github.com/PatilShreyas/permission-flow-android/actions/workflows/build.yml/badge.svg)](https://github.com/PatilShreyas/permission-flow-android/actions/workflows/build.yml)\n[![Release](https://github.com/PatilShreyas/permission-flow-android/actions/workflows/release.yml/badge.svg)](https://github.com/PatilShreyas/permission-flow-android/actions/workflows/release.yml)\n[![codecov](https://codecov.io/gh/PatilShreyas/permission-flow-android/branch/main/graph/badge.svg?token=6TOHNLQDVW)](https://codecov.io/gh/PatilShreyas/permission-flow-android)\n[![Maven Central](https://img.shields.io/maven-central/v/dev.shreyaspatil.permission-flow/permission-flow-android?label=Maven%20Central&logo=android&style=flat-square)](https://search.maven.org/artifact/dev.shreyaspatil.permission-flow/permission-flow-android)\n[![GitHub](https://img.shields.io/github/license/PatilShreyas/permission-flow-android?label=License)](LICENSE)\n\n[![dokka](https://img.shields.io/badge/Dokka-Docs-blueviolet.svg?style=flat&logo=kotlin)](https://patilshreyas.github.io/permission-flow-android/docs/)\n[![kover](https://img.shields.io/badge/Kover-Coverage-blueviolet.svg?style=flat&logo=kotlin)](https://patilshreyas.github.io/permission-flow-android/coverageReport/)\n\n## 💡Introduction\n\nIn big projects, app is generally divided in several modules and in such cases, if any individual\nmodule is just a data module (_not having UI_) and need to know state of a permission, it's not\nthat easy. This library provides a way to know state of a permission throughout the app and\nfrom any layer of the application safely.\n\n_For example, you can listen for state of contacts permission in class where you'll instantly show\nlist of contacts when permission is granted._\n\nIt's a simple and easy to use library. Just Plug and Play.\n\n## 🚀 Implementation\n\nYou can check [/app](/app) directory which includes example application for demonstration.\n\n### 1. Gradle setup\n\nIn `build.gradle` of app module, include this dependency\n\n```gradle\ndependencies {\n    implementation \"dev.shreyaspatil.permission-flow:permission-flow-android:$version\"\n    \n    // For using in Jetpack Compose\n    implementation \"dev.shreyaspatil.permission-flow:permission-flow-compose:$version\"\n}\n```\n\n_You can find latest version and changelogs in the [releases](https://github.com/PatilShreyas/permission-flow-android/releases)_.\n\n### 2. Observing a Permission State\n\n#### 2.1 Observing Permission with `StateFlow`\nA permission state can be subscribed by retrieving `StateFlow<PermissionState>` or `StateFlow<MultiplePermissionState>` as follows:\n\n```kotlin\nval permissionFlow = PermissionFlow.getInstance()\n\n// Observe state of single permission\nsuspend fun observePermission() {\n    permissionFlow.getPermissionState(Manifest.permission.READ_CONTACTS).collect { state ->\n        if (state.isGranted) {\n            // Permission granted, access contacts.\n        } else if (state.isRationaleRequired == true) {\n            // Permission denied, but can be requested again\n        } else {\n            // Permission denied, and can't be requested again\n        }\n    }\n}\n\n// Observe state of multiple permissions\nsuspend fun observeMultiplePermissions() {\n    permissionFlow.getMultiplePermissionState(\n        Manifest.permission.READ_CONTACTS,\n        Manifest.permission.READ_SMS\n    ).collect { state ->\n        // All permission states\n        val allPermissions = state.permissions\n\n        // Check whether all permissions are granted\n        val allGranted = state.allGranted\n\n        // List of granted permissions\n        val grantedPermissions = state.grantedPermissions\n\n        // List of denied permissions\n        val deniedPermissions = state.deniedPermissions\n        \n        // List of permissions requiring rationale\n        val permissionsRequiringRationale = state.permissionsRequiringRationale\n    }\n}\n```\n\n#### 2.2 Observing permissions in Jetpack Compose\n\nState of a permission and state of multiple permissions can also be observed in Jetpack Compose application as follows:\n\n```kotlin\n@Composable\nfun ExampleSinglePermission() {\n    val state by rememberPermissionState(Manifest.permission.CAMERA)\n    if (state.isGranted) {\n        // Permission granted\n    } else if (state.isRationaleRequired == true) {\n        // Permission denied, but can be requested again\n    } else {\n        // Permission denied, and can't be requested again\n    }\n}\n\n@Composable\nfun ExampleMultiplePermission() {\n    val state by rememberMultiplePermissionState(\n        Manifest.permission.CAMERA,\n        Manifest.permission.ACCESS_FINE_LOCATION,\n        Manifest.permission.READ_CONTACTS\n    )\n\n    if (state.allGranted) {\n        // Render something\n    }\n\n    val grantedPermissions = state.grantedPermissions\n    // Do something with `grantedPermissions`\n\n    val deniedPermissions = state.deniedPermissions\n    // Do something with `deniedPermissions`\n    \n    val permissionsRequiringRationale = state.permissionsRequiringRationale\n    // Do something with `permissionsRequiringRationale`\n}\n```\n\n### 3. Requesting permission with PermissionFlow\n\nIt's necessary to use utilities provided by this library to request permissions so that whenever permission state\nchanges, this library takes care of notifying respective flows.\n\n#### 3.1 Request permission from Activity / Fragment\n\nUse [`registerForPermissionFlowRequestsResult()`](https://patilshreyas.github.io/permission-flow-android/docs/permission-flow/dev.shreyaspatil.permissionFlow.utils/register-for-permission-flow-requests-result.html) method to get `ActivityResultLauncher`\nand use `launch()` method to request for permission.\n\n```kotlin\nclass ContactsActivity : AppCompatActivity() {\n\n    private val permissionLauncher = registerForPermissionFlowRequestsResult()\n\n    private fun askContactsPermission() {\n        permissionLauncher.launch(Manifest.permission.READ_CONTACTS, ...)\n    }\n}\n```\n\n#### 3.2 Request permission in Jetpack Compose\n\nUse [`rememberPermissionFlowRequestLauncher()`](https://patilshreyas.github.io/permission-flow-android/docs/permission-flow-compose/dev.shreyaspatil.permissionflow.compose/remember-permission-flow-request-launcher.html) method to get `ManagedActivityResultLauncher`\nand use `launch()` method to request for permission.\n\n```kotlin\n@Composable\nfun Example() {\n    val permissionLauncher = rememberPermissionFlowRequestLauncher()\n\n    Button(onClick = { permissionLauncher.launch(android.Manifest.permission.CAMERA, ...) }) {\n        Text(\"Request Permissions\")\n    }\n}\n```\n\n### 4. Manually notifying permission state changes ⚠️\n\nIf you're not using `ActivityResultLauncher` APIs provided by this library then\nyou will ***not receive permission state change updates***. But there's a provision by which\nyou can help this library to know about permission state changes.\n\nUse [`PermissionFlow#notifyPermissionsChanged()`](https://patilshreyas.github.io/permission-flow-android/docs/permission-flow/dev.shreyaspatil.permissionFlow/-permission-flow/notify-permissions-changed.html) to notify the permission state changes\nfrom your manual implementations.\n\nFor example:\n\n```kotlin\nclass MyActivity: AppCompatActivity() {\n    private val permissionFlow = PermissionFlow.getInstance()\n\n    private val permissionLauncher = registerForActivityResult(RequestPermission()) { isGranted ->\n        permissionFlow.notifyPermissionsChanged(android.Manifest.permission.READ_CONTACTS)\n    }\n}\n```\n\n### 5. Manually Start / Stop Listening ⚠️\n\nThis library starts processing things lazily whenever `getPermissionState()` or `getMultiplePermissionState()` is called\nfor the first time. But this can be controlled with these methods:\n\n```kotlin\nfun doSomething() {\n    // Stops listening to the state changes of permissions throughout the application.\n    // This means the state of permission retrieved with [getMultiplePermissionState] method will not \n    // be updated after stopping listening. \n    permissionFlow.stopListening()\n\n    // Starts listening the changes of state of permissions after stopping listening\n    permissionFlow.startListening()\n}\n```\n\n### 6. What about Initialization? \n\nThis library automatically gets initialized with the App Startup library.\nIf you want to provide own coroutine dispatcher\n\n#### 6.1 Initialize **PermissionFlow** as follows (For example, in `Application` class)\n\n```kotlin\nclass MyApplication: Application() {\n    override fun onCreate() {\n        super.onCreate()\n        val permissionDispatcher = Executors.newFixedThreadPool(3).asCoroutineDispatcher()\n        PermissionFlow.init(this, permissionDispatcher)\n    }\n}\n```\n\n#### 6.2 Disable PermissionFlowInitializer in AndroidManifest.xml\n\nDisable auto initialization of library with default configuration using this:\n\n```xml\n<provider\n    android:name=\"androidx.startup.InitializationProvider\"\n    android:authorities=\"${applicationId}.androidx-startup\"\n    android:exported=\"false\"\n    tools:node=\"merge\">\n\n    <meta-data\n        android:name=\"dev.shreyaspatil.permissionFlow.initializer.PermissionFlowInitializer\"\n        android:value=\"androidx.startup\"\n        tools:node=\"remove\" />\n</provider>\n```\n\n\n## 📄 API Documentation\n\n[Visit the API documentation](https://patilshreyas.github.io/permission-flow-android/docs/) of this library to get more information in detail. This documentation is generated using [Dokka](https://github.com/Kotlin/dokka).\n\n## 📊 Test coverage report\n\n[Check the Test Coverage Report](https://patilshreyas.github.io/permission-flow-android/coverageReport/) of this library. This is generated using [Kover](https://github.com/Kotlin/kotlinx-kover).\n\n---\n\n## 🙋‍♂️ Contribute\n\nRead [contribution guidelines](CONTRIBUTING.md) for more information regarding contribution.\n\n## 💬 Discuss?\n\nHave any questions, doubts or want to present your opinions, views? You're always welcome. You can [start discussions](https://github.com/PatilShreyas/permission-flow-android/discussions).\n\n## 📝 License\n\n```\nCopyright 2022 Shreyas Patil\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\n\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\nhttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n```\n"
  },
  {
    "path": "app/.gitignore",
    "content": "/build"
  },
  {
    "path": "app/README.md",
    "content": "# Example\n\nhttps://github.com/PatilShreyas/permission-flow-android/assets/19620536/5ea5ac2c-24a6-4d16-87f8-1b5a0b8e708e\n\n"
  },
  {
    "path": "app/build.gradle",
    "content": "plugins {\n    id 'com.android.application'\n    id 'org.jetbrains.kotlin.android'\n    id 'org.jetbrains.kotlin.plugin.compose'\n}\n\nkotlin {\n    jvmToolchain(17)\n}\n\nandroid {\n    compileSdk 35\n\n    defaultConfig {\n        applicationId \"dev.shreyaspatil.permissionFlow.example\"\n        minSdk 21\n        versionCode 1\n        versionName \"1.0\"\n\n        testInstrumentationRunner \"androidx.test.runner.AndroidJUnitRunner\"\n    }\n\n    buildTypes {\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'\n        }\n    }\n\n    buildFeatures {\n        viewBinding true\n        compose true\n    }\n\n    packagingOptions {\n        resources {\n            excludes += '/META-INF/{AL2.0,LGPL2.1}'\n        }\n    }\n    namespace 'dev.shreyaspatil.permissionFlow.example'\n}\n\ndependencies {\n    // Library\n    implementation(project(\":permission-flow\"))\n    implementation(project(\":permission-flow-compose\"))\n\n    // Android\n    implementation 'androidx.core:core-ktx:1.15.0'\n    implementation 'androidx.fragment:fragment-ktx:1.8.6'\n    implementation \"androidx.appcompat:appcompat:$appCompatVersion\"\n    implementation 'com.google.android.material:material:1.12.0'\n    implementation 'androidx.constraintlayout:constraintlayout:2.2.1'\n\n    // Jetpack Compose\n    implementation platform(\"androidx.compose:compose-bom:$composeBomVersion\")\n    implementation \"androidx.activity:activity-compose:$activityVersion\"\n    implementation \"androidx.compose.material:material\"\n    implementation \"androidx.compose.runtime:runtime\"\n    implementation \"androidx.compose.animation:animation\"\n    implementation \"androidx.compose.ui:ui-tooling\"\n\n    // Lifecycle\n    def lifecycleVersion = '2.8.7'\n    implementation \"androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion\"\n    implementation \"androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion\"\n\n\n    // Coroutines\n    implementation(\"org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion\")\n    implementation(\"org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutinesVersion\")\n\n    // Testing\n    testImplementation 'junit:junit:4.13.2'\n    androidTestImplementation 'androidx.test.ext:junit:1.1.5'\n    androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'\n}"
  },
  {
    "path": "app/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile"
  },
  {
    "path": "app/src/androidTest/java/dev/shreyaspatil/permissionFlow/ExampleInstrumentedTest.kt",
    "content": "/**\n * Copyright 2022 Shreyas Patil\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage dev.shreyaspatil.permissionFlow\n\nimport androidx.test.ext.junit.runners.AndroidJUnit4\nimport androidx.test.platform.app.InstrumentationRegistry\nimport org.junit.Assert.assertEquals\nimport org.junit.Test\nimport org.junit.runner.RunWith\n\n/**\n * Instrumented test, which will execute on an Android device.\n *\n * See [testing documentation](http://d.android.com/tools/testing).\n */\n@RunWith(AndroidJUnit4::class)\nclass ExampleInstrumentedTest {\n    @Test\n    fun useAppContext() {\n        // Context of the app under test.\n        val appContext = InstrumentationRegistry.getInstrumentation().targetContext\n        assertEquals(\"dev.shreyaspatil.permiduct\", appContext.packageName)\n    }\n}\n"
  },
  {
    "path": "app/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <uses-permission android:name=\"android.permission.READ_CONTACTS\" />\n    <uses-permission android:name=\"android.permission.READ_EXTERNAL_STORAGE\" />\n    <uses-permission android:name=\"android.permission.READ_CALL_LOG\" />\n    <uses-permission android:name=\"android.permission.READ_PHONE_STATE\" />\n    <uses-permission android:name=\"android.permission.CAMERA\" />\n\n    <uses-feature\n        android:name=\"android.hardware.camera\"\n        android:required=\"false\" />\n\n    <application\n        android:allowBackup=\"true\"\n        android:dataExtractionRules=\"@xml/data_extraction_rules\"\n        android:fullBackupContent=\"@xml/backup_rules\"\n        android:icon=\"@mipmap/ic_launcher\"\n        android:label=\"@string/app_name\"\n        android:roundIcon=\"@mipmap/ic_launcher_round\"\n        android:supportsRtl=\"true\"\n        android:theme=\"@style/Theme.PermissionFlow\"\n        tools:targetApi=\"31\">\n        <activity\n            android:name=\".ui.MainActivity\"\n            android:exported=\"true\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n        </activity>\n        <activity android:name=\".ui.composePermission.ComposePermissionActivity\" />\n        <activity android:name=\".ui.multipermission.MultiPermissionActivity\" />\n        <activity android:name=\".ui.contacts.ContactsActivity\" />\n        <activity android:name=\".ui.fragment.ExampleFragmentActivity\" />\n    </application>\n\n</manifest>"
  },
  {
    "path": "app/src/main/java/dev/shreyaspatil/permissionFlow/example/data/ContactRepository.kt",
    "content": "/**\n * Copyright 2022 Shreyas Patil\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage dev.shreyaspatil.permissionFlow.example.data\n\nimport dev.shreyaspatil.permissionFlow.example.data.model.Contact\nimport kotlinx.coroutines.flow.Flow\n\ninterface ContactRepository {\n    val allContacts: Flow<List<Contact>>\n}\n"
  },
  {
    "path": "app/src/main/java/dev/shreyaspatil/permissionFlow/example/data/impl/AndroidDefaultContactRepository.kt",
    "content": "/**\n * Copyright 2022 Shreyas Patil\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage dev.shreyaspatil.permissionFlow.example.data.impl\n\nimport android.Manifest\nimport android.content.ContentResolver\nimport android.database.Cursor\nimport android.provider.ContactsContract\nimport androidx.core.database.getStringOrNull\nimport dev.shreyaspatil.permissionFlow.PermissionFlow\nimport dev.shreyaspatil.permissionFlow.example.data.ContactRepository\nimport dev.shreyaspatil.permissionFlow.example.data.model.Contact\nimport kotlinx.coroutines.CoroutineDispatcher\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.transform\nimport kotlinx.coroutines.withContext\n\nclass AndroidDefaultContactRepository(\n    private val contentResolver: ContentResolver,\n    private val permissionFlow: PermissionFlow = PermissionFlow.getInstance(),\n    private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO,\n) : ContactRepository {\n\n    override val allContacts: Flow<List<Contact>> =\n        permissionFlow.getPermissionState(Manifest.permission.READ_CONTACTS).transform { state ->\n            if (state.isGranted) {\n                emit(getContacts())\n            } else {\n                emit(emptyList())\n            }\n        }\n\n    private suspend fun getContacts(): List<Contact> =\n        withContext(ioDispatcher) {\n            buildList {\n                var cursor: Cursor? = null\n                try {\n                    val projection =\n                        arrayOf(\n                            ContactsContract.CommonDataKinds.Phone.CONTACT_ID,\n                            ContactsContract.CommonDataKinds.Phone.NUMBER,\n                            ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME,\n                            ContactsContract.CommonDataKinds.Phone.PHOTO_URI,\n                        )\n\n                    val order = \"${ContactsContract.CommonDataKinds.Phone.CONTACT_ID} ASC\"\n\n                    cursor =\n                        contentResolver.query(\n                            ContactsContract.CommonDataKinds.Phone.CONTENT_URI,\n                            projection,\n                            null,\n                            null,\n                            order,\n                        )\n                    if (cursor != null) {\n                        while (cursor.moveToNext()) {\n                            val contactId =\n                                cursor.getStringOrNull(\n                                    cursor.getColumnIndex(\n                                        ContactsContract.CommonDataKinds.Phone.CONTACT_ID),\n                                )\n                            val name =\n                                cursor.getStringOrNull(\n                                    cursor.getColumnIndex(\n                                        ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME),\n                                )\n                            val number =\n                                cursor.getStringOrNull(\n                                    cursor.getColumnIndex(\n                                        ContactsContract.CommonDataKinds.Phone.NUMBER),\n                                )\n\n                            if (contactId != null && name != null && number != null) {\n                                add(Contact(contactId, name, number))\n                            }\n                        }\n                    }\n                } finally {\n                    cursor?.close()\n                }\n            }\n        }\n}\n"
  },
  {
    "path": "app/src/main/java/dev/shreyaspatil/permissionFlow/example/data/model/Contact.kt",
    "content": "/**\n * Copyright 2022 Shreyas Patil\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage dev.shreyaspatil.permissionFlow.example.data.model\n\ndata class Contact(val id: String, val name: String, val number: String)\n"
  },
  {
    "path": "app/src/main/java/dev/shreyaspatil/permissionFlow/example/ui/MainActivity.kt",
    "content": "/**\n * Copyright 2022 Shreyas Patil\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage dev.shreyaspatil.permissionFlow.example.ui\n\nimport android.content.Intent\nimport android.os.Bundle\nimport androidx.activity.ComponentActivity\nimport androidx.appcompat.app.AppCompatActivity\nimport dev.shreyaspatil.permissionFlow.example.databinding.ActivityMainBinding\nimport dev.shreyaspatil.permissionFlow.example.ui.composePermission.ComposePermissionActivity\nimport dev.shreyaspatil.permissionFlow.example.ui.contacts.ContactsActivity\nimport dev.shreyaspatil.permissionFlow.example.ui.fragment.ExampleFragmentActivity\nimport dev.shreyaspatil.permissionFlow.example.ui.multipermission.MultiPermissionActivity\n\nclass MainActivity : AppCompatActivity() {\n    private lateinit var binding: ActivityMainBinding\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        binding = ActivityMainBinding.inflate(layoutInflater)\n        setContentView(binding.root)\n\n        with(binding) {\n            buttonContacts.setOnClickListener { launchScreen<ContactsActivity>() }\n            buttonMultiPermission.setOnClickListener { launchScreen<MultiPermissionActivity>() }\n            buttonComposeSample.setOnClickListener { launchScreen<ComposePermissionActivity>() }\n            buttonFragmentSample.setOnClickListener { launchScreen<ExampleFragmentActivity>() }\n        }\n    }\n\n    private inline fun <reified S : ComponentActivity> launchScreen() {\n        startActivity(Intent(this, S::class.java))\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/dev/shreyaspatil/permissionFlow/example/ui/composePermission/ComposePermissionActivity.kt",
    "content": "/**\n * Copyright 2022 Shreyas Patil\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage dev.shreyaspatil.permissionFlow.example.ui.composePermission\n\nimport android.os.Bundle\nimport androidx.activity.ComponentActivity\nimport androidx.activity.compose.setContent\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.material.Button\nimport androidx.compose.material.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.getValue\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.unit.dp\nimport dev.shreyaspatil.permissionflow.compose.rememberMultiplePermissionState\nimport dev.shreyaspatil.permissionflow.compose.rememberPermissionFlowRequestLauncher\n\n/**\n * The example activity which demonstrates the usage of PermissionFlow APIs in the Jetpack Compose\n */\nclass ComposePermissionActivity : ComponentActivity() {\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        setContent { MainScreen() }\n    }\n}\n\nprivate val permissions =\n    arrayOf(\n        android.Manifest.permission.CAMERA,\n        android.Manifest.permission.READ_EXTERNAL_STORAGE,\n        android.Manifest.permission.READ_CALL_LOG,\n        android.Manifest.permission.READ_CONTACTS,\n        android.Manifest.permission.READ_PHONE_STATE,\n    )\n\n@Composable\nfun MainScreen() {\n    val permissionLauncher = rememberPermissionFlowRequestLauncher()\n    val state by rememberMultiplePermissionState(*permissions)\n    // or use `rememberPermissionState()` to get the state of a single permission\n\n    Column(\n        modifier = Modifier.fillMaxSize().padding(16.dp),\n        verticalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterVertically),\n        horizontalAlignment = Alignment.CenterHorizontally,\n    ) {\n        Button(onClick = { permissionLauncher.launch(permissions) }) { Text(\"Request Permissions\") }\n\n        Column(modifier = Modifier.background(Color.Green).padding(16.dp)) {\n            Text(text = \"Granted Permissions:\")\n            Text(text = state.grantedPermissions.joinToString())\n        }\n\n        Column(modifier = Modifier.background(Color.Red).padding(16.dp)) {\n            Text(text = \"Denied Permissions:\")\n            Text(text = state.deniedPermissions.joinToString())\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/dev/shreyaspatil/permissionFlow/example/ui/contacts/ContactsActivity.kt",
    "content": "/**\n * Copyright 2022 Shreyas Patil\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage dev.shreyaspatil.permissionFlow.example.ui.contacts\n\nimport android.Manifest\nimport android.os.Bundle\nimport android.widget.Toast\nimport androidx.activity.viewModels\nimport androidx.appcompat.app.AppCompatActivity\nimport androidx.lifecycle.flowWithLifecycle\nimport androidx.lifecycle.lifecycleScope\nimport dev.shreyaspatil.permissionFlow.example.databinding.ActivityContactsBinding\nimport dev.shreyaspatil.permissionFlow.utils.launch\nimport dev.shreyaspatil.permissionFlow.utils.registerForPermissionFlowRequestsResult\nimport kotlinx.coroutines.flow.launchIn\nimport kotlinx.coroutines.flow.onEach\n\nclass ContactsActivity : AppCompatActivity() {\n\n    private lateinit var binding: ActivityContactsBinding\n    private val viewModel by\n        viewModels<ContactsViewModel> { ContactsViewModel.FactoryProvider(contentResolver).get() }\n\n    private val permissionLauncher = registerForPermissionFlowRequestsResult()\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        binding = ActivityContactsBinding.inflate(layoutInflater)\n        setContentView(binding.root)\n        observeStates()\n    }\n\n    private fun render(state: ContactsUiEvents) {\n        when (state) {\n            ContactsUiEvents.ContactPermissionGranted -> {\n                binding.permissionStatusText.apply {\n                    text = \"Contacts Permission Granted!\"\n                    setOnClickListener(null)\n                }\n            }\n            ContactsUiEvents.ContactPermissionNotGranted -> {\n                binding.permissionStatusText.apply {\n                    text = \"Click here to ask for contacts permission\"\n                    setOnClickListener { askContactsPermission() }\n                }\n            }\n            is ContactsUiEvents.ContactsAvailable -> {\n                binding.contactsDataText.text =\n                    state.contacts.joinToString(\"\\n\") { \"${it.id}. ${it.name} (${it.number})\" }\n            }\n            is ContactsUiEvents.Failure -> {\n                Toast.makeText(this, state.error, Toast.LENGTH_SHORT).show()\n            }\n        }\n    }\n\n    private fun askContactsPermission() {\n        permissionLauncher.launch(Manifest.permission.READ_CONTACTS)\n    }\n\n    private fun observeStates() {\n        viewModel.state.flowWithLifecycle(lifecycle).onEach { render(it) }.launchIn(lifecycleScope)\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/dev/shreyaspatil/permissionFlow/example/ui/contacts/ContactsUiEvents.kt",
    "content": "/**\n * Copyright 2022 Shreyas Patil\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage dev.shreyaspatil.permissionFlow.example.ui.contacts\n\nimport dev.shreyaspatil.permissionFlow.example.data.model.Contact\n\nsealed class ContactsUiEvents {\n    object ContactPermissionNotGranted : ContactsUiEvents()\n\n    object ContactPermissionGranted : ContactsUiEvents()\n\n    data class ContactsAvailable(val contacts: List<Contact>) : ContactsUiEvents()\n\n    data class Failure(val error: String) : ContactsUiEvents()\n}\n"
  },
  {
    "path": "app/src/main/java/dev/shreyaspatil/permissionFlow/example/ui/contacts/ContactsViewModel.kt",
    "content": "/**\n * Copyright 2022 Shreyas Patil\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage dev.shreyaspatil.permissionFlow.example.ui.contacts\n\nimport android.Manifest\nimport android.content.ContentResolver\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.ViewModelProvider\nimport androidx.lifecycle.viewModelScope\nimport androidx.lifecycle.viewmodel.ViewModelInitializer\nimport dev.shreyaspatil.permissionFlow.PermissionFlow\nimport dev.shreyaspatil.permissionFlow.example.data.ContactRepository\nimport dev.shreyaspatil.permissionFlow.example.data.impl.AndroidDefaultContactRepository\nimport kotlinx.coroutines.channels.Channel\nimport kotlinx.coroutines.channels.Channel.Factory.BUFFERED\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.catch\nimport kotlinx.coroutines.flow.receiveAsFlow\nimport kotlinx.coroutines.launch\n\nclass ContactsViewModel(\n    private val repository: ContactRepository,\n    private val permissionFlow: PermissionFlow = PermissionFlow.getInstance(),\n) : ViewModel() {\n    private val uiEvents = Channel<ContactsUiEvents>(capacity = BUFFERED)\n    val state: Flow<ContactsUiEvents> = uiEvents.receiveAsFlow()\n\n    init {\n        observeContacts()\n        observeContactsPermission()\n    }\n\n    private fun observeContacts() {\n        viewModelScope.launch {\n            repository.allContacts\n                .catch { setNextState(ContactsUiEvents.Failure(it.message ?: \"Error occurred\")) }\n                .collect { contacts -> setNextState(ContactsUiEvents.ContactsAvailable(contacts)) }\n        }\n    }\n\n    private fun observeContactsPermission() {\n        viewModelScope.launch {\n            permissionFlow.getPermissionState(Manifest.permission.READ_CONTACTS).collect { state ->\n                if (state.isGranted) {\n                    setNextState(ContactsUiEvents.ContactPermissionGranted)\n                } else {\n                    setNextState(ContactsUiEvents.ContactPermissionNotGranted)\n                }\n            }\n        }\n    }\n\n    private fun setNextState(nextState: ContactsUiEvents) {\n        uiEvents.trySend(nextState)\n    }\n\n    class FactoryProvider(private val contentResolver: ContentResolver) {\n        fun get(): ViewModelProvider.Factory {\n            val initializer =\n                ViewModelInitializer(ContactsViewModel::class.java) {\n                    ContactsViewModel(AndroidDefaultContactRepository(contentResolver))\n                }\n            return ViewModelProvider.Factory.from(initializer)\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/dev/shreyaspatil/permissionFlow/example/ui/fragment/ExampleFragment.kt",
    "content": "/**\n * Copyright 2022 Shreyas Patil\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage dev.shreyaspatil.permissionFlow.example.ui.fragment\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.core.view.isVisible\nimport androidx.fragment.app.Fragment\nimport androidx.lifecycle.lifecycleScope\nimport dev.shreyaspatil.permissionFlow.PermissionFlow\nimport dev.shreyaspatil.permissionFlow.example.databinding.ViewFragmentExampleBinding\nimport dev.shreyaspatil.permissionFlow.utils.registerForPermissionFlowRequestsResult\nimport kotlinx.coroutines.launch\n\n@Suppress(\"ktlint:standard:property-naming\")\nclass ExampleFragment : Fragment() {\n\n    private var _binding: ViewFragmentExampleBinding? = null\n    private val binding: ViewFragmentExampleBinding\n        get() = _binding!!\n\n    private val permissions =\n        arrayOf(\n            android.Manifest.permission.CAMERA,\n            android.Manifest.permission.READ_CALL_LOG,\n            android.Manifest.permission.READ_CONTACTS,\n            android.Manifest.permission.READ_PHONE_STATE,\n        )\n\n    private val permissionFlow = PermissionFlow.getInstance()\n\n    private val permissionLauncher = registerForPermissionFlowRequestsResult()\n\n    override fun onCreateView(\n        inflater: LayoutInflater,\n        container: ViewGroup?,\n        savedInstanceState: Bundle?,\n    ): View {\n        _binding = ViewFragmentExampleBinding.inflate(inflater, null, false)\n        return binding.root\n    }\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n        initView()\n        observePermissions()\n    }\n\n    private fun initView() {\n        binding.button.setOnClickListener { permissionLauncher.launch(permissions) }\n    }\n\n    private fun observePermissions() {\n        viewLifecycleOwner.lifecycleScope.launch {\n            permissionFlow.getMultiplePermissionState(*permissions).collect {\n                val grantedPermissionsText =\n                    it.grantedPermissions.joinToString(\n                        separator = \"\\n\",\n                        prefix = \"Granted Permissions:\\n\",\n                    )\n                val deniedPermissionsText =\n                    it.deniedPermissions.joinToString(\n                        separator = \"\\n\",\n                        prefix = \"Denied Permissions:\\n\",\n                    )\n\n                binding.grantedPermissionsText.text = grantedPermissionsText\n                binding.deniedPermissionsText.text = deniedPermissionsText\n\n                binding.button.isVisible = !it.allGranted\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/dev/shreyaspatil/permissionFlow/example/ui/fragment/ExampleFragmentActivity.kt",
    "content": "/**\n * Copyright 2022 Shreyas Patil\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage dev.shreyaspatil.permissionFlow.example.ui.fragment\n\nimport android.os.Bundle\nimport android.widget.FrameLayout\nimport androidx.appcompat.app.AppCompatActivity\nimport dev.shreyaspatil.permissionFlow.example.R\n\nclass ExampleFragmentActivity : AppCompatActivity() {\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        setContentView(R.layout.activity_fragment_example)\n        findViewById<FrameLayout>(R.id.frameLayout).let { frameLayout ->\n            supportFragmentManager\n                .beginTransaction()\n                .replace(frameLayout.id, ExampleFragment())\n                .commit()\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/dev/shreyaspatil/permissionFlow/example/ui/multipermission/MultiPermissionActivity.kt",
    "content": "/**\n * Copyright 2022 Shreyas Patil\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage dev.shreyaspatil.permissionFlow.example.ui.multipermission\n\nimport android.os.Bundle\nimport android.widget.Toast\nimport androidx.appcompat.app.AppCompatActivity\nimport androidx.lifecycle.flowWithLifecycle\nimport androidx.lifecycle.lifecycleScope\nimport dev.shreyaspatil.permissionFlow.MultiplePermissionState\nimport dev.shreyaspatil.permissionFlow.PermissionFlow\nimport dev.shreyaspatil.permissionFlow.example.databinding.ActivityMultipermissionBinding\nimport dev.shreyaspatil.permissionFlow.utils.registerForPermissionFlowRequestsResult\nimport kotlinx.coroutines.flow.launchIn\nimport kotlinx.coroutines.flow.onEach\n\n/**\n * This example activity shows how to use [PermissionFlow] to request multiple permissions at once\n * and observe multiple permissions at once.\n */\nclass MultiPermissionActivity : AppCompatActivity() {\n    private lateinit var binding: ActivityMultipermissionBinding\n\n    private val permissionFlow = PermissionFlow.getInstance()\n\n    private val permissionLauncher = registerForPermissionFlowRequestsResult()\n\n    private val permissions =\n        arrayOf(\n            android.Manifest.permission.CAMERA,\n            android.Manifest.permission.READ_EXTERNAL_STORAGE,\n            android.Manifest.permission.READ_CALL_LOG,\n            android.Manifest.permission.READ_CONTACTS,\n            android.Manifest.permission.READ_PHONE_STATE,\n        )\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        binding = ActivityMultipermissionBinding.inflate(layoutInflater)\n        setContentView(binding.root)\n\n        initViews()\n        observePermissions()\n    }\n\n    private fun initViews() {\n        binding.buttonAskPermission.setOnClickListener { permissionLauncher.launch(permissions) }\n    }\n\n    private fun observePermissions() {\n        permissionFlow\n            .getMultiplePermissionState(\n                *permissions) // or use `getPermissionState()` for observing a single permission\n            .flowWithLifecycle(lifecycle)\n            .onEach { onPermissionStateChange(it) }\n            .launchIn(lifecycleScope)\n    }\n\n    private fun onPermissionStateChange(state: MultiplePermissionState) {\n        if (state.allGranted) {\n            Toast.makeText(this, \"All permissions are granted!\", Toast.LENGTH_SHORT).show()\n        }\n\n        binding.textViewGrantedPermissions.text =\n            \"Granted permissions: ${state.grantedPermissions.joinToStringByNewLine()}\"\n        binding.textViewDeniedPermissions.text =\n            \"Denied permissions: ${state.deniedPermissions.joinToStringByNewLine()}\"\n    }\n\n    private fun List<String>.joinToStringByNewLine(): String {\n        return joinToString(prefix = \"\\n\", postfix = \"\\n\", separator = \",\\n\")\n    }\n}\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_launcher_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"108dp\"\n    android:height=\"108dp\"\n    android:viewportWidth=\"108\"\n    android:viewportHeight=\"108\">\n    <path\n        android:fillColor=\"#3DDC84\"\n        android:pathData=\"M0,0h108v108h-108z\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M9,0L9,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,0L19,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M29,0L29,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M39,0L39,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M49,0L49,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M59,0L59,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M69,0L69,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M79,0L79,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M89,0L89,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M99,0L99,108\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,9L108,9\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,19L108,19\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,29L108,29\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,39L108,39\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,49L108,49\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,59L108,59\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,69L108,69\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,79L108,79\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,89L108,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M0,99L108,99\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,29L89,29\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,39L89,39\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,49L89,49\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,59L89,59\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,69L89,69\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M19,79L89,79\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M29,19L29,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M39,19L39,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M49,19L49,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M59,19L59,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M69,19L69,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M79,19L79,89\"\n        android:strokeWidth=\"0.8\"\n        android:strokeColor=\"#33FFFFFF\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable-v24/ic_launcher_foreground.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:aapt=\"http://schemas.android.com/aapt\"\n    android:width=\"108dp\"\n    android:height=\"108dp\"\n    android:viewportWidth=\"108\"\n    android:viewportHeight=\"108\">\n    <path android:pathData=\"M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z\">\n        <aapt:attr name=\"android:fillColor\">\n            <gradient\n                android:endX=\"85.84757\"\n                android:endY=\"92.4963\"\n                android:startX=\"42.9492\"\n                android:startY=\"49.59793\"\n                android:type=\"linear\">\n                <item\n                    android:color=\"#44000000\"\n                    android:offset=\"0.0\" />\n                <item\n                    android:color=\"#00000000\"\n                    android:offset=\"1.0\" />\n            </gradient>\n        </aapt:attr>\n    </path>\n    <path\n        android:fillColor=\"#FFFFFF\"\n        android:fillType=\"nonZero\"\n        android:pathData=\"M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z\"\n        android:strokeWidth=\"1\"\n        android:strokeColor=\"#00000000\" />\n</vector>"
  },
  {
    "path": "app/src/main/res/layout/activity_contacts.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    tools:context=\"dev.shreyaspatil.permissionFlow.example.ui.contacts.ContactsActivity\">\n\n    <TextView\n        android:id=\"@+id/permissionStatusText\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"16dp\"\n        android:textColor=\"@android:color/black\"\n        android:textSize=\"16sp\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        tools:text=\"Permission Granted\" />\n\n    <TextView\n        android:id=\"@+id/contactsDataText\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"0dp\"\n        android:layout_marginStart=\"16dp\"\n        android:layout_marginTop=\"8dp\"\n        android:layout_marginEnd=\"16dp\"\n        android:layout_marginBottom=\"16dp\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@+id/permissionStatusText\"\n        tools:text=\"Contacts Data\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "app/src/main/res/layout/activity_fragment_example.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <FrameLayout\n        android:id=\"@+id/frameLayout\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"/>\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "app/src/main/res/layout/activity_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:orientation=\"vertical\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:gravity=\"center\">\n\n    <Button\n        android:id=\"@+id/buttonContacts\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"Observing Contact Permission\"\n        tools:ignore=\"HardcodedText\" />\n\n    <Button\n        android:id=\"@+id/buttonMultiPermission\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"Observing Multiple Permissions\"\n        tools:ignore=\"HardcodedText\" />\n\n    <Button\n        android:id=\"@+id/buttonComposeSample\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"Observing permissions in Compose\"\n        tools:ignore=\"HardcodedText\" />\n\n    <Button\n        android:id=\"@+id/buttonFragmentSample\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"Fragment Example\"\n        tools:ignore=\"HardcodedText\" />\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/activity_multipermission.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <Button\n        android:id=\"@+id/buttonAskPermission\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"16dp\"\n        android:text=\"Request Permissions\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:layout_constraintVertical_bias=\"0.0\" />\n\n    <TextView\n        android:id=\"@+id/textViewGrantedPermissions\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"8dp\"\n        android:layout_marginTop=\"16dp\"\n        android:layout_marginEnd=\"8dp\"\n        android:textColor=\"@android:color/holo_green_dark\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@+id/buttonAskPermission\"\n        tools:text=\"Granted Permissions\" />\n\n    <TextView\n        android:id=\"@+id/textViewDeniedPermissions\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"8dp\"\n        android:layout_marginTop=\"8dp\"\n        android:layout_marginEnd=\"8dp\"\n        android:textColor=\"@android:color/holo_red_dark\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@+id/textViewGrantedPermissions\"\n        tools:text=\"Denied Permisisons\" />\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "app/src/main/res/layout/view_fragment_example.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:padding=\"16dp\">\n\n    <Button\n        android:id=\"@+id/button\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"16dp\"\n        android:layout_marginTop=\"16dp\"\n        android:layout_marginEnd=\"16dp\"\n        android:text=\"Request Permissions\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\" />\n\n    <TextView\n        android:id=\"@+id/grantedPermissionsText\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"16dp\"\n        android:layout_marginTop=\"16dp\"\n        android:layout_marginEnd=\"16dp\"\n        android:text=\"Granted Permissions:\"\n        android:textColor=\"#009688\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@+id/button\" />\n\n    <TextView\n        android:id=\"@+id/deniedPermissionsText\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"16dp\"\n        android:layout_marginTop=\"16dp\"\n        android:layout_marginEnd=\"16dp\"\n        android:text=\"Denied Permissions:\"\n        android:textColor=\"#E91E63\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@+id/grantedPermissionsText\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <background android:drawable=\"@drawable/ic_launcher_background\" />\n    <foreground android:drawable=\"@drawable/ic_launcher_foreground\" />\n</adaptive-icon>"
  },
  {
    "path": "app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <background android:drawable=\"@drawable/ic_launcher_background\" />\n    <foreground android:drawable=\"@drawable/ic_launcher_foreground\" />\n</adaptive-icon>"
  },
  {
    "path": "app/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"purple_200\">#FFBB86FC</color>\n    <color name=\"purple_500\">#FF6200EE</color>\n    <color name=\"purple_700\">#FF3700B3</color>\n    <color name=\"teal_200\">#FF03DAC5</color>\n    <color name=\"teal_700\">#FF018786</color>\n    <color name=\"black\">#FF000000</color>\n    <color name=\"white\">#FFFFFFFF</color>\n</resources>"
  },
  {
    "path": "app/src/main/res/values/strings.xml",
    "content": "<resources>\n    <string name=\"app_name\">PermissionFlow</string>\n</resources>"
  },
  {
    "path": "app/src/main/res/values/themes.xml",
    "content": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n    <!-- Base application theme. -->\n    <style name=\"Theme.PermissionFlow\" parent=\"Theme.MaterialComponents.DayNight.DarkActionBar\">\n        <!-- Primary brand color. -->\n        <item name=\"colorPrimary\">@color/purple_500</item>\n        <item name=\"colorPrimaryVariant\">@color/purple_700</item>\n        <item name=\"colorOnPrimary\">@color/white</item>\n        <!-- Secondary brand color. -->\n        <item name=\"colorSecondary\">@color/teal_200</item>\n        <item name=\"colorSecondaryVariant\">@color/teal_700</item>\n        <item name=\"colorOnSecondary\">@color/black</item>\n        <!-- Status bar color. -->\n        <item name=\"android:statusBarColor\" tools:targetApi=\"l\">?attr/colorPrimaryVariant</item>\n        <!-- Customize your theme here. -->\n    </style>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-night/themes.xml",
    "content": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n    <!-- Base application theme. -->\n    <style name=\"Theme.PermissionFlow\" parent=\"Theme.MaterialComponents.DayNight.DarkActionBar\">\n        <!-- Primary brand color. -->\n        <item name=\"colorPrimary\">@color/purple_200</item>\n        <item name=\"colorPrimaryVariant\">@color/purple_700</item>\n        <item name=\"colorOnPrimary\">@color/black</item>\n        <!-- Secondary brand color. -->\n        <item name=\"colorSecondary\">@color/teal_200</item>\n        <item name=\"colorSecondaryVariant\">@color/teal_200</item>\n        <item name=\"colorOnSecondary\">@color/black</item>\n        <!-- Status bar color. -->\n        <item name=\"android:statusBarColor\" tools:targetApi=\"l\">?attr/colorPrimaryVariant</item>\n        <!-- Customize your theme here. -->\n    </style>\n</resources>"
  },
  {
    "path": "app/src/main/res/xml/backup_rules.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n   Sample backup rules file; uncomment and customize as necessary.\n   See https://developer.android.com/guide/topics/data/autobackup\n   for details.\n   Note: This file is ignored for devices older that API 31\n   See https://developer.android.com/about/versions/12/backup-restore\n-->\n<full-backup-content>\n    <!--\n   <include domain=\"sharedpref\" path=\".\"/>\n   <exclude domain=\"sharedpref\" path=\"device.xml\"/>\n-->\n</full-backup-content>"
  },
  {
    "path": "app/src/main/res/xml/data_extraction_rules.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n   Sample data extraction rules file; uncomment and customize as necessary.\n   See https://developer.android.com/about/versions/12/backup-restore#xml-changes\n   for details.\n-->\n<data-extraction-rules>\n    <cloud-backup>\n        <!-- TODO: Use <include> and <exclude> to control what is backed up.\n        <include .../>\n        <exclude .../>\n        -->\n    </cloud-backup>\n    <!--\n    <device-transfer>\n        <include .../>\n        <exclude .../>\n    </device-transfer>\n    -->\n</data-extraction-rules>"
  },
  {
    "path": "app/src/test/java/dev/shreyaspatil/permissionFlow/ExampleUnitTest.kt",
    "content": "/**\n * Copyright 2022 Shreyas Patil\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage dev.shreyaspatil.permissionFlow\n\nimport org.junit.Assert.assertEquals\nimport org.junit.Test\n\n/**\n * Example local unit test, which will execute on the development machine (host).\n *\n * See [testing documentation](http://d.android.com/tools/testing).\n */\nclass ExampleUnitTest {\n    @Test\n    fun addition_isCorrect() {\n        assertEquals(4, 2 + 2)\n    }\n}\n"
  },
  {
    "path": "build.gradle",
    "content": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\nbuildscript {\n    ext {\n        agpVersion = '8.9.0'\n        mavenPublishVersion = '0.31.0'\n        kotlinVersion = '2.1.10'\n        dokkaVersion = '2.0.0'\n        composeBomVersion = '2025.02.00'\n        activityVersion = '1.10.1'\n        coroutinesVersion = '1.10.0'\n        appCompatVersion = '1.7.0'\n        jUnitVersion = '4.13.2'\n        robolectricVersion = \"4.12.2\"\n        mockkVersion = '1.13.17'\n        turbineVersion = '1.1.0'\n        androidxCoreTestingVersion = '1.5.0'\n        spotlessVersion = '6.25.0'\n        koverVersion = \"0.5.1\"\n        startupVersion = \"1.2.0\"\n    }\n}\n\nplugins {\n    id 'com.android.application' version \"$agpVersion\" apply false\n    id 'com.android.library' version \"$agpVersion\" apply false\n    id 'org.jetbrains.kotlin.android' version \"$kotlinVersion\" apply false\n    id 'org.jetbrains.kotlin.plugin.compose' version \"$kotlinVersion\" apply false\n    id 'org.jetbrains.dokka' version \"$dokkaVersion\"\n    id 'com.diffplug.spotless' version \"$spotlessVersion\" apply false\n    id(\"org.jetbrains.kotlinx.kover\") version \"$koverVersion\" apply false\n    id(\"com.vanniktech.maven.publish\") version \"$mavenPublishVersion\" apply false\n}\n\nsubprojects {\n    apply plugin: 'com.diffplug.spotless'\n    spotless {\n        kotlin {\n            target '**/*.kt'\n            targetExclude(\"$buildDir/**/*.kt\")\n            targetExclude('bin/**/*.kt')\n\n            ktfmt().dropboxStyle()\n            licenseHeaderFile rootProject.file('spotless/copyright.kt')\n        }\n    }\n}\n\ntask clean(type: Delete) {\n    delete rootProject.buildDir\n}\n\napply plugin: 'org.jetbrains.dokka'\n\ntasks.dokkaHtmlMultiModule.configure {\n    outputDirectory.set(project.mkdir(\"build/docs\"))\n}"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "content": "#Sat Mar 08 22:38:14 IST 2025\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-8.11.1-bin.zip\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "gradle.properties",
    "content": "org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8\norg.gradle.parallel=true\norg.gradle.configureondemand=true\nandroid.useAndroidX=true\nandroid.nonFinalResIds=false\nandroid.nonTransitiveRClass=true\nkotlin.code.style=official\n\n# Library configuration\n\nSONATYPE_HOST=DEFAULT\nRELEASE_SIGNING_ENABLED=true\nSONATYPE_AUTOMATIC_RELEASE=true\n\nGROUP=dev.shreyaspatil.permission-flow\nVERSION_NAME=2.1.0\n\nPOM_INCEPTION_YEAR=2022\nPOM_URL=https://github.com/PatilShreyas/permission-flow-android/\n\nPOM_LICENSE_NAME=The Apache Software License, Version 2.0\nPOM_LICENSE_URL=https://www.apache.org/licenses/LICENSE-2.0.txt\nPOM_LICENSE_DIST=repo\n\nPOM_SCM_URL=https://github.com/PatilShreyas/permission-flow-android/\nPOM_SCM_CONNECTION=scm:git:git://github.com/PatilShreyas/permission-flow-android.git\nPOM_SCM_DEV_CONNECTION=scm:git:ssh://git@github.com/PatilShreyas/permission-flow-android.git\n\nPOM_DEVELOPER_ID=PatilShreyas\nPOM_DEVELOPER_NAME=Shreyas Patil\nPOM_DEVELOPER_URL=https://github.com/PatilShreyas/\n"
  },
  {
    "path": "gradlew",
    "content": "#!/usr/bin/env sh\n\n#\n# Copyright 2015 the original author or authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >/dev/null\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >/dev/null\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS='\"-Xmx64m\" \"-Xms64m\"'\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn () {\n    echo \"$*\"\n}\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\n  NONSTOP* )\n    nonstop=true\n    ;;\nesac\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n        JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=\"java\"\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" -a \"$nonstop\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin or MSYS, switch paths to Windows format before running java\nif [ \"$cygwin\" = \"true\" -o \"$msys\" = \"true\" ] ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n\n    JAVACMD=`cygpath --unix \"$JAVACMD\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|egrep -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|egrep -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=`expr $i + 1`\n    done\n    case $i in\n        0) set -- ;;\n        1) set -- \"$args0\" ;;\n        2) set -- \"$args0\" \"$args1\" ;;\n        3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Escape application args\nsave () {\n    for i do printf %s\\\\n \"$i\" | sed \"s/'/'\\\\\\\\''/g;1s/^/'/;\\$s/\\$/' \\\\\\\\/\" ; done\n    echo \" \"\n}\nAPP_ARGS=`save \"$@\"`\n\n# Collect all arguments for the java command, following the shell quoting and substitution rules\neval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS \"\\\"-Dorg.gradle.appname=$APP_BASE_NAME\\\"\" -classpath \"\\\"$CLASSPATH\\\"\" org.gradle.wrapper.GradleWrapperMain \"$APP_ARGS\"\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "gradlew.bat",
    "content": "@rem\n@rem Copyright 2015 the original author or authors.\n@rem\n@rem Licensed under the Apache License, Version 2.0 (the \"License\");\n@rem you may not use this file except in compliance with the License.\n@rem You may obtain a copy of the License at\n@rem\n@rem      https://www.apache.org/licenses/LICENSE-2.0\n@rem\n@rem Unless required by applicable law or agreed to in writing, software\n@rem distributed under the License is distributed on an \"AS IS\" BASIS,\n@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n@rem See the License for the specific language governing permissions and\n@rem limitations under the License.\n@rem\n\n@if \"%DEBUG%\" == \"\" @echo off\n@rem ##########################################################################\n@rem\n@rem  Gradle startup script for Windows\n@rem\n@rem ##########################################################################\n\n@rem Set local scope for the variables with windows NT shell\nif \"%OS%\"==\"Windows_NT\" setlocal\n\nset DIRNAME=%~dp0\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\nset APP_BASE_NAME=%~n0\nset APP_HOME=%DIRNAME%\n\n@rem Resolve any \".\" and \"..\" in APP_HOME to make it shorter.\nfor %%i in (\"%APP_HOME%\") do set APP_HOME=%%~fi\n\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nset DEFAULT_JVM_OPTS=\"-Xmx64m\" \"-Xms64m\"\n\n@rem Find java.exe\nif defined JAVA_HOME goto findJavaFromJavaHome\n\nset JAVA_EXE=java.exe\n%JAVA_EXE% -version >NUL 2>&1\nif \"%ERRORLEVEL%\" == \"0\" goto execute\n\necho.\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:findJavaFromJavaHome\nset JAVA_HOME=%JAVA_HOME:\"=%\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\n\nif exist \"%JAVA_EXE%\" goto execute\n\necho.\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:execute\n@rem Setup the command line\n\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\n\n\n@rem Execute Gradle\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %*\n\n:end\n@rem End local scope for the variables with windows NT shell\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\n\n:fail\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\nrem the _cmd.exe /c_ return code!\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\nexit /b 1\n\n:mainEnd\nif \"%OS%\"==\"Windows_NT\" endlocal\n\n:omega\n"
  },
  {
    "path": "permission-flow/.gitignore",
    "content": "/build"
  },
  {
    "path": "permission-flow/build.gradle",
    "content": "plugins {\n    id 'com.android.library'\n    id 'org.jetbrains.kotlin.android'\n    id 'org.jetbrains.kotlinx.kover'\n    id 'com.vanniktech.maven.publish'\n    id 'org.jetbrains.dokka'\n}\n\nkotlin {\n    jvmToolchain(17)\n}\n\nandroid {\n    compileSdk 35\n\n    defaultConfig {\n        minSdk 21\n\n        testInstrumentationRunner \"androidx.test.runner.AndroidJUnitRunner\"\n        consumerProguardFiles \"consumer-rules.pro\"\n    }\n\n    buildTypes {\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'\n        }\n    }\n\n    buildFeatures {\n        buildConfig false\n    }\n\n    testOptions {\n        unitTests.all {\n            if (name == \"testDebugUnitTest\") {\n                kover {\n                    disabled = false\n                    binaryReportFile.set(file(\"$buildDir/coverageReport/coverageReport.bin\"))\n                }\n            }\n        }\n    }\n    namespace 'dev.shreyaspatil.permissionFlow'\n}\n\ndependencies {\n    // Android\n    implementation \"androidx.appcompat:appcompat:$appCompatVersion\"\n    implementation \"androidx.startup:startup-runtime:$startupVersion\"\n\n    // Coroutines\n    implementation \"org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion\"\n\n    // Testing\n    testImplementation \"junit:junit:$jUnitVersion\"\n    testImplementation \"androidx.test:core:$androidxCoreTestingVersion\"\n    testImplementation(\"org.robolectric:robolectric:$robolectricVersion\")\n    testImplementation \"org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutinesVersion\"\n    testImplementation \"app.cash.turbine:turbine:$turbineVersion\"\n    testImplementation \"io.mockk:mockk:$mockkVersion\"\n    androidTestImplementation 'androidx.test.ext:junit:1.1.5'\n}\n\ntasks.koverHtmlReport {\n    enabled = true\n    htmlReportDir.set(layout.buildDirectory.dir(\"coverageReport/html\"))\n}\n\ntasks.koverXmlReport {\n    enabled = true\n    xmlReportFile.set(layout.buildDirectory.file(\"coverageReport/report.xml\"))\n}\n\ndokkaHtml.configure {\n    dokkaSourceSets {\n        named(\"main\") {\n            noAndroidSdkLink.set(false)\n        }\n    }\n}"
  },
  {
    "path": "permission-flow/consumer-rules.pro",
    "content": ""
  },
  {
    "path": "permission-flow/gradle.properties",
    "content": "POM_ARTIFACT_ID=permission-flow-android\nPOM_NAME=Permission Flow for Android\nPOM_DESCRIPTION=Know about real-time state of a Android app Permissions with Kotlin Flow APIs."
  },
  {
    "path": "permission-flow/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile"
  },
  {
    "path": "permission-flow/src/androidTest/java/dev/shreyaspatil/permissionFlow/ExampleInstrumentedTest.kt",
    "content": "/**\n * Copyright 2022 Shreyas Patil\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage dev.shreyaspatil.permissionFlow\n\nimport androidx.test.ext.junit.runners.AndroidJUnit4\nimport androidx.test.platform.app.InstrumentationRegistry\nimport org.junit.Assert.assertEquals\nimport org.junit.Test\nimport org.junit.runner.RunWith\n\n/**\n * Instrumented test, which will execute on an Android device.\n *\n * See [testing documentation](http://d.android.com/tools/testing).\n */\n@RunWith(AndroidJUnit4::class)\nclass ExampleInstrumentedTest {\n    @Test\n    fun useAppContext() {\n        // Context of the app under test.\n        val appContext = InstrumentationRegistry.getInstrumentation().targetContext\n        assertEquals(\"dev.shreyaspatil.permiduct.test\", appContext.packageName)\n    }\n}\n"
  },
  {
    "path": "permission-flow/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <application>\n        <provider\n            android:name=\"androidx.startup.InitializationProvider\"\n            android:authorities=\"${applicationId}.androidx-startup\"\n            android:exported=\"false\"\n            tools:node=\"merge\">\n\n            <meta-data\n                android:name=\"dev.shreyaspatil.permissionFlow.initializer.PermissionFlowInitializer\"\n                android:value=\"androidx.startup\" />\n        </provider>\n    </application>\n</manifest>"
  },
  {
    "path": "permission-flow/src/main/java/dev/shreyaspatil/permissionFlow/PermissionFlow.kt",
    "content": "/**\n * Copyright 2022 Shreyas Patil\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage dev.shreyaspatil.permissionFlow\n\nimport android.content.Context\nimport dev.shreyaspatil.permissionFlow.PermissionFlow.Companion.getInstance\nimport dev.shreyaspatil.permissionFlow.PermissionFlow.Companion.init\nimport dev.shreyaspatil.permissionFlow.impl.PermissionFlowImpl\nimport kotlinx.coroutines.CoroutineDispatcher\nimport kotlinx.coroutines.DelicateCoroutinesApi\nimport kotlinx.coroutines.flow.StateFlow\nimport kotlinx.coroutines.newFixedThreadPoolContext\n\n/**\n * A utility class which provides a functionality for observing state of a permission (whether it's\n * granted or not) with reactive coroutine stream i.e. [StateFlow].\n *\n * This takes care of listening to permission state change from any screen throughout the\n * application so that you can listen to permission in any layer of architecture within app.\n *\n * To retrieve the instance, use [getInstance] method but make sure to initialize it with [init]\n * method before retrieving instance. Otherwise, it'll throw [IllegalStateException]\n *\n * Example usage:\n *\n * **1. Initialization**\n *\n * ```\n *  class MyApplication: Application() {\n *      override fun onCreate() {\n *          super.onCreate()\n *          PermissionFlow.init(this)\n *      }\n *  }\n * ```\n *\n * **2. Observing permission**\n *\n * ```\n *  val permissionFlow = PermissionFlow.getInstance()\n *\n *  fun observeContactsPermission() {\n *      coroutineScope.launch {\n *          permissionFlow.getPermissionState(android.Manifest.permission.READ_CONTACTS)\n *              .collect { state ->\n *                  if (state.isGranted) {\n *                      // Do something\n *                  } else {\n *                      if (state.isRationaleRequired) {\n *                          // Do something\n *                      }\n *                  }\n *              }\n *      }\n *  }\n * ```\n *\n * **3. Launching permission**\n *\n * ```\n *  class MyActivity: AppCompatActivity() {\n *      private val permissionLauncher = registerForPermissionFlowRequestsResult()\n *\n *      fun askContactPermission() {\n *          permissionLauncher.launch(android.Manifest.permission.READ_CONTACTS)\n *      }\n *  }\n * ```\n *\n * This utility tries to listen to permission state change which may not happen within a application\n * (_e.g. user trying to allow permission from app settings_), but doesn't guarantee that you'll\n * always get a updated state at the accurate instant.\n */\ninterface PermissionFlow {\n    /**\n     * Returns [StateFlow] for a given [permission]\n     *\n     * @param permission Unique permission identity (for e.g.\n     *   [android.Manifest.permission.READ_CONTACTS])\n     *\n     * Example:\n     * ```\n     *  permissionFlow.getPermissionState(android.Manifest.permission.READ_CONTACTS)\n     *      .collect { state ->\n     *          if (state.isGranted) {\n     *              // Do something\n     *          } else {\n     *              if (state.isRationaleRequired) {\n     *                  // Do something\n     *              }\n     *          }\n     *      }\n     * ```\n     */\n    fun getPermissionState(permission: String): StateFlow<PermissionState>\n\n    /**\n     * Returns [StateFlow] of a combining state for [permissions]\n     *\n     * @param permissions List of permissions (for e.g. [android.Manifest.permission.READ_CONTACTS],\n     *   [android.Manifest.permission.READ_SMS])\n     *\n     * Example:\n     * ```\n     *  permissionFlow.getMultiplePermissionState(\n     *      android.Manifest.permission.READ_CONTACTS,\n     *      android.Manifest.permission.READ_SMS\n     *  ).collect { state ->\n     *      // All permission states\n     *      val allPermissions = state.permissions\n     *\n     *      // Check whether all permissions are granted\n     *      val allGranted = state.allGranted\n     *\n     *      // List of granted permissions\n     *      val grantedPermissions = state.grantedPermissions\n     *\n     *      // List of denied permissions\n     *      val deniedPermissions = state.deniedPermissions\n     *  }\n     * ```\n     */\n    fun getMultiplePermissionState(vararg permissions: String): StateFlow<MultiplePermissionState>\n\n    /**\n     * This helps to check if specified [permissions] are changed and it verifies it and updates the\n     * state of permissions which are being observed via [getMultiplePermissionState] method.\n     *\n     * This can be useful when you are not using result launcher which is provided with this library\n     * and manually handling permission request and want to update the state of permission in this\n     * library so that flows which are being observed should get an updated state.\n     *\n     * If [stopListening] is called earlier and hasn't started listening again, notifying permission\n     * doesn't work. Its new state is automatically calculated after starting listening to states\n     * again by calling [startListening] method.\n     *\n     * Example usage:\n     *\n     * In this example, we are not using result launcher provided by this library. So we are\n     * manually notifying library about state change of a permission.\n     *\n     * ```\n     *  class MyActivity: AppCompatActivity() {\n     *      private val permissionFlow = PermissionFlow.getInstance()\n     *      private val permissionLauncher = registerForActivityResult(RequestPermission()) { isGranted ->\n     *          permissionFlow.notifyPermissionsChanged(android.Manifest.permission.READ_CONTACTS)\n     *      }\n     *  }\n     * ```\n     *\n     * @param permissions List of permissions\n     */\n    fun notifyPermissionsChanged(vararg permissions: String)\n\n    /**\n     * Starts listening the changes of state of permissions.\n     *\n     * Ideally it automatically starts listening eagerly when application is started and created via\n     * [dev.shreyaspatil.permissionFlow.initializer.PermissionFlowInitializer]. If initializer is\n     * disabled, then starts listening lazily when [getPermissionState] [getPermissionEvent] or\n     * [getMultiplePermissionState] method is used for the first time. But this can be used to start\n     * to listen again after stopping listening with [stopListening].\n     */\n    fun startListening()\n\n    /**\n     * Stops listening to the state changes of permissions throughout the application. This means\n     * the state of permission retrieved with [getMultiplePermissionState] method will not be\n     * updated after stopping listening. To start to listen again, use [startListening] method.\n     */\n    fun stopListening()\n\n    /**\n     * Companion of [PermissionFlow] to provide initialization of [PermissionFlow] as well as\n     * getting instance.\n     */\n    companion object {\n        @OptIn(DelicateCoroutinesApi::class)\n        private val DEFAULT_DISPATCHER = newFixedThreadPoolContext(2, \"PermissionFlow\")\n\n        /**\n         * Initializes this [PermissionFlow] instance with specified arguments.\n         *\n         * @param context The Android's [Context]. Application context is recommended.\n         * @param dispatcher Coroutine dispatcher to be used in the [PermissionFlow]. By default, it\n         *   utilizes dispatcher having fixed two number of threads.\n         */\n        @JvmStatic\n        @JvmOverloads\n        fun init(context: Context, dispatcher: CoroutineDispatcher = DEFAULT_DISPATCHER) {\n            PermissionFlowImpl.init(context, dispatcher)\n        }\n\n        /**\n         * Returns an instance with default implementation of [PermissionFlow].\n         *\n         * @return Instance of [PermissionFlow].\n         * @throws IllegalStateException If method [init] is not called before using this method.\n         */\n        @JvmStatic\n        fun getInstance(): PermissionFlow =\n            PermissionFlowImpl.instance\n                ?: error(\n                    \"Failed to create instance of PermissionFlow. Did you forget to call `PermissionFlow.init(context)`?\")\n    }\n}\n"
  },
  {
    "path": "permission-flow/src/main/java/dev/shreyaspatil/permissionFlow/State.kt",
    "content": "/**\n * Copyright 2022 Shreyas Patil\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage dev.shreyaspatil.permissionFlow\n\n/**\n * State model of a permission\n *\n * @property permission Name of a permission\n * @property isGranted State of a permission whether it's granted or not\n * @property isRationaleRequired Whether to show rationale for a permission or not.\n */\ndata class PermissionState(\n    val permission: String,\n    val isGranted: Boolean,\n    val isRationaleRequired: Boolean?,\n)\n\n/**\n * State model for multiple permissions\n *\n * @property permissions List of state of multiple permissions\n */\ndata class MultiplePermissionState(val permissions: List<PermissionState>) {\n    /** Returns true if all permissions are granted by user */\n    val allGranted by lazy { permissions.all { it.isGranted } }\n\n    /** List of permissions which are granted by user */\n    val grantedPermissions by lazy { permissions.filter { it.isGranted }.map { it.permission } }\n\n    /** List of permissions which are denied / not granted by user */\n    val deniedPermissions by lazy { permissions.filter { !it.isGranted }.map { it.permission } }\n\n    /** List of permissions which are required to show rationale */\n    val permissionsRequiringRationale by lazy {\n        permissions.filter { it.isRationaleRequired == true }.map { it.permission }\n    }\n}\n"
  },
  {
    "path": "permission-flow/src/main/java/dev/shreyaspatil/permissionFlow/contract/RequestPermissionsContract.kt",
    "content": "/**\n * Copyright 2022 Shreyas Patil\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage dev.shreyaspatil.permissionFlow.contract\n\nimport android.content.Context\nimport android.content.Intent\nimport androidx.activity.ComponentActivity\nimport androidx.activity.result.ActivityResultLauncher\nimport androidx.activity.result.contract.ActivityResultContract\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.activity.result.contract.ActivityResultContracts.RequestMultiplePermissions\nimport dev.shreyaspatil.permissionFlow.PermissionFlow\n\n/**\n * An [ActivityResultContract] which delegates request and response to\n * [ActivityResultContracts.RequestMultiplePermissions] and silently notifies [PermissionFlow]\n * regarding state change of a permissions which are requested through [ActivityResultLauncher].\n *\n * Refer to [ComponentActivity.registerForPermissionFlowRequestsResult] for actual usage.\n */\nclass RequestPermissionsContract(\n    private val contract: RequestMultiplePermissions = RequestMultiplePermissions(),\n    private val permissionFlow: PermissionFlow = PermissionFlow.getInstance(),\n) : ActivityResultContract<Array<String>, Map<String, Boolean>>() {\n\n    override fun createIntent(context: Context, input: Array<String>): Intent {\n        return contract.createIntent(context, input)\n    }\n\n    override fun parseResult(resultCode: Int, intent: Intent?): Map<String, Boolean> {\n        return contract.parseResult(resultCode, intent).also {\n            val permissions = it.keys.toList().toTypedArray()\n            permissionFlow.notifyPermissionsChanged(*permissions)\n        }\n    }\n}\n"
  },
  {
    "path": "permission-flow/src/main/java/dev/shreyaspatil/permissionFlow/impl/PermissionFlowImpl.kt",
    "content": "/**\n * Copyright 2022 Shreyas Patil\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage dev.shreyaspatil.permissionFlow.impl\n\nimport android.app.Application\nimport android.content.Context\nimport androidx.annotation.VisibleForTesting\nimport dev.shreyaspatil.permissionFlow.MultiplePermissionState\nimport dev.shreyaspatil.permissionFlow.PermissionFlow\nimport dev.shreyaspatil.permissionFlow.PermissionState\nimport dev.shreyaspatil.permissionFlow.internal.ApplicationStateMonitor\nimport dev.shreyaspatil.permissionFlow.watchmen.PermissionWatchmen\nimport kotlinx.coroutines.CoroutineDispatcher\nimport kotlinx.coroutines.flow.StateFlow\n\n/** Default implementation of a [PermissionFlow] */\ninternal class PermissionFlowImpl\n@VisibleForTesting\nconstructor(\n    private val watchmen: PermissionWatchmen,\n) : PermissionFlow {\n\n    override fun getPermissionState(permission: String): StateFlow<PermissionState> {\n        return watchmen.watchState(permission)\n    }\n\n    override fun getMultiplePermissionState(\n        vararg permissions: String\n    ): StateFlow<MultiplePermissionState> {\n        return watchmen.watchMultipleState(permissions.toList().toTypedArray())\n    }\n\n    override fun notifyPermissionsChanged(vararg permissions: String) {\n        watchmen.notifyPermissionsChanged(permissions.toList().toTypedArray())\n    }\n\n    override fun startListening() {\n        watchmen.wakeUp()\n    }\n\n    override fun stopListening() {\n        watchmen.sleep()\n    }\n\n    internal companion object {\n        @Volatile\n        var instance: PermissionFlowImpl? = null\n            private set\n\n        @Synchronized\n        fun init(context: Context, dispatcher: CoroutineDispatcher) {\n            if (instance == null) {\n                val monitor = ApplicationStateMonitor(context.applicationContext as Application)\n                val watchmen =\n                    PermissionWatchmen(\n                        appStateMonitor = monitor,\n                        dispatcher = dispatcher,\n                    )\n                instance = PermissionFlowImpl(watchmen)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "permission-flow/src/main/java/dev/shreyaspatil/permissionFlow/initializer/PermissionFlowInitializer.kt",
    "content": "/**\n * Copyright 2022 Shreyas Patil\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage dev.shreyaspatil.permissionFlow.initializer\n\nimport android.content.Context\nimport androidx.startup.Initializer\nimport dev.shreyaspatil.permissionFlow.PermissionFlow\n\n/** Initializes [PermissionFlow] instance on app startup. */\nclass PermissionFlowInitializer : Initializer<Unit> {\n    override fun create(context: Context) {\n        PermissionFlow.init(context)\n        PermissionFlow.getInstance().startListening()\n    }\n\n    override fun dependencies(): List<Class<out Initializer<*>>> {\n        return emptyList()\n    }\n}\n"
  },
  {
    "path": "permission-flow/src/main/java/dev/shreyaspatil/permissionFlow/internal/ApplicationStateMonitor.kt",
    "content": "/**\n * Copyright 2022 Shreyas Patil\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage dev.shreyaspatil.permissionFlow.internal\n\nimport android.app.Activity\nimport android.app.Application\nimport android.content.pm.PackageManager\nimport android.os.Build\nimport android.os.Bundle\nimport androidx.annotation.RequiresApi\nimport androidx.annotation.VisibleForTesting\nimport androidx.core.app.ActivityCompat\nimport androidx.core.content.ContextCompat\nimport dev.shreyaspatil.permissionFlow.PermissionState\nimport java.lang.ref.WeakReference\nimport kotlinx.coroutines.channels.awaitClose\nimport kotlinx.coroutines.flow.callbackFlow\n\n/**\n * Monitors the state of the application and provides information about the info and state of\n * application.\n */\ninternal class ApplicationStateMonitor(private val application: Application) {\n    private var currentActivity: WeakReference<Activity>? = null\n\n    /** Returns the current state of the permission. */\n    fun getPermissionState(permission: String): PermissionState {\n        val isGranted = isPermissionGranted(permission)\n        val isRationaleRequired = shouldShowPermissionRationale(permission)\n        return PermissionState(permission, isGranted, isRationaleRequired)\n    }\n\n    /** Returns whether the permission should show rationale or not. */\n    private fun shouldShowPermissionRationale(permission: String): Boolean? {\n        val activity = currentActivity?.get() ?: return null\n        return ActivityCompat.shouldShowRequestPermissionRationale(activity, permission)\n    }\n\n    /** Returns whether the permission is granted or not. */\n    private fun isPermissionGranted(permission: String): Boolean {\n        return ContextCompat.checkSelfPermission(\n            application,\n            permission,\n        ) == PackageManager.PERMISSION_GRANTED\n    }\n\n    /**\n     * A flow which gives callback whenever any activity is started withing application (without\n     * configuration change) or any activity is resumed after being in multi-window or\n     * picture-in-picture mode.\n     */\n    val activityForegroundEvents\n        get() = callbackFlow {\n            val callback =\n                object : Application.ActivityLifecycleCallbacks {\n                    private var isActivityChangingConfigurations: Boolean? = null\n                    private var wasInMultiWindowMode: Boolean? = null\n                    private var wasInPictureInPictureMode: Boolean? = null\n\n                    override fun onActivityPreCreated(\n                        activity: Activity,\n                        savedInstanceState: Bundle?,\n                    ) {\n                        currentActivity = WeakReference(activity)\n                    }\n\n                    override fun onActivityCreated(\n                        activity: Activity,\n                        savedInstanceState: Bundle?,\n                    ) {\n                        if (currentActivity?.get() != activity) {\n                            currentActivity = WeakReference(activity)\n                        }\n                    }\n\n                    /**\n                     * Whenever activity receives onStart() lifecycle callback, emit foreground\n                     * event only when activity hasn't changed configurations.\n                     */\n                    override fun onActivityStarted(activity: Activity) {\n                        if (isActivityChangingConfigurations == false) {\n                            trySend(Unit)\n                        }\n                    }\n\n                    override fun onActivityStopped(activity: Activity) {\n                        isActivityChangingConfigurations = activity.isChangingConfigurations\n                    }\n\n                    /**\n                     * Whenever application is resized after being in in PiP or multi-window mode,\n                     * or exits from these modes, onResumed() lifecycle callback is triggered.\n                     *\n                     * Here we assume that user has changed permission from app settings after being\n                     * in PiP or multi-window mode. So whenever these modes are exited, emit\n                     * foreground event.\n                     */\n                    override fun onActivityResumed(activity: Activity) {\n                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {\n                            if (isActivityResumedAfterMultiWindowOrPiPMode(activity)) {\n                                trySend(Unit)\n                            }\n                            wasInMultiWindowMode = activity.isInMultiWindowMode\n                            wasInPictureInPictureMode = activity.isInPictureInPictureMode\n                        }\n                    }\n\n                    /**\n                     * Whenever application is launched in PiP or multi-window mode, onPaused()\n                     * lifecycle callback is triggered.\n                     */\n                    override fun onActivityPaused(activity: Activity) {\n                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {\n                            wasInMultiWindowMode = activity.isInMultiWindowMode\n                            wasInPictureInPictureMode = activity.isInPictureInPictureMode\n                        }\n                    }\n\n                    override fun onActivitySaveInstanceState(\n                        activity: Activity,\n                        outState: Bundle,\n                    ) {}\n\n                    override fun onActivityDestroyed(activity: Activity) {\n                        if (activity == currentActivity?.get()) {\n                            currentActivity?.clear()\n                        }\n                    }\n\n                    /**\n                     * Returns whether [activity] was previously in multi-window mode or PiP mode.\n                     */\n                    @RequiresApi(Build.VERSION_CODES.N)\n                    private fun isActivityResumedAfterMultiWindowOrPiPMode(activity: Activity) =\n                        (wasInMultiWindowMode == true && !activity.isInMultiWindowMode) ||\n                            (wasInPictureInPictureMode == true &&\n                                !activity.isInPictureInPictureMode)\n                }\n\n            application.registerActivityLifecycleCallbacks(callback)\n\n            awaitClose {\n                // Cleanup\n                application.unregisterActivityLifecycleCallbacks(callback)\n            }\n        }\n\n    @VisibleForTesting fun getCurrentActivityReference() = currentActivity\n}\n"
  },
  {
    "path": "permission-flow/src/main/java/dev/shreyaspatil/permissionFlow/utils/ActivityResultLauncherExt.kt",
    "content": "/**\n * Copyright 2022 Shreyas Patil\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage dev.shreyaspatil.permissionFlow.utils\n\nimport androidx.activity.result.ActivityResultLauncher\n\n/**\n * A short-hand utility for launching multiple requests with variable arguments support.\n *\n * @param input Input string\n */\nfun ActivityResultLauncher<Array<String>>.launch(vararg input: String) =\n    launch(input.toList().toTypedArray())\n"
  },
  {
    "path": "permission-flow/src/main/java/dev/shreyaspatil/permissionFlow/utils/PermissionResultLauncher.kt",
    "content": "/**\n * Copyright 2022 Shreyas Patil\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n@file:JvmName(\"PermissionResultLauncher\")\n\npackage dev.shreyaspatil.permissionFlow.utils\n\nimport androidx.activity.ComponentActivity\nimport androidx.activity.result.ActivityResultCallback\nimport androidx.activity.result.ActivityResultLauncher\nimport androidx.activity.result.ActivityResultRegistry\nimport androidx.fragment.app.Fragment\nimport dev.shreyaspatil.permissionFlow.PermissionFlow\nimport dev.shreyaspatil.permissionFlow.contract.RequestPermissionsContract\n\n/**\n * Returns a [ActivityResultLauncher] for this Activity which internally notifies [PermissionFlow]\n * about the state change whenever permission state is changed with this launcher.\n *\n * Usage:\n * ```\n *  class MyActivity: AppCompatActivity() {\n *      private val permissionLauncher = registerForPermissionFlowRequestsResult()\n *\n *      fun askContactPermission() {\n *          permissionLauncher.launch(android.Manifest.permission.READ_CONTACTS)\n *      }\n *  }\n * ```\n *\n * @param requestPermissionsContract A contract specifying permission request and result.\n * @param activityResultRegistry Activity result registry. By default it uses Activity's Result\n *   registry.\n * @param callback Callback of a permission state change.\n */\n@JvmOverloads\nfun ComponentActivity.registerForPermissionFlowRequestsResult(\n    requestPermissionsContract: RequestPermissionsContract = RequestPermissionsContract(),\n    activityResultRegistry: ActivityResultRegistry = this.activityResultRegistry,\n    callback: ActivityResultCallback<Map<String, Boolean>> = emptyCallback(),\n): ActivityResultLauncher<Array<String>> =\n    registerForActivityResult(\n        requestPermissionsContract,\n        activityResultRegistry,\n        callback,\n    )\n\n/**\n * Returns a [ActivityResultLauncher] for this Fragment which internally notifies [PermissionFlow]\n * about the state change whenever permission state is changed with this launcher.\n *\n * Usage:\n * ```\n *  class MyFragment: Fragment() {\n *      private val permissionLauncher = registerForPermissionFlowRequestsResult()\n *\n *      fun askContactPermission() {\n *          permissionLauncher.launch(android.Manifest.permission.READ_CONTACTS)\n *      }\n *  }\n * ```\n *\n * @param requestPermissionsContract A contract specifying permission request and result. registry.\n * @param callback Callback of a permission state change.\n */\n@JvmOverloads\nfun Fragment.registerForPermissionFlowRequestsResult(\n    requestPermissionsContract: RequestPermissionsContract = RequestPermissionsContract(),\n    callback: ActivityResultCallback<Map<String, Boolean>> = emptyCallback(),\n): ActivityResultLauncher<Array<String>> =\n    registerForActivityResult(\n        requestPermissionsContract,\n        callback,\n    )\n\n/**\n * Returns a [ActivityResultLauncher] for this Fragment which internally notifies [PermissionFlow]\n * about the state change whenever permission state is changed with this launcher.\n *\n * Usage:\n * ```\n *  class MyFragment: Fragment() {\n *      private val permissionLauncher = registerForPermissionFlowRequestsResult()\n *\n *      fun askContactPermission() {\n *          permissionLauncher.launch(android.Manifest.permission.READ_CONTACTS)\n *      }\n *  }\n * ```\n *\n * @param requestPermissionsContract A contract specifying permission request and result.\n * @param activityResultRegistry Activity result registry. By default it uses Activity's Result\n *   registry.\n * @param callback Callback of a permission state change.\n */\n@JvmOverloads\nfun Fragment.registerForPermissionFlowRequestsResult(\n    requestPermissionsContract: RequestPermissionsContract = RequestPermissionsContract(),\n    activityResultRegistry: ActivityResultRegistry,\n    callback: ActivityResultCallback<Map<String, Boolean>> = emptyCallback(),\n): ActivityResultLauncher<Array<String>> =\n    registerForActivityResult(\n        requestPermissionsContract,\n        activityResultRegistry,\n        callback,\n    )\n\nprivate fun <T> emptyCallback() = ActivityResultCallback<T> {}\n"
  },
  {
    "path": "permission-flow/src/main/java/dev/shreyaspatil/permissionFlow/utils/stateFlow/CombinedStateFlow.kt",
    "content": "/**\n * Copyright 2022 Shreyas Patil\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage dev.shreyaspatil.permissionFlow.utils.stateFlow\n\nimport kotlinx.coroutines.coroutineScope\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.FlowCollector\nimport kotlinx.coroutines.flow.StateFlow\nimport kotlinx.coroutines.flow.combine\nimport kotlinx.coroutines.flow.stateIn\n\n/**\n * [StateFlow] which delegates [flow] to use it as StateFlow and uses [getValue] to calculate value\n * at the instant.\n */\nprivate class CombinedStateFlow<T>(\n    private val getValue: () -> T,\n    private val flow: Flow<T>,\n) : StateFlow<T> {\n    override val replayCache: List<T>\n        get() = listOf(value)\n\n    override val value: T\n        get() = getValue()\n\n    override suspend fun collect(collector: FlowCollector<T>): Nothing = coroutineScope {\n        flow.stateIn(this).collect(collector)\n    }\n}\n\n/** Returns implementation of [CombinedStateFlow] */\ninternal fun <T> combineStates(\n    getValue: () -> T,\n    flow: Flow<T>,\n): StateFlow<T> = CombinedStateFlow(getValue, flow)\n\n/** Combines multiple [StateFlow]s and transforms them into another [StateFlow] */\ninternal inline fun <reified T, R> combineStates(\n    vararg stateFlows: StateFlow<T>,\n    crossinline transform: (Array<T>) -> R,\n): StateFlow<R> =\n    combineStates(\n        getValue = { transform(stateFlows.map { it.value }.toTypedArray()) },\n        flow = combine(*stateFlows) { transform(it) },\n    )\n"
  },
  {
    "path": "permission-flow/src/main/java/dev/shreyaspatil/permissionFlow/watchmen/PermissionWatchmen.kt",
    "content": "/**\n * Copyright 2022 Shreyas Patil\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage dev.shreyaspatil.permissionFlow.watchmen\n\nimport dev.shreyaspatil.permissionFlow.MultiplePermissionState\nimport dev.shreyaspatil.permissionFlow.PermissionState\nimport dev.shreyaspatil.permissionFlow.internal.ApplicationStateMonitor\nimport dev.shreyaspatil.permissionFlow.utils.stateFlow.combineStates\nimport kotlinx.coroutines.CoroutineDispatcher\nimport kotlinx.coroutines.CoroutineName\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.SupervisorJob\nimport kotlinx.coroutines.cancelChildren\nimport kotlinx.coroutines.flow.MutableSharedFlow\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.StateFlow\nimport kotlinx.coroutines.flow.asStateFlow\nimport kotlinx.coroutines.flow.launchIn\nimport kotlinx.coroutines.flow.onEach\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.yield\n\n/** A watchmen which keeps watching state changes of permissions and events of permissions. */\n@Suppress(\"unused\")\ninternal class PermissionWatchmen(\n    private val appStateMonitor: ApplicationStateMonitor,\n    dispatcher: CoroutineDispatcher,\n) {\n    private val watchmenScope =\n        CoroutineScope(\n            dispatcher + SupervisorJob() + CoroutineName(\"PermissionWatchmen\"),\n        )\n\n    private var watchEventsJob: Job? = null\n    private var watchActivityEventJob: Job? = null\n\n    /** A in-memory store for storing permission and its state holder i.e. [StateFlow] */\n    private val permissionFlows = mutableMapOf<String, PermissionStateFlowDelegate>()\n\n    private val permissionEvents = MutableSharedFlow<PermissionState>()\n\n    fun watchState(permission: String): StateFlow<PermissionState> {\n        // Wakeup watchmen if sleeping\n        wakeUp()\n        return getOrCreatePermissionStateFlow(permission)\n    }\n\n    fun watchMultipleState(permissions: Array<String>): StateFlow<MultiplePermissionState> {\n        // Wakeup watchmen if sleeping\n        wakeUp()\n\n        val permissionStates =\n            permissions.distinct().map { getOrCreatePermissionStateFlow(it) }.toTypedArray()\n\n        return combineStates(*permissionStates) { MultiplePermissionState(it.toList()) }\n    }\n\n    fun notifyPermissionsChanged(permissions: Array<String>) {\n        watchmenScope.launch {\n            permissions.forEach { permission ->\n                permissionEvents.emit(appStateMonitor.getPermissionState(permission))\n            }\n        }\n    }\n\n    @Synchronized\n    fun wakeUp() {\n        watchPermissionEvents()\n        watchActivities()\n        notifyAllPermissionsChanged()\n    }\n\n    @Synchronized\n    fun sleep() {\n        watchmenScope.coroutineContext.cancelChildren()\n    }\n\n    /**\n     * First finds for existing flow (if available) otherwise creates a new [MutableStateFlow] for\n     * [permission] and returns a read-only [StateFlow] for a [permission].\n     */\n    @Synchronized\n    private fun getOrCreatePermissionStateFlow(permission: String): StateFlow<PermissionState> {\n        return permissionFlows\n            .getOrPut(permission) {\n                PermissionStateFlowDelegate(appStateMonitor.getPermissionState(permission))\n            }\n            .state\n    }\n\n    /** Watches for the permission events and updates appropriate state holders of permission */\n    private fun watchPermissionEvents() {\n        if (watchEventsJob != null && watchEventsJob?.isActive == true) return\n        watchEventsJob =\n            watchmenScope.launch {\n                permissionEvents.collect { permissionFlows[it.permission]?.setState(it) }\n            }\n    }\n\n    /**\n     * Watches for activity foreground events (to detect whether user has changed permission by\n     * going in settings) and recalculates state of the permissions which are currently being\n     * observed.\n     */\n    private fun watchActivities() {\n        if (watchActivityEventJob != null && watchActivityEventJob?.isActive == true) return\n        watchActivityEventJob =\n            appStateMonitor.activityForegroundEvents\n                .onEach {\n                    // Since this is not priority task, we want to yield current thread for other\n                    // tasks for the watchmen.\n                    yield()\n                    notifyAllPermissionsChanged()\n                }\n                .launchIn(watchmenScope)\n    }\n\n    private fun notifyAllPermissionsChanged() {\n        if (permissionFlows.isEmpty()) return\n        notifyPermissionsChanged(permissionFlows.keys.toTypedArray())\n    }\n\n    /** A delegate for [MutableStateFlow] which creates flow for holding state of a permission. */\n    private class PermissionStateFlowDelegate(initialState: PermissionState) {\n\n        private val _state = MutableStateFlow(initialState)\n        val state = _state.asStateFlow()\n\n        fun setState(newState: PermissionState) {\n            _state.value = newState\n        }\n    }\n}\n"
  },
  {
    "path": "permission-flow/src/test/java/dev/shreyaspatil/permissionFlow/MultiplePermissionStateTest.kt",
    "content": "/**\n * Copyright 2022 Shreyas Patil\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage dev.shreyaspatil.permissionFlow\n\nimport org.junit.Assert.assertEquals\nimport org.junit.Assert.assertFalse\nimport org.junit.Assert.assertTrue\nimport org.junit.Test\n\nclass MultiplePermissionStateTest {\n\n    @Test\n    fun allGranted_shouldReturnTrue_whenAllPermissionsAreGranted() {\n        // When: Multiple permission state having all permissions granted\n        val permissions =\n            multiplePermissionState(\n                grantedPermission(\"A\"),\n                grantedPermission(\"B\"),\n                grantedPermission(\"C\"),\n            )\n\n        // Then: All permissions should be granted\n        assertTrue(permissions.allGranted)\n    }\n\n    @Test\n    fun allGranted_shouldReturnFalse_whenAllPermissionsAreNotGranted() {\n        // When: Multiple permission state having some permissions as not granted\n        val permissions =\n            multiplePermissionState(\n                grantedPermission(\"A\"),\n                deniedPermission(\"B\"),\n                grantedPermission(\"C\"),\n            )\n\n        // Then: All permissions should NOT be granted\n        assertFalse(permissions.allGranted)\n    }\n\n    @Test\n    fun grantedPermissions_shouldReturnListOfGrantedPermissions() {\n        // When: Multiple permission state\n        val permissions =\n            multiplePermissionState(\n                grantedPermission(\"A\"),\n                deniedPermission(\"B\"),\n                grantedPermission(\"C\"),\n                deniedPermission(\"D\"),\n            )\n\n        // Then: Permissions A and C should be present in granted permissions list\n        assertEquals(permissions.grantedPermissions, listOf(\"A\", \"C\"))\n    }\n\n    @Test\n    fun deniedPermissions_shouldReturnListOfDeniedPermissions() {\n        // When: Multiple permission state\n        val permissions =\n            multiplePermissionState(\n                grantedPermission(\"A\"),\n                deniedPermission(\"B\"),\n                grantedPermission(\"C\"),\n                deniedPermission(\"D\"),\n            )\n\n        // Then: Permissions B and D should be present in granted permissions list\n        assertEquals(permissions.deniedPermissions, listOf(\"B\", \"D\"))\n    }\n\n    @Test\n    fun permissionsRequiringRationale_shouldReturnListOfDeniedPermissionsRequiringRationale() {\n        // When: Multiple permission state\n        val permissions =\n            multiplePermissionState(\n                grantedPermission(\"A\"),\n                deniedPermission(\"B\"),\n                grantedPermission(\"C\"),\n                deniedPermissionRequiringRationale(\"D\"),\n                deniedPermissionRequiringRationale(\"E\"),\n            )\n\n        // Then: Permissions D and E should be present in permissions requiring rationale list\n        assertEquals(permissions.permissionsRequiringRationale, listOf(\"D\", \"E\"))\n    }\n\n    private fun multiplePermissionState(vararg permissionState: PermissionState) =\n        MultiplePermissionState(permissionState.toList())\n\n    private fun grantedPermission(permission: String) =\n        PermissionState(permission = permission, isGranted = true, isRationaleRequired = false)\n\n    private fun deniedPermission(permission: String) =\n        PermissionState(permission = permission, isGranted = false, isRationaleRequired = null)\n\n    private fun deniedPermissionRequiringRationale(permission: String) =\n        PermissionState(permission = permission, isGranted = false, isRationaleRequired = true)\n}\n"
  },
  {
    "path": "permission-flow/src/test/java/dev/shreyaspatil/permissionFlow/PermissionFlowTest.kt",
    "content": "/**\n * Copyright 2022 Shreyas Patil\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage dev.shreyaspatil.permissionFlow\n\nimport android.app.Application\nimport io.mockk.every\nimport io.mockk.mockk\nimport kotlinx.coroutines.ExperimentalCoroutinesApi\nimport org.junit.Assert.assertEquals\nimport org.junit.Assert.assertTrue\nimport org.junit.Test\n\n@OptIn(ExperimentalCoroutinesApi::class)\nclass PermissionFlowTest {\n    @Test\n    fun testGetInstanceWithoutInit_shouldThrowException() {\n        val result = runCatching { PermissionFlow.getInstance() }\n\n        assertTrue(result.isFailure)\n\n        val expectedErrorMessage =\n            \"Failed to create instance of PermissionFlow. Did you forget to call `PermissionFlow.init(context)`?\"\n        assertEquals(expectedErrorMessage, result.exceptionOrNull()!!.message)\n    }\n\n    @Test\n    fun testGetInstanceWithInit_shouldBeSingleInstanceAlways() {\n        // Init for the first time\n        initPermissionFlow()\n        // Get instance 1\n        val instance1 = PermissionFlow.getInstance()\n\n        // Init for the second time\n        initPermissionFlow()\n        // Get instance 2\n        val instance2 = PermissionFlow.getInstance()\n\n        // Both instances should be the same\n        assert(instance1 === instance2)\n    }\n\n    private fun initPermissionFlow() {\n        PermissionFlow.init(\n            mockk { every { applicationContext } returns mockk<Application>() },\n        )\n    }\n}\n"
  },
  {
    "path": "permission-flow/src/test/java/dev/shreyaspatil/permissionFlow/contract/RequestPermissionsContractTest.kt",
    "content": "/**\n * Copyright 2022 Shreyas Patil\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage dev.shreyaspatil.permissionFlow.contract\n\nimport android.content.Context\nimport androidx.activity.result.contract.ActivityResultContracts.RequestMultiplePermissions\nimport dev.shreyaspatil.permissionFlow.PermissionFlow\nimport io.mockk.every\nimport io.mockk.mockk\nimport io.mockk.verify\nimport org.junit.Assert.assertEquals\nimport org.junit.Before\nimport org.junit.Test\n\nclass RequestPermissionsContractTest {\n    private lateinit var requestMultiplePermissions: RequestMultiplePermissions\n    private lateinit var permissionFlow: PermissionFlow\n    private lateinit var context: Context\n\n    private lateinit var contract: RequestPermissionsContract\n\n    @Before\n    fun setUp() {\n        requestMultiplePermissions = mockk()\n        permissionFlow = mockk(relaxUnitFun = true)\n        context = mockk()\n\n        contract = RequestPermissionsContract(requestMultiplePermissions, permissionFlow)\n    }\n\n    @Test\n    fun testCreateIntent() {\n        // Given: List of permissions\n        val permissions = arrayOf(\"A\", \"B\", \"C\", \"D\")\n        every { requestMultiplePermissions.createIntent(any(), any()) } returns mockk()\n\n        // When: Intent is created with permissions\n        contract.createIntent(context, permissions)\n\n        // Then: The context and permissions should be delegated to original contract\n        verify(exactly = 1) { requestMultiplePermissions.createIntent(context, permissions) }\n    }\n\n    @Test\n    fun testParseResult() {\n        // Given: Result to be parsed\n        val expectedResult = mapOf(\"A\" to true, \"B\" to false, \"C\" to true)\n        every { requestMultiplePermissions.parseResult(any(), any()) } returns expectedResult\n\n        // When: The result is parsed\n        val actualResult = contract.parseResult(0, null)\n\n        // Then: Correct result should be returned\n        assertEquals(expectedResult, actualResult)\n\n        // Then: PermissionFlow should be notified with updated permissions\n        verify(exactly = 1) { permissionFlow.notifyPermissionsChanged(\"A\", \"B\", \"C\") }\n    }\n}\n"
  },
  {
    "path": "permission-flow/src/test/java/dev/shreyaspatil/permissionFlow/impl/PermissionFlowImplTest.kt",
    "content": "/**\n * Copyright 2022 Shreyas Patil\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage dev.shreyaspatil.permissionFlow.impl\n\nimport dev.shreyaspatil.permissionFlow.MultiplePermissionState\nimport dev.shreyaspatil.permissionFlow.PermissionState\nimport dev.shreyaspatil.permissionFlow.watchmen.PermissionWatchmen\nimport io.mockk.every\nimport io.mockk.mockk\nimport io.mockk.verify\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport org.junit.Assert.assertEquals\nimport org.junit.Before\nimport org.junit.Test\n\nclass PermissionFlowImplTest {\n    private lateinit var watchmen: PermissionWatchmen\n    private lateinit var permissionFlow: PermissionFlowImpl\n\n    @Before\n    fun setUp() {\n        watchmen = mockk(relaxUnitFun = true)\n        permissionFlow = PermissionFlowImpl(watchmen)\n    }\n\n    @Test\n    fun testGetPermissionState() {\n        // Given: Permission flow\n        val expectedFlow = MutableStateFlow(PermissionState(\"A\", true, false))\n        every { watchmen.watchState(\"A\") } returns expectedFlow\n\n        // When: Flow for any permission is retrieved\n        val actualFlow = permissionFlow.getPermissionState(\"A\")\n\n        // Then: Correct flow should be returned\n        assertEquals(expectedFlow, actualFlow)\n    }\n\n    @Test\n    fun testGetMultiplePermissionState() {\n        // Given: Permission flow\n        val expectedFlow = MutableStateFlow(MultiplePermissionState(emptyList()))\n        every { watchmen.watchMultipleState(arrayOf(\"A\", \"B\")) } returns expectedFlow\n\n        // When: Flow for multiple permissions is retrieved\n        val actualFlow = permissionFlow.getMultiplePermissionState(\"A\", \"B\")\n\n        // Then: Correct flow should be returned\n        assertEquals(expectedFlow, actualFlow)\n    }\n\n    @Test\n    fun testNotifyPermissionsChanged() {\n        // Given: Permissions whose state to be notified\n        val permissions = arrayOf(\"A\", \"B\", \"C\")\n\n        // When: Permission state changes are notified\n        permissionFlow.notifyPermissionsChanged(*permissions)\n\n        // Then: Watchmen should be get notified about permission changes\n        verify(exactly = 1) { watchmen.notifyPermissionsChanged(permissions) }\n    }\n\n    @Test\n    fun testStartListening() {\n        // When: Starts listening\n        permissionFlow.startListening()\n\n        // Then: Watchmen should wake up\n        verify(exactly = 1) { watchmen.wakeUp() }\n    }\n\n    @Test\n    fun testStopListening() {\n        // When: Stops listening\n        permissionFlow.stopListening()\n\n        // Then: Watchmen should sleep\n        verify(exactly = 1) { watchmen.sleep() }\n    }\n}\n"
  },
  {
    "path": "permission-flow/src/test/java/dev/shreyaspatil/permissionFlow/initializer/PermissionFlowInitializerTest.kt",
    "content": "/**\n * Copyright 2022 Shreyas Patil\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage dev.shreyaspatil.permissionFlow.initializer\n\nimport android.content.Context\nimport android.os.Build\nimport androidx.test.core.app.ApplicationProvider\nimport dev.shreyaspatil.permissionFlow.PermissionFlow\nimport io.mockk.clearAllMocks\nimport io.mockk.every\nimport io.mockk.mockk\nimport io.mockk.mockkObject\nimport io.mockk.verifySequence\nimport org.junit.After\nimport org.junit.Assert\nimport org.junit.Before\nimport org.junit.Test\nimport org.junit.runner.RunWith\nimport org.robolectric.RobolectricTestRunner\nimport org.robolectric.annotation.Config\n\n@RunWith(RobolectricTestRunner::class)\n@Config(sdk = [Build.VERSION_CODES.P])\nclass PermissionFlowInitializerTest {\n    private lateinit var initializer: PermissionFlowInitializer\n    private lateinit var permissionFlow: PermissionFlow\n\n    @Before\n    fun setUp() {\n        permissionFlow = mockk(relaxUnitFun = true)\n        mockkObject(PermissionFlow)\n        every { PermissionFlow.getInstance() } returns permissionFlow\n\n        initializer = PermissionFlowInitializer()\n    }\n\n    @After\n    fun tearDown() {\n        clearAllMocks()\n    }\n\n    @Test\n    fun testInitializer() {\n        // Given: A application context providing context\n        val context = ApplicationProvider.getApplicationContext<Context>()\n\n        // When: Initializer is created\n        initializer.create(context = context)\n\n        // Then: Permission flow should be initialized\n        verifySequence {\n            PermissionFlow.init(context)\n            PermissionFlow.getInstance()\n            permissionFlow.startListening()\n        }\n    }\n\n    @Test\n    fun testInitializerDependencies_shouldBeEmpty() {\n        Assert.assertTrue(initializer.dependencies().isEmpty())\n    }\n}\n"
  },
  {
    "path": "permission-flow/src/test/java/dev/shreyaspatil/permissionFlow/internal/ApplicationStateMonitorTest.kt",
    "content": "/**\n * Copyright 2022 Shreyas Patil\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage dev.shreyaspatil.permissionFlow.internal\n\nimport android.app.Activity\nimport android.app.Application\nimport android.content.pm.PackageManager\nimport android.os.Build\nimport androidx.core.app.ActivityCompat\nimport androidx.core.content.ContextCompat\nimport app.cash.turbine.test\nimport dev.shreyaspatil.permissionFlow.PermissionState\nimport io.mockk.Runs\nimport io.mockk.every\nimport io.mockk.just\nimport io.mockk.mockk\nimport io.mockk.mockkStatic\nimport io.mockk.slot\nimport io.mockk.verify\nimport junit.framework.TestCase.assertEquals\nimport junit.framework.TestCase.assertNull\nimport kotlinx.coroutines.test.runTest\nimport org.junit.Before\nimport org.junit.Test\nimport org.junit.runner.RunWith\nimport org.robolectric.RobolectricTestRunner\nimport org.robolectric.annotation.Config\n\n@RunWith(RobolectricTestRunner::class)\n@Config(sdk = [Build.VERSION_CODES.P])\nclass ApplicationStateMonitorTest {\n\n    private lateinit var monitor: ApplicationStateMonitor\n\n    private lateinit var application: Application\n    private val callbackSlot = slot<Application.ActivityLifecycleCallbacks>()\n    private val lifecycleCallbacks\n        get() = callbackSlot.captured\n\n    @Before\n    fun setUp() {\n        application =\n            mockk(relaxUnitFun = true) {\n                every { registerActivityLifecycleCallbacks(capture(callbackSlot)) } just Runs\n            }\n        monitor = ApplicationStateMonitor(application)\n    }\n\n    @Test\n    fun getPermissionState_returnGrantedPermissionState_andCurrentActivityNotPresent() {\n        // Given: No current activity\n        val permission = \"A\"\n        mockPermissions(permission to true)\n        val expectedPermissionState =\n            PermissionState(permission = permission, isGranted = true, isRationaleRequired = null)\n\n        // When: Permission state is retrieved\n        val actualPermissionState = monitor.getPermissionState(permission)\n\n        // Then: Correct permission state should be returned\n        assertEquals(expectedPermissionState, actualPermissionState)\n    }\n\n    @Test\n    fun getPermissionState_returnDeniedPermissionState_andCurrentActivityNotPresent() {\n        // Given: No current activity\n        val permission = \"A\"\n        mockPermissions(permission to false)\n        val expectedPermissionState =\n            PermissionState(permission = permission, isGranted = false, isRationaleRequired = null)\n\n        // When: Permission state is retrieved\n        val actualPermissionState = monitor.getPermissionState(permission)\n\n        // Then: Correct permission state should be returned\n        assertEquals(expectedPermissionState, actualPermissionState)\n    }\n\n    @Test\n    fun getPermissionState_returnGrantedAndValidRationale_whenCurrentActivityIsPresent() = runTest {\n        monitor.activityForegroundEvents.test {\n            // Given: current activity\n            val activity = activity()\n            lifecycleCallbacks.onActivityCreated(activity, null)\n\n            val permission = \"A\"\n            mockPermissions(permission to true)\n            mockPermissionRationale(permission to false)\n            val expectedPermissionState =\n                PermissionState(\n                    permission = permission, isGranted = true, isRationaleRequired = false)\n\n            // When: Permission state is retrieved\n            val actualPermissionState = monitor.getPermissionState(permission)\n\n            // Then: Correct permission state should be returned\n            assertEquals(expectedPermissionState, actualPermissionState)\n        }\n    }\n\n    @Test\n    fun getPermissionState_returnDeniedAndValidRationale_whenCurrentActivityIsPresent() = runTest {\n        monitor.activityForegroundEvents.test {\n            // Given: current activity\n            val activity = activity()\n            lifecycleCallbacks.onActivityCreated(activity, null)\n\n            val permission = \"A\"\n            mockPermissions(permission to false)\n            mockPermissionRationale(permission to true)\n            val expectedPermissionState =\n                PermissionState(\n                    permission = permission, isGranted = false, isRationaleRequired = true)\n\n            // When: Permission state is retrieved\n            val actualPermissionState = monitor.getPermissionState(permission)\n\n            // Then: Correct permission state should be returned\n            assertEquals(expectedPermissionState, actualPermissionState)\n        }\n    }\n\n    @Test\n    fun getPermissionDeniedState_returnValidRationale_whenCurrentActivityIsPresent() = runTest {\n        monitor.activityForegroundEvents.test {\n            // Given: current activity\n            val activity = activity()\n            lifecycleCallbacks.onActivityCreated(activity, null)\n\n            val permission = \"A\"\n            mockPermissions(permission to false)\n            mockPermissionRationale(permission to true)\n            val expectedPermissionState =\n                PermissionState(\n                    permission = permission, isGranted = false, isRationaleRequired = true)\n\n            // When: Permission state is retrieved\n            val actualPermissionState = monitor.getPermissionState(permission)\n\n            // Then: Correct permission state should be returned\n            assertEquals(expectedPermissionState, actualPermissionState)\n        }\n    }\n\n    @Test\n    fun testRegisterUnregisterCallback() = runTest {\n        monitor.activityForegroundEvents.test {\n            verify(exactly = 1) {\n                application.registerActivityLifecycleCallbacks(lifecycleCallbacks)\n            }\n            cancelAndIgnoreRemainingEvents()\n        }\n        verify(exactly = 1) { application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks) }\n    }\n\n    @Test\n    @Config(sdk = [Build.VERSION_CODES.Q])\n    fun shouldSetCurrentActivity_whenActivityIsPreCreated() = runTest {\n        monitor.activityForegroundEvents.test {\n            // Given: Activity not created\n            assertNull(monitor.getCurrentActivityReference())\n\n            // When: Activity is created\n            val activity = activity()\n            lifecycleCallbacks.onActivityPreCreated(activity, null)\n\n            // Then: Activity should be set\n            assertEquals(activity, monitor.getCurrentActivityReference()?.get())\n        }\n    }\n\n    @Test\n    fun shouldSetCurrentActivity_whenActivityIsCreated() = runTest {\n        monitor.activityForegroundEvents.test {\n            // Given: Activity not created\n            assertNull(monitor.getCurrentActivityReference())\n\n            // When: Activity is created\n            val activity = activity()\n            lifecycleCallbacks.onActivityCreated(activity, null)\n\n            // Then: Activity should be set\n            assertEquals(activity, monitor.getCurrentActivityReference()?.get())\n        }\n    }\n\n    @Test\n    fun shouldSetCurrentActivity_whenAnotherActivityIsCreated() = runTest {\n        monitor.activityForegroundEvents.test {\n            // Given: Activity not created\n            assertNull(monitor.getCurrentActivityReference())\n\n            // When: Activity is created\n            val activity1 = activity()\n            val activity2 = activity()\n            lifecycleCallbacks.onActivityCreated(activity1, null)\n            lifecycleCallbacks.onActivityCreated(activity2, null)\n\n            // Then: Latest created activity should be set as current\n            assertEquals(activity2, monitor.getCurrentActivityReference()?.get())\n        }\n    }\n\n    @Test\n    @Config(sdk = [Build.VERSION_CODES.Q])\n    fun shouldSetCurrentActivity_whenAnotherActivityIsCreatedInApi29() = runTest {\n        monitor.activityForegroundEvents.test {\n            // Given: Activity not created\n            assertNull(monitor.getCurrentActivityReference())\n\n            // When: Activity is created\n            val activity = activity()\n            lifecycleCallbacks.onActivityPreCreated(activity, null)\n            val previousRef = monitor.getCurrentActivityReference()\n            lifecycleCallbacks.onActivityCreated(activity, null)\n            val afterRef = monitor.getCurrentActivityReference()\n\n            // Then: Reference should be same\n            assertEquals(previousRef, afterRef)\n        }\n    }\n\n    @Test\n    fun shouldClearCurrentActivity_whenActivityIsDestroyed() = runTest {\n        monitor.activityForegroundEvents.test {\n            // Given: Activity created\n            val activity = activity()\n            lifecycleCallbacks.onActivityCreated(activity, null)\n\n            // When: Activity is destroyed\n            lifecycleCallbacks.onActivityDestroyed(activity)\n\n            // Then: Activity should be cleared\n            assertNull(monitor.getCurrentActivityReference()?.get())\n        }\n    }\n\n    @Test\n    fun shouldNotClearCurrentActivity_whenAnotherActivityIsDestroyed() = runTest {\n        monitor.activityForegroundEvents.test {\n            // Given: Activity created\n            val activity1 = activity()\n            val activity2 = activity()\n\n            // When: One activity is created and other is destroyed\n            lifecycleCallbacks.onActivityCreated(activity1, null)\n            lifecycleCallbacks.onActivityDestroyed(activity2)\n\n            // Then: Activity1 should be present\n            assertEquals(activity1, monitor.getCurrentActivityReference()?.get())\n        }\n    }\n\n    @Test\n    fun shouldNotEmitEvent_whenActivityIsStartedWithConfigChanges() = runTest {\n        monitor.activityForegroundEvents.test {\n            // Before onStart(), onStop() should be called first\n            lifecycleCallbacks.onActivityStopped(activity(isChangingConfigurations = true))\n            lifecycleCallbacks.onActivityStarted(activity())\n\n            // Event should not get emitted\n            expectNoEvents()\n        }\n    }\n\n    @Test\n    fun shouldEmitEvent_whenActivityIsStarted_andNoConfigChanges() = runTest {\n        monitor.activityForegroundEvents.test {\n            // Before onStart(), onStop() should be called first\n            lifecycleCallbacks.onActivityStopped(activity(isChangingConfigurations = false))\n            lifecycleCallbacks.onActivityStarted(activity())\n\n            // Event should get emitted\n            awaitItem()\n        }\n    }\n\n    @Test\n    @Config(sdk = [Build.VERSION_CODES.N])\n    fun shouldEmitEvent_whenActivityIsResumedAfterExitingFromInMultiWindowMode() = runTest {\n        monitor.activityForegroundEvents.test {\n            // Before onStart(), onStop() should be called first\n            lifecycleCallbacks.onActivityPaused(activity(isInMultiWindowMode = true))\n            lifecycleCallbacks.onActivityResumed(activity(isInMultiWindowMode = false))\n\n            // Event should get emitted\n            awaitItem()\n        }\n    }\n\n    @Test\n    @Config(sdk = [Build.VERSION_CODES.M])\n    fun shouldNotEmitEvent_whenActivityIsResumedAfterPaused_onAndroidM() = runTest {\n        monitor.activityForegroundEvents.test {\n            // Before onStart(), onStop() should be called first\n            lifecycleCallbacks.onActivityPaused(mockk())\n            lifecycleCallbacks.onActivityResumed(mockk())\n\n            // Event should get emitted\n            expectNoEvents()\n        }\n    }\n\n    @Test\n    @Config(sdk = [Build.VERSION_CODES.N])\n    fun shouldNotEmitEvent_whenActivityIsResumedButStillInMultiWindowMode() = runTest {\n        monitor.activityForegroundEvents.test {\n            // Before onStart(), onStop() should be called first\n            lifecycleCallbacks.onActivityPaused(activity(isInMultiWindowMode = true))\n            lifecycleCallbacks.onActivityResumed(activity(isInMultiWindowMode = true))\n\n            // Event should not get emitted\n            expectNoEvents()\n        }\n    }\n\n    @Test\n    @Config(sdk = [Build.VERSION_CODES.N])\n    fun shouldEmitEvent_whenActivityIsResumedAfterExitingFromPiPMode() = runTest {\n        monitor.activityForegroundEvents.test {\n            // Before onStart(), onStop() should be called first\n            lifecycleCallbacks.onActivityPaused(activity(isInPictureInPictureMode = true))\n            lifecycleCallbacks.onActivityResumed(activity(isInPictureInPictureMode = false))\n\n            // Event should get emitted\n            awaitItem()\n        }\n    }\n\n    @Test\n    @Config(sdk = [Build.VERSION_CODES.N])\n    fun shouldNotEmitEvent_whenActivityIsResumedButStillInPiPMode() = runTest {\n        monitor.activityForegroundEvents.test {\n            // Before onStart(), onStop() should be called first\n            lifecycleCallbacks.onActivityPaused(activity(isInPictureInPictureMode = true))\n            lifecycleCallbacks.onActivityResumed(activity(isInPictureInPictureMode = true))\n\n            // Event should not get emitted\n            expectNoEvents()\n        }\n    }\n\n    /** Activity factory function */\n    private fun activity(\n        isChangingConfigurations: Boolean = false,\n        isInMultiWindowMode: Boolean = false,\n        isInPictureInPictureMode: Boolean = false,\n    ): Activity = mockk {\n        every { isChangingConfigurations() } returns isChangingConfigurations\n        every { isInMultiWindowMode() } returns isInMultiWindowMode\n        every { isInPictureInPictureMode() } returns isInPictureInPictureMode\n    }\n\n    /** Mocks permission state i.e. granted / denied. */\n    private fun mockPermissions(vararg permissionStates: Pair<String, Boolean>) {\n        mockkStatic(ContextCompat::checkSelfPermission)\n        permissionStates.forEach { (permission, isGranted) ->\n            every { ContextCompat.checkSelfPermission(any(), permission) } returns\n                if (isGranted) {\n                    PackageManager.PERMISSION_GRANTED\n                } else {\n                    PackageManager.PERMISSION_DENIED\n                }\n        }\n    }\n\n    /** Mocks permission rationale state i.e. should shown or not */\n    private fun mockPermissionRationale(vararg permissionStates: Pair<String, Boolean>) {\n        mockkStatic(ActivityCompat::shouldShowRequestPermissionRationale)\n        permissionStates.forEach { (permission, shouldShow) ->\n            every { ActivityCompat.shouldShowRequestPermissionRationale(any(), permission) } returns\n                shouldShow\n        }\n    }\n}\n"
  },
  {
    "path": "permission-flow/src/test/java/dev/shreyaspatil/permissionFlow/utils/ActivityResultLauncherExtTest.kt",
    "content": "/**\n * Copyright 2022 Shreyas Patil\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage dev.shreyaspatil.permissionFlow.utils\n\nimport androidx.activity.result.ActivityResultLauncher\nimport io.mockk.mockk\nimport io.mockk.verify\nimport org.junit.Test\n\nclass ActivityResultLauncherExtTest {\n\n    @Test\n    fun testLaunch() {\n        val activityResultLauncher: ActivityResultLauncher<Array<String>> =\n            mockk(\n                relaxUnitFun = true,\n            )\n\n        activityResultLauncher.launch(\"A\", \"B\", \"C\")\n\n        verify { activityResultLauncher.launch(arrayOf(\"A\", \"B\", \"C\")) }\n    }\n}\n"
  },
  {
    "path": "permission-flow/src/test/java/dev/shreyaspatil/permissionFlow/utils/PermissionResultLauncherTest.kt",
    "content": "/**\n * Copyright 2022 Shreyas Patil\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage dev.shreyaspatil.permissionFlow.utils\n\nimport androidx.activity.ComponentActivity\nimport androidx.activity.result.ActivityResultCallback\nimport androidx.activity.result.ActivityResultLauncher\nimport androidx.activity.result.ActivityResultRegistry\nimport androidx.activity.result.contract.ActivityResultContract\nimport androidx.fragment.app.Fragment\nimport dev.shreyaspatil.permissionFlow.contract.RequestPermissionsContract\nimport io.mockk.every\nimport io.mockk.mockk\nimport io.mockk.verify\nimport org.junit.Test\n\nclass PermissionResultLauncherTest {\n\n    @Test\n    fun test_Activity_registerForPermissionFlowRequestsResult_default() {\n        val activityProvidedResultRegistry = mockk<ActivityResultRegistry>()\n        val activity =\n            mockk<ComponentActivity> {\n                every {\n                    registerForActivityResult(\n                        any<ActivityResultContract<Array<String>, Map<String, Boolean>>>(),\n                        any(),\n                        any<ActivityResultCallback<Map<String, Boolean>>>(),\n                    )\n                } returns mockk<ActivityResultLauncher<Array<String>>>()\n\n                every { activityResultRegistry } returns activityProvidedResultRegistry\n            }\n\n        activity.registerForPermissionFlowRequestsResult(mockk())\n\n        verify(exactly = 1) {\n            activity.registerForActivityResult(\n                any<RequestPermissionsContract>(), activityProvidedResultRegistry, any())\n        }\n    }\n\n    @Test\n    fun test_Activity_registerForPermissionFlowRequestsResult_withProvidedActivityResultRegistry() {\n        val activityResultRegistry = mockk<ActivityResultRegistry>()\n        val activity =\n            mockk<ComponentActivity> {\n                every {\n                    registerForActivityResult(\n                        any<ActivityResultContract<Array<String>, Map<String, Boolean>>>(),\n                        any(),\n                        any<ActivityResultCallback<Map<String, Boolean>>>(),\n                    )\n                } returns mockk<ActivityResultLauncher<Array<String>>>()\n            }\n\n        activity.registerForPermissionFlowRequestsResult(\n            mockk(), activityResultRegistry = activityResultRegistry)\n\n        verify(exactly = 1) {\n            activity.registerForActivityResult(\n                any<RequestPermissionsContract>(), activityResultRegistry, any())\n        }\n    }\n\n    @Test\n    fun test_Fragment_registerForPermissionFlowRequestsResult() {\n        val fragment =\n            mockk<Fragment> {\n                every {\n                    registerForActivityResult(\n                        any<ActivityResultContract<Array<String>, Map<String, Boolean>>>(),\n                        any(),\n                        any<ActivityResultCallback<Map<String, Boolean>>>(),\n                    )\n                } returns mockk<ActivityResultLauncher<Array<String>>>()\n            }\n\n        fragment.registerForPermissionFlowRequestsResult(mockk(), mockk(), mockk())\n\n        verify(exactly = 1) {\n            fragment.registerForActivityResult(any<RequestPermissionsContract>(), any(), any())\n        }\n    }\n\n    @Test\n    fun test_Fragment_registerForPermissionFlowRequestsResult_withoutActivityRegistry() {\n        val fragment =\n            mockk<Fragment> {\n                every {\n                    registerForActivityResult(\n                        any<ActivityResultContract<Array<String>, Map<String, Boolean>>>(),\n                        any<ActivityResultCallback<Map<String, Boolean>>>(),\n                    )\n                } returns mockk<ActivityResultLauncher<Array<String>>>()\n            }\n\n        val requestPermissionsContract = mockk<RequestPermissionsContract>()\n        val callback = mockk<ActivityResultCallback<Map<String, Boolean>>>()\n\n        fragment.registerForPermissionFlowRequestsResult(\n            requestPermissionsContract = requestPermissionsContract,\n            callback = callback,\n        )\n\n        verify(exactly = 1) {\n            fragment.registerForActivityResult(requestPermissionsContract, callback)\n        }\n    }\n}\n"
  },
  {
    "path": "permission-flow/src/test/java/dev/shreyaspatil/permissionFlow/utils/stateFlow/CombinedStateFlowTest.kt",
    "content": "/**\n * Copyright 2022 Shreyas Patil\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage dev.shreyaspatil.permissionFlow.utils.stateFlow\n\nimport app.cash.turbine.test\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.test.runTest\nimport org.junit.Assert.assertEquals\nimport org.junit.Test\n\nclass CombinedStateFlowTest {\n    @Test\n    fun combineStates_shouldReturnValidValueInitially() {\n        // Given: Individual state flows\n        val intState = MutableStateFlow(0)\n        val stringState = MutableStateFlow(\"\")\n        val booleanState = MutableStateFlow(true)\n\n        // When: State flows are combined\n        val state =\n            combineStates(intState, stringState, booleanState) { (s1, s2, s3) ->\n                TestState(s1 as Int, s2 as String, s3 as Boolean)\n            }\n\n        // Then: Combined state should have valid initial value\n        val expectedState = TestState(intValue = 0, stringValue = \"\", booleanValue = true)\n        assertEquals(expectedState, state.value)\n    }\n\n    @Test\n    fun combineStates_shouldUpdateValue_whenIndividualStateIsUpdated() {\n        // Given: Individual state flows combined into another state\n        val intState = MutableStateFlow(0)\n        val stringState = MutableStateFlow(\"\")\n        val booleanState = MutableStateFlow(true)\n\n        val state =\n            combineStates(intState, stringState, booleanState) { (s1, s2, s3) ->\n                TestState(s1 as Int, s2 as String, s3 as Boolean)\n            }\n\n        // When: Individual states are updated\n        intState.value = 10\n        stringState.value = \"Test\"\n        booleanState.value = false\n\n        // Then: Combined state should have valid updated value\n        val expectedState = TestState(intValue = 10, stringValue = \"Test\", booleanValue = false)\n        assertEquals(expectedState, state.value)\n    }\n\n    @Test\n    fun combineStates_shouldReturnRecentValueAsCache() {\n        // Given: Individual state flows combined into another state\n        val intState = MutableStateFlow(0)\n        val stringState = MutableStateFlow(\"\")\n        val booleanState = MutableStateFlow(true)\n\n        val state =\n            combineStates(intState, stringState, booleanState) { (s1, s2, s3) ->\n                TestState(s1 as Int, s2 as String, s3 as Boolean)\n            }\n\n        // When: Individual states are updated\n        intState.value = 10\n        stringState.value = \"Test\"\n        booleanState.value = false\n\n        // Then: Combined state should have valid updated value\n        val expectedCacheItem = TestState(intValue = 10, stringValue = \"Test\", booleanValue = false)\n        assertEquals(listOf(expectedCacheItem), state.replayCache)\n    }\n\n    @Test\n    fun combineStates_shouldEmitInCollector_whenIndividualStateIsUpdated() = runTest {\n        // Given: Individual state flows combined into another state\n        val intState = MutableStateFlow(0)\n        val stringState = MutableStateFlow(\"\")\n        val booleanState = MutableStateFlow(true)\n\n        val state =\n            combineStates(intState, stringState, booleanState) { (s1, s2, s3) ->\n                TestState(s1 as Int, s2 as String, s3 as Boolean)\n            }\n\n        // When: Individual states are collected\n        state.test {\n            // Then: Valid initial state should be emitted\n            assertEquals(\n                TestState(intValue = 0, stringValue = \"\", booleanValue = true),\n                awaitItem(),\n            )\n\n            // Then: Updated state should be emitted on updating individual state\n            intState.value = 10\n            stringState.value = \"Test\"\n            booleanState.value = false\n\n            // Since we updated three values, test on third event.\n            // This is because of StateFlow's conflated behaviour\n            // So intentionally receive events twice with `awaitItem()`\n            awaitItem()\n            awaitItem()\n\n            assertEquals(\n                TestState(intValue = 10, stringValue = \"Test\", booleanValue = false),\n                awaitItem(),\n            )\n\n            cancelAndIgnoreRemainingEvents()\n        }\n    }\n\n    /**\n     * Sample class used to test combining different StateFlows into transformed StateFlow of this\n     * class.\n     */\n    private data class TestState(\n        val intValue: Int,\n        val stringValue: String,\n        val booleanValue: Boolean,\n    )\n}\n"
  },
  {
    "path": "permission-flow/src/test/java/dev/shreyaspatil/permissionFlow/watchmen/PermissionWatchmenTest.kt",
    "content": "/**\n * Copyright 2022 Shreyas Patil\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage dev.shreyaspatil.permissionFlow.watchmen\n\nimport dev.shreyaspatil.permissionFlow.PermissionState\nimport dev.shreyaspatil.permissionFlow.internal.ApplicationStateMonitor\nimport io.mockk.every\nimport io.mockk.mockk\nimport io.mockk.verify\nimport kotlinx.coroutines.ExperimentalCoroutinesApi\nimport kotlinx.coroutines.channels.BufferOverflow\nimport kotlinx.coroutines.flow.MutableSharedFlow\nimport kotlinx.coroutines.test.StandardTestDispatcher\nimport kotlinx.coroutines.test.TestScope\nimport kotlinx.coroutines.test.advanceUntilIdle\nimport kotlinx.coroutines.test.runTest\nimport org.junit.Assert.assertEquals\nimport org.junit.Assert.assertFalse\nimport org.junit.Assert.assertTrue\nimport org.junit.Before\nimport org.junit.Test\n\n@OptIn(ExperimentalCoroutinesApi::class)\nclass PermissionWatchmenTest {\n\n    private val dispatcher = StandardTestDispatcher()\n    private lateinit var applicationStateMonitor: ApplicationStateMonitor\n\n    private lateinit var watchmen: PermissionWatchmen\n\n    private lateinit var foregroundEvents: MutableSharedFlow<Unit>\n\n    @Before\n    fun setUp() {\n        foregroundEvents =\n            MutableSharedFlow(\n                extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)\n        applicationStateMonitor =\n            mockk(relaxed = true) { every { activityForegroundEvents } returns foregroundEvents }\n        watchmen = PermissionWatchmen(applicationStateMonitor, dispatcher)\n    }\n\n    @Test\n    fun shouldWakeUpAndReturnFlow_whenWatchPermissionForTheFirstTime() {\n        // Given: Permission\n        val permission = \"permission\"\n        mockPermissions(permission to true)\n\n        // When: Starts watching permission for the first time.\n        val flow = watchmen.watchState(permission)\n\n        // Then: StateFlow should be returned with valid value i.e. true (Granted).\n        assertTrue(flow.value.isGranted)\n\n        // Then: Should start watching activity foreground events.\n        dispatcher.scheduler.runCurrent()\n        verify(exactly = 1) { applicationStateMonitor.activityForegroundEvents }\n        assertEquals(1, foregroundEvents.subscriptionCount.value)\n    }\n\n    @Test\n    fun shouldWakeUpAndReturnFlow_whenWatchMultiplePermissionForTheFirstTime() {\n        // Given: Multiple Permission\n        val permission1 = \"permission-1\"\n        val permission2 = \"permission-2\"\n        mockPermissions(permission1 to true, permission2 to false)\n\n        // When: Starts watching multiple permission for the first time.\n        val flow = watchmen.watchMultipleState(arrayOf(permission1, permission2))\n\n        // Then: StateFlow should be returned with valid value i.e. true (Granted).\n        assertTrue(flow.value.permissions[0].isGranted)\n        assertFalse(flow.value.permissions[1].isGranted)\n\n        // Then: Should start watching activity foreground events.\n        dispatcher.scheduler.runCurrent()\n        verify(exactly = 1) { applicationStateMonitor.activityForegroundEvents }\n        assertEquals(1, foregroundEvents.subscriptionCount.value)\n    }\n\n    @Test\n    fun shouldReturnFilteredMultiplePermissionState_whenDuplicatePermissionsAreWatched() {\n        // Given: Multiple Permission\n        val permission1 = \"permission-1\"\n        val permission2 = \"permission-2\"\n        mockPermissions(permission1 to true, permission2 to false)\n\n        // When: Starts watching multiple permission having list of repeated permissions\n        val flow = watchmen.watchMultipleState(arrayOf(permission1, permission1, permission2))\n\n        // Then: State should only contain list having two items\n        assertEquals(flow.value.permissions.size, 2)\n        assertEquals(flow.value.permissions.map { it.permission }, listOf(permission1, permission2))\n    }\n\n    @Test\n    fun shouldReturnSameInstance_whenWatchingPermissionMoreThanOnce() {\n        // Given: A permission to be observed\n        val permission = \"permission\"\n        mockPermissions(permission to true)\n\n        // When: A permission state is watched more than once\n        val flow1 = watchmen.watchState(permission)\n        val flow2 = watchmen.watchState(permission)\n\n        // Then: Same instance should be returned\n        assert(flow1 === flow2)\n    }\n\n    @Test\n    fun shouldUpdateFlowState_whenPermissionChangesAreNotified() {\n        // Given: Watching a permission flow\n        val permission = \"permission\"\n        mockPermissions(permission to true)\n        val state = watchmen.watchState(permission)\n\n        // When: Change in state is notified for the same permission\n        mockPermissions(permission to false)\n        watchmen.notifyPermissionsChanged(permissions = arrayOf(permission))\n\n        // Then: Current value of flow should be false i.e. Not granted\n        dispatcher.scheduler.runCurrent()\n        assertFalse(state.value.isGranted)\n    }\n\n    @Test\n    fun shouldUpdateMultiplePermissionFlowState_whenPermissionChangesAreNotified() {\n        // Given: Watching multiple permission state\n        val permission1 = \"permission-1\"\n        val permission2 = \"permission-2\"\n        mockPermissions(permission1 to true, permission2 to false)\n\n        val flow = watchmen.watchMultipleState(arrayOf(permission1, permission2))\n\n        // When: Change in state is notified for the these permission\n        mockPermissions(permission2 to true)\n        watchmen.notifyPermissionsChanged(permissions = arrayOf(permission2))\n\n        // Then: All permissions should be granted\n        dispatcher.scheduler.runCurrent()\n        assertTrue(flow.value.allGranted)\n    }\n\n    @Test\n    fun shouldUpdatePermissionFlowState_whenWatchmenWakesAfterSleeping() {\n        // Given: Watching a permission\n        val permission = \"permission\"\n        mockPermissions(permission to true)\n        val flow = watchmen.watchState(permission)\n\n        // When: Watchmen sleeps, permission state changes and watchmen wakes after that\n        watchmen.sleep()\n        mockPermissions(permission to false)\n        watchmen.wakeUp()\n\n        // Then: Permission state should be get updated\n        dispatcher.scheduler.advanceUntilIdle()\n        assertFalse(flow.value.isGranted)\n    }\n\n    @Test\n    fun shouldUpdateMultiplePermissionFlowState_whenWatchmenWakesAfterSleeping() {\n        // Given: Watching multiple permissions\n        val permission1 = \"permission-1\"\n        val permission2 = \"permission-2\"\n        mockPermissions(permission1 to true, permission2 to false)\n        val flow = watchmen.watchMultipleState(arrayOf(permission1, permission2))\n\n        // When: Watchmen sleeps, permission state changes and watchmen wakes after that\n        watchmen.sleep()\n        mockPermissions(permission1 to true, permission2 to true)\n        watchmen.wakeUp()\n\n        // Then: Permission state should be get updated\n        dispatcher.scheduler.advanceUntilIdle()\n        assertTrue(flow.value.permissions[1].isGranted)\n    }\n\n    @Test\n    fun shouldNotUpdateFlowState_whenPermissionChangesAreNotifiedAndWatchmenIsSleeping() {\n        // Given: Watching a permission flow and watchmen is sleeping\n        val permission = \"permission\"\n        mockPermissions(permission to true)\n        val flow = watchmen.watchState(permission)\n        watchmen.sleep()\n\n        // When: Change in state is notified for the same permission\n        mockPermissions(permission to false)\n        watchmen.notifyPermissionsChanged(permissions = arrayOf(permission))\n\n        // Then: Current value of flow should not be changed i.e. it should remain as Granted\n        dispatcher.scheduler.runCurrent()\n        assertTrue(flow.value.isGranted)\n    }\n\n    @Test\n    fun shouldNotUpdateMultipleFlowState_whenPermissionChangesAreNotifiedAndWatchmenIsSleeping() {\n        // Given: Watching a permission flow and watchmen is sleeping\n        val permission1 = \"permission-1\"\n        val permission2 = \"permission-2\"\n        mockPermissions(permission1 to true, permission2 to false)\n        val flow = watchmen.watchMultipleState(arrayOf(permission1, permission2))\n        watchmen.sleep()\n\n        // When: Change in state is notified for the same permission\n        mockPermissions(permission2 to true)\n        watchmen.notifyPermissionsChanged(permissions = arrayOf(permission2))\n\n        // Then: Current value of flow should not be changed i.e. it should remain as Granted\n        dispatcher.scheduler.runCurrent()\n        assertFalse(flow.value.permissions[1].isGranted)\n    }\n\n    @Test\n    fun shouldStartObservingActivity_whenWakingUp() {\n        // When: Request watchmen to wake-up\n        watchmen.wakeUp()\n\n        // Then: Should start watching activity foreground events\n        dispatcher.scheduler.runCurrent()\n        verify(exactly = 1) { applicationStateMonitor.activityForegroundEvents }\n        assertEquals(1, foregroundEvents.subscriptionCount.value)\n    }\n\n    @Test\n    fun shouldStartObservingActivityOnceOnce_whenWakingUpMultipleTimes() {\n        // When: Request watchmen to wake-up twice\n        watchmen.wakeUp()\n        dispatcher.scheduler.runCurrent()\n\n        watchmen.wakeUp()\n        dispatcher.scheduler.runCurrent()\n\n        // Then: Should start watching activity foreground events only once\n        verify(exactly = 1) { applicationStateMonitor.activityForegroundEvents }\n        assertEquals(1, foregroundEvents.subscriptionCount.value)\n    }\n\n    @Test\n    fun shouldStopObservingActivityEvent_whenSleeping() {\n        // When: Requests watchmen to sleep after being waking up\n        watchmen.wakeUp()\n        dispatcher.scheduler.runCurrent()\n        watchmen.sleep()\n\n        // Then: Should stop watching activity foreground events\n        dispatcher.scheduler.runCurrent()\n        verify(exactly = 1) { applicationStateMonitor.activityForegroundEvents }\n\n        // Then: Subscription should be removed\n        assertEquals(0, foregroundEvents.subscriptionCount.value)\n    }\n\n    @Test\n    fun shouldNotifyAllPermissionChanges_whenActivityForegroundEventIsReceived() = runTest {\n        // Given: Watching permissions\n        mockPermissions(\"permission-1\" to false, \"permission-2\" to false, \"permission-3\" to false)\n        val permissionFlow1 = watchmen.watchState(\"permission-1\")\n        val permissionFlow2 = watchmen.watchState(\"permission-2\")\n        val permissionFlow3 = watchmen.watchState(\"permission-3\")\n        advanceUntilIdle()\n\n        // When: Permission state is changed and activity foreground event is received\n        mockPermissions(\"permission-1\" to true, \"permission-2\" to true, \"permission-3\" to true)\n        // and When: Application is in foreground\n        foregroundEvents.tryEmit(Unit)\n        advanceUntilIdle()\n\n        // Then: Permission state for all active flows should be get updated after debounce time.\n        assertTrue(permissionFlow1.value.isGranted)\n        assertTrue(permissionFlow2.value.isGranted)\n        assertTrue(permissionFlow3.value.isGranted)\n    }\n\n    /** Mocks permission state i.e. granted / denied. */\n    private fun mockPermissions(vararg permissionStates: Pair<String, Boolean>) {\n        permissionStates.forEach { (permission, isGranted) ->\n            every { applicationStateMonitor.getPermissionState(permission) } returns\n                PermissionState(\n                    permission = permission, isGranted = isGranted, isRationaleRequired = false)\n        }\n    }\n\n    private fun runTest(testBody: suspend TestScope.() -> Unit) =\n        runTest(\n            context = dispatcher,\n            testBody = testBody,\n        )\n}\n"
  },
  {
    "path": "permission-flow-compose/.gitignore",
    "content": "/build"
  },
  {
    "path": "permission-flow-compose/build.gradle",
    "content": "plugins {\n    id 'com.android.library'\n    id 'org.jetbrains.kotlin.android'\n    id 'org.jetbrains.kotlin.plugin.compose'\n    id 'org.jetbrains.dokka'\n    id 'com.vanniktech.maven.publish'\n}\n\nkotlin {\n    jvmToolchain(17)\n}\n\nandroid {\n    compileSdk 35\n\n    defaultConfig {\n        minSdk 21\n\n        testInstrumentationRunner \"androidx.test.runner.AndroidJUnitRunner\"\n        consumerProguardFiles \"consumer-rules.pro\"\n    }\n\n    buildTypes {\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'\n        }\n    }\n\n    buildFeatures {\n        buildConfig false\n        compose true\n    }\n\n    packagingOptions {\n        resources {\n            excludes += '/META-INF/{AL2.0,LGPL2.1}'\n        }\n    }\n    namespace 'dev.shreyaspatil.permissionflow.compose'\n}\n\ndependencies {\n    api project(\":permission-flow\")\n\n    implementation platform(\"androidx.compose:compose-bom:$composeBomVersion\")\n    implementation \"androidx.compose.runtime:runtime\"\n    implementation \"androidx.activity:activity-compose:$activityVersion\"\n\n    testImplementation 'junit:junit:4.13.2'\n    androidTestImplementation 'androidx.test.ext:junit:1.1.5'\n    androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'\n}\n\ndokkaHtml.configure {\n    dokkaSourceSets {\n        named(\"main\") {\n            noAndroidSdkLink.set(false)\n        }\n    }\n}\n"
  },
  {
    "path": "permission-flow-compose/consumer-rules.pro",
    "content": ""
  },
  {
    "path": "permission-flow-compose/gradle.properties",
    "content": "POM_ARTIFACT_ID=permission-flow-compose\nPOM_NAME=Permission Flow for Android and Jetpack Compose\nPOM_DESCRIPTION=Know about real-time state of a Android app Permissions with Jetpack Compose APIs."
  },
  {
    "path": "permission-flow-compose/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile"
  },
  {
    "path": "permission-flow-compose/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest>\n\n</manifest>"
  },
  {
    "path": "permission-flow-compose/src/main/java/dev/shreyaspatil/permissionflow/compose/PermissionFlow.kt",
    "content": "/**\n * Copyright 2022 Shreyas Patil\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage dev.shreyaspatil.permissionflow.compose\n\nimport androidx.activity.compose.ManagedActivityResultLauncher\nimport androidx.activity.compose.rememberLauncherForActivityResult\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.State\nimport androidx.compose.runtime.collectAsState\nimport androidx.compose.runtime.remember\nimport dev.shreyaspatil.permissionFlow.MultiplePermissionState\nimport dev.shreyaspatil.permissionFlow.PermissionFlow\nimport dev.shreyaspatil.permissionFlow.PermissionState\nimport dev.shreyaspatil.permissionFlow.contract.RequestPermissionsContract\n\n/**\n * Creates a [PermissionState] for a [permission] that is remembered across compositions.\n *\n * Example:\n * ```\n *  @Composable\n *  fun PermissionDemo() {\n *      val state by rememberPermissionState(Manifest.permission.CAMERA)\n *      if (state.isGranted) {\n *          // Render something\n *      } else {\n *          if (state.isRationaleRequired) {\n *              // Show rationale\n *          }\n *          // Render something else\n *      }\n *  }\n * ```\n *\n * @param permission The permission to observe.\n * @throws IllegalStateException If [PermissionFlow] is not initialized\n */\n@Composable\nfun rememberPermissionState(\n    permission: String,\n): State<PermissionState> =\n    remember { PermissionFlow.getInstance().getPermissionState(permission) }.collectAsState()\n\n/**\n * Creates a [MultiplePermissionState] for a multiple [permissions] that is remembered across\n * compositions.\n *\n * Example:\n * ```\n *  @Composable\n *  fun PermissionDemo() {\n *      val state by rememberMultiplePermissionState(\n *          Manifest.permission.CAMERA\n *          Manifest.permission.ACCESS_FINE_LOCATION,\n *          Manifest.permission.READ_CONTACTS\n *      )\n *\n *      if (state.allGranted) {\n *          // Render something\n *      }\n *\n *      val grantedPermissions = state.grantedPermissions\n *      // Do something with `grantedPermissions`\n *\n *      val deniedPermissions = state.deniedPermissions\n *      // Do something with `deniedPermissions`\n *  }\n * ```\n *\n * @param permissions The list of permissions to observe.\n * @throws IllegalStateException If [PermissionFlow] is not initialized\n */\n@Composable\nfun rememberMultiplePermissionState(\n    vararg permissions: String,\n): State<MultiplePermissionState> =\n    remember { PermissionFlow.getInstance().getMultiplePermissionState(*permissions) }\n        .collectAsState()\n\n/**\n * Creates a [ManagedActivityResultLauncher] that is tied with [PermissionFlow] APIs and remembered\n * across recompositions.\n *\n * Example:\n * ```\n *  @Composable\n *  fun DemoPermissionLaunch() {\n *      val permissionLauncher = rememberPermissionFlowRequestLauncher()\n *\n *      Button(onClick = { permissionLauncher.launch(Manifest.permission.CAMERA) }) {\n *          Text(\"Launch Camera Permission Request\")\n *      }\n *  }\n * ```\n *\n * Make sure to use [ManagedActivityResultLauncher.launch] method inside callback or a side effect\n * and not directly in the compose scope. Otherwise, it'll be invoked across recompositions.\n *\n * @param onResult The callback to invoke when the result is received.\n */\n@Composable\nfun rememberPermissionFlowRequestLauncher(\n    onResult: (Map<String, Boolean>) -> Unit = {},\n): ManagedActivityResultLauncher<Array<String>, Map<String, Boolean>> {\n    return rememberLauncherForActivityResult(\n        contract = RequestPermissionsContract(),\n        onResult = onResult,\n    )\n}\n"
  },
  {
    "path": "settings.gradle",
    "content": "pluginManagement {\n    repositories {\n        gradlePluginPortal()\n        google()\n        mavenCentral()\n    }\n}\ndependencyResolutionManagement {\n    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)\n    repositories {\n        google()\n        mavenCentral()\n    }\n}\n\nrootProject.name = \"PermissionFlow\"\ninclude ':app'\ninclude ':permission-flow'\ninclude ':permission-flow-compose'\n"
  },
  {
    "path": "spotless/copyright.kt",
    "content": "/**\n * Copyright 2022 Shreyas Patil\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n"
  }
]