Full Code of ssseasonnn/Butterfly for AI

master 29857872ee8b cached
221 files
299.7 KB
75.6k tokens
1 requests
Download .txt
Showing preview only (365K chars total). Download the full file or copy to clipboard to get everything.
Repository: ssseasonnn/Butterfly
Branch: master
Commit: 29857872ee8b
Files: 221
Total size: 299.7 KB

Directory structure:
gitextract_u_htjb9w/

├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── README.zh.md
├── annotation/
│   ├── .gitignore
│   ├── build.gradle.kts
│   └── src/
│       └── main/
│           └── java/
│               └── zlc/
│                   └── season/
│                       └── butterfly/
│                           ├── annotation/
│                           │   ├── Annotations.kt
│                           │   └── EvadeData.kt
│                           └── module/
│                               └── Module.kt
├── build.gradle.kts
├── buildLogic/
│   ├── .gitignore
│   ├── build.gradle.kts
│   ├── gradle/
│   │   └── wrapper/
│   │       ├── gradle-wrapper.jar
│   │       └── gradle-wrapper.properties
│   ├── gradlew
│   ├── gradlew.bat
│   ├── settings.gradle.kts
│   └── src/
│       └── main/
│           └── kotlin/
│               ├── BuildLogicPlugin.kt
│               └── zlc/
│                   └── season/
│                       └── buildlogic/
│                           └── base/
│                               ├── AndroidExtensions.kt
│                               ├── BaseExtensions.kt
│                               └── MavenExtensions.kt
├── butterfly/
│   ├── .gitignore
│   ├── build.gradle.kts
│   ├── consumer-rules.pro
│   ├── proguard-rules.pro
│   └── src/
│       ├── main/
│       │   ├── AndroidManifest.xml
│       │   └── java/
│       │       └── zlc/
│       │           └── season/
│       │               └── butterfly/
│       │                   ├── Butterfly.kt
│       │                   ├── ButterflyCore.kt
│       │                   ├── DestinationHandler.kt
│       │                   ├── action/
│       │                   │   └── Action.kt
│       │                   ├── core/
│       │                   │   ├── BackStackEntryManager.kt
│       │                   │   ├── EvadeManager.kt
│       │                   │   ├── GroupEntryManager.kt
│       │                   │   ├── InterceptorManager.kt
│       │                   │   ├── ModuleManager.kt
│       │                   │   └── NavigatorManager.kt
│       │                   ├── entities/
│       │                   │   ├── BackStackEntry.kt
│       │                   │   ├── DestinationData.kt
│       │                   │   ├── EvadeData.kt
│       │                   │   └── GroupEntry.kt
│       │                   ├── interceptor/
│       │                   │   ├── DefaultInterceptor.kt
│       │                   │   └── Interceptor.kt
│       │                   ├── internal/
│       │                   │   ├── ButterflyFragment.kt
│       │                   │   ├── ButterflyHelper.kt
│       │                   │   ├── LogUtil.kt
│       │                   │   └── Util.kt
│       │                   ├── launcher/
│       │                   │   ├── DestinationLauncher.kt
│       │                   │   └── DestinationLauncherManager.kt
│       │                   └── navigator/
│       │                       ├── ActionNavigator.kt
│       │                       ├── ActivityNavigator.kt
│       │                       ├── ErrorNavigator.kt
│       │                       ├── Navigator.kt
│       │                       └── fragment/
│       │                           ├── DialogFragmentNavigator.kt
│       │                           ├── FragmentHelper.kt
│       │                           ├── FragmentNavigator.kt
│       │                           ├── FragmentParamUpdatable.kt
│       │                           ├── NavigatorContext.kt
│       │                           ├── backstack/
│       │                           │   ├── BackstackNavigator.kt
│       │                           │   ├── BackstackNavigatorHelper.kt
│       │                           │   ├── ClearTopBackstackNavigator.kt
│       │                           │   ├── SingleTopBackstackNavigator.kt
│       │                           │   └── StandardBackstackNavigator.kt
│       │                           └── group/
│       │                               └── GroupNavigator.kt
│       └── test/
│           └── java/
│               └── zlc/
│                   └── season/
│                       └── butterfly/
│                           └── ExampleUnitTest.kt
├── butterfly-compose/
│   ├── .gitignore
│   ├── build.gradle.kts
│   ├── consumer-rules.pro
│   ├── proguard-rules.pro
│   └── src/
│       ├── main/
│       │   ├── AndroidManifest.xml
│       │   └── java/
│       │       └── zlc/
│       │           └── season/
│       │               └── butterfly/
│       │                   └── compose/
│       │                       ├── ComposeDestination.kt
│       │                       ├── ComposeNavigator.kt
│       │                       ├── ComposeViewModel.kt
│       │                       ├── DestinationViewModelStoreOwner.kt
│       │                       ├── Utils.kt
│       │                       └── navigator/
│       │                           ├── ComposeBackStackNavigator.kt
│       │                           ├── ComposeGroupNavigator.kt
│       │                           └── ComposeNavigatorHelper.kt
│       └── test/
│           └── java/
│               └── zlc/
│                   └── season/
│                       └── butterfly/
│                           └── compose/
│                               └── ExampleUnitTest.kt
├── compiler/
│   ├── .gitignore
│   ├── build.gradle.kts
│   └── src/
│       └── main/
│           ├── java/
│           │   └── zlc/
│           │       └── season/
│           │           └── butterfly/
│           │               └── compiler/
│           │                   ├── Entities.kt
│           │                   ├── ProcessorProvider.kt
│           │                   ├── generator/
│           │                   │   ├── ComposableGenerator.kt
│           │                   │   └── ModuleClassGenerator.kt
│           │                   ├── utils/
│           │                   │   ├── KspUtils.kt
│           │                   │   └── Util.kt
│           │                   └── visitor/
│           │                       ├── DestinationAnnotationVisitor.kt
│           │                       ├── EvadeAnnotationVisitor.kt
│           │                       └── EvadeImplAnnotationVisitor.kt
│           └── resources/
│               └── META-INF/
│                   └── services/
│                       └── com.google.devtools.ksp.processing.SymbolProcessorProvider
├── gradle/
│   ├── libs.versions.toml
│   └── wrapper/
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── jitpack.yml
├── plugin/
│   ├── .gitignore
│   ├── build.gradle.kts
│   └── src/
│       └── main/
│           ├── java/
│           │   └── zlc/
│           │       └── season/
│           │           └── butterfly/
│           │               └── plugin/
│           │                   ├── ButterflyPlugin.kt
│           │                   ├── ModuleClassVisitorFactory.kt
│           │                   └── Utils.kt
│           └── resources/
│               └── META-INF/
│                   └── gradle-plugins/
│                       └── io.github.ssseasonnn.butterfly.properties
├── samples/
│   ├── app/
│   │   ├── .gitignore
│   │   ├── build.gradle.kts
│   │   ├── proguard-rules.pro
│   │   └── src/
│   │       └── main/
│   │           ├── AndroidManifest.xml
│   │           ├── java/
│   │           │   └── zlc/
│   │           │       └── season/
│   │           │           └── butterflydemo/
│   │           │               ├── ComposeBottomNavigationActivity.kt
│   │           │               ├── ComposeDemoActivity.kt
│   │           │               ├── DemoApplication.kt
│   │           │               ├── DestinationTestActivity.kt
│   │           │               ├── EvadeTestActivity.kt
│   │           │               ├── FragmentBottomNavigationActivity.kt
│   │           │               ├── FragmentDemoActivity.kt
│   │           │               ├── Home.kt
│   │           │               └── MainActivity.kt
│   │           └── res/
│   │               ├── drawable/
│   │               │   ├── ic_dashboard_black_24dp.xml
│   │               │   ├── ic_home_black_24dp.xml
│   │               │   ├── ic_launcher_background.xml
│   │               │   └── ic_notifications_black_24dp.xml
│   │               ├── drawable-v24/
│   │               │   └── ic_launcher_foreground.xml
│   │               ├── layout/
│   │               │   ├── activity_compose_bottom_navigation.xml
│   │               │   ├── activity_compose_demo.xml
│   │               │   ├── activity_destination_test.xml
│   │               │   ├── activity_evade_test.xml
│   │               │   ├── activity_fragment_bottom_navigation.xml
│   │               │   ├── activity_fragment_demo.xml
│   │               │   └── activity_main.xml
│   │               ├── menu/
│   │               │   └── bottom_nav_menu.xml
│   │               ├── mipmap-anydpi-v26/
│   │               │   ├── ic_launcher.xml
│   │               │   └── ic_launcher_round.xml
│   │               ├── values/
│   │               │   ├── colors.xml
│   │               │   ├── dimens.xml
│   │               │   ├── strings.xml
│   │               │   └── themes.xml
│   │               └── xml/
│   │                   └── network_security_config.xml
│   └── modules/
│       ├── base/
│       │   ├── .gitignore
│       │   ├── build.gradle.kts
│       │   ├── consumer-rules.pro
│       │   ├── proguard-rules.pro
│       │   └── src/
│       │       └── main/
│       │           ├── AndroidManifest.xml
│       │           └── java/
│       │               └── zlc/
│       │                   └── season/
│       │                       └── base/
│       │                           ├── BaseActivity.kt
│       │                           ├── BaseFragment.kt
│       │                           └── Destinations.kt
│       ├── compose/
│       │   ├── compose_dashboard/
│       │   │   ├── .gitignore
│       │   │   ├── build.gradle.kts
│       │   │   ├── consumer-rules.pro
│       │   │   ├── proguard-rules.pro
│       │   │   └── src/
│       │   │       └── main/
│       │   │           ├── AndroidManifest.xml
│       │   │           └── java/
│       │   │               └── zlc/
│       │   │                   └── season/
│       │   │                       └── compose/
│       │   │                           └── dashboard/
│       │   │                               ├── DashboardScreen.kt
│       │   │                               └── DashboardViewModel.kt
│       │   ├── compose_home/
│       │   │   ├── .gitignore
│       │   │   ├── build.gradle.kts
│       │   │   ├── consumer-rules.pro
│       │   │   ├── proguard-rules.pro
│       │   │   └── src/
│       │   │       └── main/
│       │   │           ├── AndroidManifest.xml
│       │   │           └── java/
│       │   │               └── zlc/
│       │   │                   └── season/
│       │   │                       └── compose/
│       │   │                           └── home/
│       │   │                               └── HomeScreen.kt
│       │   └── compose_notifications/
│       │       ├── .gitignore
│       │       ├── build.gradle.kts
│       │       ├── consumer-rules.pro
│       │       ├── proguard-rules.pro
│       │       └── src/
│       │           └── main/
│       │               ├── AndroidManifest.xml
│       │               └── java/
│       │                   └── zlc/
│       │                       └── season/
│       │                           └── compose/
│       │                               └── notifications/
│       │                                   ├── NotificationsScreen.kt
│       │                                   └── NotificationsViewModel.kt
│       ├── feature1/
│       │   ├── .gitignore
│       │   ├── build.gradle.kts
│       │   ├── consumer-rules.pro
│       │   ├── proguard-rules.pro
│       │   └── src/
│       │       └── main/
│       │           ├── AndroidManifest.xml
│       │           ├── java/
│       │           │   └── zlc/
│       │           │       └── season/
│       │           │           └── feature1/
│       │           │               ├── AFragment.kt
│       │           │               ├── BFragment.kt
│       │           │               ├── CFragment.kt
│       │           │               ├── TestAction.kt
│       │           │               ├── TestActivity.kt
│       │           │               ├── TestBottomSheetDialogFragment.kt
│       │           │               ├── TestDialogFragment.kt
│       │           │               ├── TestFragment.kt
│       │           │               └── TestResultActivity.kt
│       │           └── res/
│       │               ├── layout/
│       │               │   ├── activity_test.xml
│       │               │   ├── activity_test_result.xml
│       │               │   ├── dialog_test.xml
│       │               │   ├── fragment_common.xml
│       │               │   └── fragment_test.xml
│       │               └── values/
│       │                   ├── colors.xml
│       │                   └── strings.xml
│       ├── feature2/
│       │   ├── .gitignore
│       │   ├── build.gradle.kts
│       │   ├── consumer-rules.pro
│       │   ├── proguard-rules.pro
│       │   └── src/
│       │       └── main/
│       │           ├── AndroidManifest.xml
│       │           └── java/
│       │               └── zlc/
│       │                   └── season/
│       │                       └── bar/
│       │                           ├── Colors.kt
│       │                           ├── ComposeScreenA.kt
│       │                           ├── ComposeScreenB.kt
│       │                           ├── ComposeScreenC.kt
│       │                           └── ScreenViewModels.kt
│       └── normal/
│           ├── dashboard/
│           │   ├── .gitignore
│           │   ├── build.gradle.kts
│           │   ├── consumer-rules.pro
│           │   ├── proguard-rules.pro
│           │   └── src/
│           │       └── main/
│           │           ├── AndroidManifest.xml
│           │           ├── java/
│           │           │   └── zlc/
│           │           │       └── season/
│           │           │           └── dashboard/
│           │           │               ├── DashboardFragment.kt
│           │           │               └── DashboardViewModel.kt
│           │           └── res/
│           │               └── layout/
│           │                   └── fragment_dashboard.xml
│           ├── home/
│           │   ├── .gitignore
│           │   ├── build.gradle.kts
│           │   ├── consumer-rules.pro
│           │   ├── proguard-rules.pro
│           │   └── src/
│           │       └── main/
│           │           ├── AndroidManifest.xml
│           │           ├── java/
│           │           │   └── zlc/
│           │           │       └── season/
│           │           │           └── home/
│           │           │               ├── HomeFragment.kt
│           │           │               └── HomeImpl.kt
│           │           └── res/
│           │               └── layout/
│           │                   └── fragment_home.xml
│           └── notifications/
│               ├── .gitignore
│               ├── build.gradle.kts
│               ├── consumer-rules.pro
│               ├── proguard-rules.pro
│               └── src/
│                   └── main/
│                       ├── AndroidManifest.xml
│                       ├── java/
│                       │   └── zlc/
│                       │       └── season/
│                       │           └── notifications/
│                       │               ├── NotificationsFragment.kt
│                       │               └── NotificationsViewModel.kt
│                       └── res/
│                           └── layout/
│                               └── fragment_notifications.xml
└── settings.gradle.kts

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
*.iml
.idea
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties


================================================
FILE: CHANGELOG.md
================================================
# Changelog
All notable changes to this project will be documented in this file.

## [1.2.0] - 2022-11-19
### Support Compose

## [1.1.0] - 2022-08-17
### Support fragment navigation

## [1.0.0] - 2022-03-01
### First version


================================================
FILE: LICENSE
================================================
                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright [yyyy] [name of copyright owner]

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.


================================================
FILE: README.md
================================================
![](Butterfly.png)

[![](https://jitpack.io/v/ssseasonnn/Butterfly.svg)](https://jitpack.io/#ssseasonnn/Butterfly)

# Butterfly

*Read this in other languages: [中文](README.zh.md), [English](README.md), [Change Log](CHANGELOG.md)*

Butterfly is a versatile component-based navigation framework based on `Coroutine + Annotation + Ksp`.
Not only does it support navigation for Activity, Fragment, and DialogFragment, but it also supports navigation for Compose pages.

Butterfly supports configuring startup modes for Fragment and Composite navigation, including Standard mode navigation
ClearTop clears stack top mode navigation and SingleTop reuse mode navigation.

Butterfly provides a unified fallback stack for managing all types of page navigation, using standard fallback APIs to reduce developer workload.

Butterfly also provides a powerful framework for inter component communication, allowing for true component decoupling without any dependencies between components.


## Feature List

The butterfly provides these features:

✅ Support navigation to Activity <br>
✅ Support navigation to Fragment <br>
✅ Support navigation to DialogFragment <br>
✅ Support navigation to Compose <br>
✅ Support navigation to Action <br>
✅ Support parameter transfer and parse <br>
✅ Support interceptor <br>
✅ Support backstack <br>
✅ Support group manage <br>
✅ Support launch mode,such as SingleTop、ClearTop <br>
✅ Support component communicate <br>

## Installation

```kotlin
// Add jitpack repository
repositories {
  maven { url("https://jitpack.io") }
}
```

```kotlin
// First, declare the KSP plugin in your top level build.gradle.kts file.
plugins {
    id("com.google.devtools.ksp") version "1.8.10-1.0.9" apply false
}

// Then, enable KSP in your module-level build.gradle.kts file:
plugins {
    id("com.google.devtools.ksp")
}


// Then, add Butterfly dependencies.
dependencies {
  implementation("com.github.ssseasonnn.Butterfly:butterfly:1.3.0")
  ksp("com.github.ssseasonnn.Butterfly:compiler:1.3.0")

  //for compose
  implementation("com.github.ssseasonnn.Butterfly:butterfly-compose:1.3.0")
}
```

## Basic Usage

### Navigation

Butterfly support navigation for Activity、Fragment and DialogFragment and Compose component.

```kotlin
@Destination("test/activity")
class TestActivity : AppCompatActivity()

@Destination("test/fragment")
class TestFragment : Fragment()

@Destination("test/dialog")
class TestDialogFragment : DialogFragment()

@Destination("test/compose")
@Composable
fun HomeScreen() {}

//Navigation
Butterfly.of(context).navigate("test/xxx")

//Navigation and get result
Butterfly.of(context).navigate("test/xxx") {
    val result = it.getStringExtra("result")
    binding.tvResult.text = result
}
```

### Communication

Butterfly implements component-based communication through `Evade` and `EvadeImpl` 
annotations. And the communication between components without any dependence 
is supported by dynamic agent technology.

For example, two components, `Home` and `Dashboard`, component `Dashboard` need to call the method in the component `Home`:

```mermaid
flowchart TD
	A(Main App) --> B(Component Home)
	A(Main App) --> C(Component Dashboard)
```

```kotlin
//Define the Api that needs to access the Home in the component Dashboard and add the @ Evade annotation
@Evade
interface DashboardCallHomeApi {
    fun showHome(fragmentManager: FragmentManager, container: Int)
}


//Implement the corresponding Api in the component Home and add the @ EvadeImpl annotation
@EvadeImpl
class DashboardCallHomeApiImpl {  //The implementation class name must end with Impl
    val TAG = "home_tag"

    //For the implementation of HomeApi, the method name and parameters must be the same
    fun showHome(fragmentManager: FragmentManager, container: Int) {
        val homeFragment = HomeFragment()
        fragmentManager.beginTransaction()
            .replace(container, homeFragment, TAG)
            .commit()
    }
}

//You can then invoke the interface in the component Home in the component Dashboard:
val dashboardCallHomeApi = Butterfly.evade<DashboardCallHomeApi>()
dashboardCallHomeApi.showHome(supportFragmentManager, R.id.container)
```

## Documentation

See examples and browse complete documentation at the Butterfly Wiki: [wiki](https://github.com/ssseasonnn/Butterfly/wiki)

If you still have questions, feel free to create a new issue.


## License

> ```
> Copyright 2022 Season.Zlc
>
> Licensed under the Apache License, Version 2.0 (the "License");
> you may not use this file except in compliance with the License.
> You may obtain a copy of the License at
>
>    http://www.apache.org/licenses/LICENSE-2.0
>
> Unless required by applicable law or agreed to in writing, software
> distributed under the License is distributed on an "AS IS" BASIS,
> WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
> See the License for the specific language governing permissions and
> limitations under the License.
> ```


================================================
FILE: README.zh.md
================================================
![](Butterfly.png)

[![](https://jitpack.io/v/ssseasonnn/Butterfly.svg)](https://jitpack.io/#ssseasonnn/Butterfly)

# Butterfly

*Read this in other languages: [中文](README.zh.md), [English](README.md), [Change Log](CHANGELOG.md)*

Butterfly是一个基于`Coroutine + Annotation注解 + Ksp注解处理器`打造的全能的组件化路由框架。
不仅支持Activity、Fragment、DialogFragment的导航,还支持Compose页面的导航。

Butterfly支持为Fragment和Compose的导航配置启动模式, 包括Standard标准模式导航、
ClearTop清除栈顶模式导航以及SingleTop栈顶复用模式导航。

Butterfly提供统一的回退栈对所有类型的页面导航进行管理,使用标准的回退Api,减轻开发者工作负担。

Butterfly还提供强大的组件间通信框架,可在组件间没有任何依赖情况下通信,实现真正的组件解耦。


## 功能列表

Butterfly提供以下功能:

✅ 支持导航Activity <br>
✅ 支持导航Fragment <br>
✅ 支持导航DialogFragment <br>
✅ 支持导航Compose <br>
✅ 支持导航Action <br>
✅ 支持导航参数传递和解析 <br>
✅ 支持拦截器 <br>
✅ 支持回退栈 <br>
✅ 支持组管理 <br>
✅ 支持启动模式,如SingleTop、ClearTop <br>
✅ 支持组件化通信 <br>

## 安装

```kotlin
// 添加jitpack仓库
repositories {
  maven { url("https://jitpack.io") }
}
```

```kotlin
// 首先,在顶级build.gradle.kts文件中声明ksp插件
plugins {
    id("com.google.devtools.ksp") version "1.8.10-1.0.9" apply false
}

// 然后,在模块级build.gradle.kts文件中启用ksp:
plugins {
    id("com.google.devtools.ksp")
}

// 然后,添加 Butterfly 依赖项
dependencies {
    implementation("com.github.ssseasonnn.Butterfly:butterfly:1.3.0")
    ksp("com.github.ssseasonnn.Butterfly:compiler:1.3.0")

    //for compose
    implementation("com.github.ssseasonnn.Butterfly:butterfly-compose:1.3.0")
}
```


## 基本用法

### 导航

Butterfly支持Activity、Fragment、DialogFragment和Compose组件的导航。

```kotlin
@Destination("test/activity")
class TestActivity : AppCompatActivity()

@Destination("test/fragment")
class TestFragment : Fragment()

@Destination("test/dialog")
class TestDialogFragment : DialogFragment()

@Destination("test/compose")
@Composable
fun HomeScreen() {}

//导航
Butterfly.of(context).navigate("test/xxx")

//导航并获取结果
Butterfly.of(context).navigate("test/xxx") {
    val result = it.getStringExtra("result")
    binding.tvResult.text = result
}
```

### 组件间通信

Butterfly通过`Evade`和`EvadeImpl`注解实现组件化通信。并且通过动态代理技术支持组件间在无任何依赖的情况下的通信。

### 1. 在没有依赖的组件之间通信

例如两个组件`Home`和组件`Dashboard`,组件`Dashboard`需要调用组件`Home`中的方法:

```mermaid
flowchart TD
	A(Main App) --> B(Component Home)
	A(Main App) --> C(Component Dashboard)
```

```kotlin
//在组件Dashboard中定义需要访问Home对应的Api,并添加@Evade注解
@Evade
interface DashboardCallHomeApi {
    fun showHome(fragmentManager: FragmentManager, container: Int)
}


//在组件Home中实现对应的Api,并添加@EvadeImpl注解
@EvadeImpl
class DashboardCallHomeApiImpl {  //实现类名必须以Impl结尾
    val TAG = "home_tag"

    //HomeApi的实现,方法名和方法参数必须相同
    fun showHome(fragmentManager: FragmentManager, container: Int) {
        val homeFragment = HomeFragment()
        fragmentManager.beginTransaction()
            .replace(container, homeFragment, TAG)
            .commit()
    }
}

//然后就可以在组件Dashboard中调用组件Home中的接口:
val dashboardCallHomeApi = Butterfly.evade<DashboardCallHomeApi>()
dashboardCallHomeApi.showHome(supportFragmentManager, R.id.container)
```

## 详细文档

查看示例并浏览完整的文档,请访问Butterfly Wiki:[wiki](https://github.com/ssseasonnn/Butterfly/wiki)

如果您仍有疑问,请随意创建新的issue。


## License

> ```
> Copyright 2022 Season.Zlc
>
> Licensed under the Apache License, Version 2.0 (the "License");
> you may not use this file except in compliance with the License.
> You may obtain a copy of the License at
>
>    http://www.apache.org/licenses/LICENSE-2.0
>
> Unless required by applicable law or agreed to in writing, software
> distributed under the License is distributed on an "AS IS" BASIS,
> WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
> See the License for the specific language governing permissions and
> limitations under the License.
> ```


================================================
FILE: annotation/.gitignore
================================================
/build

================================================
FILE: annotation/build.gradle.kts
================================================
plugins {
    id("kotlin")
}

group = "com.github.ssseasonnn"

================================================
FILE: annotation/src/main/java/zlc/season/butterfly/annotation/Annotations.kt
================================================
package zlc.season.butterfly.annotation

@Retention(AnnotationRetention.BINARY)
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
annotation class Destination(
    val route: String
)

@Retention(AnnotationRetention.BINARY)
@Target(AnnotationTarget.CLASS)
annotation class Evade(
    val identity: String = ""
)

@Retention(AnnotationRetention.BINARY)
@Target(AnnotationTarget.CLASS)
annotation class EvadeImpl(
    val singleton: Boolean = true,
    val identity: String = ""
)

================================================
FILE: annotation/src/main/java/zlc/season/butterfly/annotation/EvadeData.kt
================================================
package zlc.season.butterfly.annotation

data class EvadeData(val cls: Class<*>, val singleton: Boolean)

================================================
FILE: annotation/src/main/java/zlc/season/butterfly/module/Module.kt
================================================
package zlc.season.butterfly.module

import zlc.season.butterfly.annotation.EvadeData

interface Module {
    fun getDestination(): Map<String, Class<*>>
    fun getEvade(): Map<String, Class<*>>
    fun getEvadeImpl(): Map<String, EvadeData>
}

================================================
FILE: build.gradle.kts
================================================
@file:Suppress("DSL_SCOPE_VIOLATION", "UnstableApiUsage")

plugins {
    id("build.logic")
    alias(libs.plugins.application) apply false
    alias(libs.plugins.library) apply false
    alias(libs.plugins.kotlin) apply false
    alias(libs.plugins.kotlin.jvm) apply false
    alias(libs.plugins.kotlin.serialization) apply false
    alias(libs.plugins.kotlin.parcelize) apply false
    alias(libs.plugins.kotlin.kover) apply false
    alias(libs.plugins.ksp) apply false
    alias(libs.plugins.hilt) apply false
    alias(libs.plugins.butterfly) apply false
}

tasks.register("clean", Delete::class) {
    delete(rootProject.buildDir)
}

================================================
FILE: buildLogic/.gitignore
================================================
/build

================================================
FILE: buildLogic/build.gradle.kts
================================================
plugins {
    `kotlin-dsl`
}

java {
    sourceCompatibility = JavaVersion.VERSION_17
    targetCompatibility = JavaVersion.VERSION_17
}

dependencies {
    implementation("com.android.tools.build:gradle:8.0.2")
    implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.10")
}

gradlePlugin {
    plugins.register("buildLogicPlugin") {
        id = "build.logic"
        implementationClass = "BuildLogicPlugin"
    }
}


================================================
FILE: buildLogic/gradle/wrapper/gradle-wrapper.properties
================================================
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
networkTimeout=10000
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists


================================================
FILE: buildLogic/gradlew
================================================
#!/bin/sh

#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

##############################################################################
#
#   Gradle start up script for POSIX generated by Gradle.
#
#   Important for running:
#
#   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
#       noncompliant, but you have some other compliant shell such as ksh or
#       bash, then to run this script, type that shell name before the whole
#       command line, like:
#
#           ksh Gradle
#
#       Busybox and similar reduced shells will NOT work, because this script
#       requires all of these POSIX shell features:
#         * functions;
#         * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
#           «${var#prefix}», «${var%suffix}», and «$( cmd )»;
#         * compound commands having a testable exit status, especially «case»;
#         * various built-in commands including «command», «set», and «ulimit».
#
#   Important for patching:
#
#   (2) This script targets any POSIX shell, so it avoids extensions provided
#       by Bash, Ksh, etc; in particular arrays are avoided.
#
#       The "traditional" practice of packing multiple parameters into a
#       space-separated string is a well documented source of bugs and security
#       problems, so this is (mostly) avoided, by progressively accumulating
#       options in "$@", and eventually passing that to Java.
#
#       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
#       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
#       see the in-line comments for details.
#
#       There are tweaks for specific operating systems such as AIX, CygWin,
#       Darwin, MinGW, and NonStop.
#
#   (3) This script is generated from the Groovy template
#       https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
#       within the Gradle project.
#
#       You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################

# Attempt to set APP_HOME

# Resolve links: $0 may be a link
app_path=$0

# Need this for daisy-chained symlinks.
while
    APP_HOME=${app_path%"${app_path##*/}"}  # leaves a trailing /; empty if no leading path
    [ -h "$app_path" ]
do
    ls=$( ls -ld "$app_path" )
    link=${ls#*' -> '}
    case $link in             #(
      /*)   app_path=$link ;; #(
      *)    app_path=$APP_HOME$link ;;
    esac
done

# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit

# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'

# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum

warn () {
    echo "$*"
} >&2

die () {
    echo
    echo "$*"
    echo
    exit 1
} >&2

# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in                #(
  CYGWIN* )         cygwin=true  ;; #(
  Darwin* )         darwin=true  ;; #(
  MSYS* | MINGW* )  msys=true    ;; #(
  NONSTOP* )        nonstop=true ;;
esac

CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar


# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
        # IBM's JDK on AIX uses strange locations for the executables
        JAVACMD=$JAVA_HOME/jre/sh/java
    else
        JAVACMD=$JAVA_HOME/bin/java
    fi
    if [ ! -x "$JAVACMD" ] ; then
        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME

Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
    fi
else
    JAVACMD=java
    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.

Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi

# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
    case $MAX_FD in #(
      max*)
        # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
        # shellcheck disable=SC3045 
        MAX_FD=$( ulimit -H -n ) ||
            warn "Could not query maximum file descriptor limit"
    esac
    case $MAX_FD in  #(
      '' | soft) :;; #(
      *)
        # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
        # shellcheck disable=SC3045 
        ulimit -n "$MAX_FD" ||
            warn "Could not set maximum file descriptor limit to $MAX_FD"
    esac
fi

# Collect all arguments for the java command, stacking in reverse order:
#   * args from the command line
#   * the main class name
#   * -classpath
#   * -D...appname settings
#   * --module-path (only if needed)
#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.

# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
    APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
    CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )

    JAVACMD=$( cygpath --unix "$JAVACMD" )

    # Now convert the arguments - kludge to limit ourselves to /bin/sh
    for arg do
        if
            case $arg in                                #(
              -*)   false ;;                            # don't mess with options #(
              /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath
                    [ -e "$t" ] ;;                      #(
              *)    false ;;
            esac
        then
            arg=$( cygpath --path --ignore --mixed "$arg" )
        fi
        # Roll the args list around exactly as many times as the number of
        # args, so each arg winds up back in the position where it started, but
        # possibly modified.
        #
        # NB: a `for` loop captures its iteration list before it begins, so
        # changing the positional parameters here affects neither the number of
        # iterations, nor the values presented in `arg`.
        shift                   # remove old arg
        set -- "$@" "$arg"      # push replacement arg
    done
fi

# Collect all arguments for the java command;
#   * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
#     shell script including quotes and variable substitutions, so put them in
#     double quotes to make sure that they get re-expanded; and
#   * put everything else in single quotes, so that it's not re-expanded.

set -- \
        "-Dorg.gradle.appname=$APP_BASE_NAME" \
        -classpath "$CLASSPATH" \
        org.gradle.wrapper.GradleWrapperMain \
        "$@"

# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
    die "xargs is not available"
fi

# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
#   readarray ARGS < <( xargs -n1 <<<"$var" ) &&
#   set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#

eval "set -- $(
        printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
        xargs -n1 |
        sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
        tr '\n' ' '
    )" '"$@"'

exec "$JAVACMD" "$@"


================================================
FILE: buildLogic/gradlew.bat
================================================
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem      https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem

@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem  Gradle startup script for Windows
@rem
@rem ##########################################################################

@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal

set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%

@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi

@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"

@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome

set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute

echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.

goto fail

:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe

if exist "%JAVA_EXE%" goto execute

echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.

goto fail

:execute
@rem Setup the command line

set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar


@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*

:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd

:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%

:mainEnd
if "%OS%"=="Windows_NT" endlocal

:omega


================================================
FILE: buildLogic/settings.gradle.kts
================================================
@file:Suppress("DSL_SCOPE_VIOLATION", "UnstableApiUsage")

enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")

dependencyResolutionManagement {
    repositories {
        google()
        mavenCentral()
        mavenLocal()
    }
}


================================================
FILE: buildLogic/src/main/kotlin/BuildLogicPlugin.kt
================================================
import org.gradle.api.Plugin
import org.gradle.api.Project
import zlc.season.buildlogic.base.setupMaven

class BuildLogicPlugin : Plugin<Project> {
    override fun apply(project: Project) {
        project.subprojects {
            setupMaven()
        }
    }
}


================================================
FILE: buildLogic/src/main/kotlin/zlc/season/buildlogic/base/AndroidExtensions.kt
================================================
@file:Suppress("UnstableApiUsage")

package zlc.season.buildlogic.base

import com.android.build.api.dsl.CommonExtension
import com.android.build.gradle.BaseExtension
import com.android.build.gradle.LibraryExtension
import com.android.build.gradle.internal.dsl.BaseAppModuleExtension
import org.gradle.api.JavaVersion
import org.gradle.api.Project

fun Project.androidApplication(block: BaseAppModuleExtension.() -> Unit = {}) {
    configBase().applyAs<BaseAppModuleExtension> {
        block()
    }
    configPackaging()
}

fun Project.androidLibrary(block: LibraryExtension.() -> Unit = {}) {
    configBase().applyAs<LibraryExtension> {
        block()
    }
    configPackaging()
}

private fun Project.configBase(): BaseExtension {
    return android.apply {
        val libs = getLibs()
        setCompileSdkVersion(libs.getVersion("sdk-compile-version"))

        defaultConfig {
            minSdk = libs.getVersion("sdk-min-version")
            targetSdk = libs.getVersion("sdk-target-version")
            vectorDrawables { useSupportLibrary = true }

            testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
        }

        compileOptions {
            sourceCompatibility = JavaVersion.VERSION_17
            targetCompatibility = JavaVersion.VERSION_17
        }

        kotlinOptions {
            jvmTarget = JavaVersion.VERSION_17.toString()
        }

        testOptions {
            unitTests {
                isReturnDefaultValues = true
                isIncludeAndroidResources = true
            }
        }
    }
}

private fun Project.configPackaging() {
    android.applyAs<CommonExtension<*, *, *, *>> {
        packaging {
            resources.excludes.add("META-INF/LICENSE.md")
            resources.excludes.add("META-INF/LICENSE-notice.md")
        }
    }
}

fun Project.enableCompose() {
    val libs = getLibs()

    android.apply {
        buildFeatures.compose = true

        composeOptions {
            kotlinCompilerExtensionVersion = libs.getVersionStr("compose-compiler")
        }
    }
}

fun Project.addDefaultConstant(vararg constantPair: Pair<String, String>) {
    android.apply {
        defaultConfig {
            val addDefaultConstantLambda = createAddDefaultConstantLambda()
            constantPair.forEach {
                addDefaultConstantLambda(it.first, it.second)
            }
        }
    }
}

================================================
FILE: buildLogic/src/main/kotlin/zlc/season/buildlogic/base/BaseExtensions.kt
================================================
@file:Suppress("TooManyFunctions", "UnstableApiUsage")

package zlc.season.buildlogic.base

import com.android.build.gradle.BaseExtension
import com.android.build.gradle.LibraryExtension
import com.android.build.gradle.internal.dsl.BaseAppModuleExtension
import com.android.build.gradle.internal.dsl.BuildType
import com.android.build.gradle.internal.dsl.DefaultConfig
import com.android.build.gradle.internal.dsl.ProductFlavor
import org.gradle.api.Project
import org.gradle.api.artifacts.Dependency
import org.gradle.api.artifacts.VersionCatalog
import org.gradle.api.artifacts.VersionCatalogsExtension
import org.gradle.api.artifacts.dsl.DependencyHandler
import org.gradle.api.plugins.ExtensionAware
import org.gradle.api.provider.Provider
import org.gradle.kotlin.dsl.getByType
import org.gradle.plugin.use.PluginDependency
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptions
import java.util.Properties

fun Project.getApp(): BaseAppModuleExtension {
    return extensions.getByType()
}

fun Project.getLibrary(): LibraryExtension {
    return extensions.getByType()
}

fun Project.loadProperties(fileName: String): Properties {
    return Properties().apply {
        load(rootProject.file(fileName).reader())
    }
}

fun Project.getLibs(): VersionCatalog {
    return extensions.getByType<VersionCatalogsExtension>().named("libs")
}

fun VersionCatalog.getVersion(name: String): Int {
    return findVersion(name).get().toString().toInt()
}

fun VersionCatalog.getVersionStr(name: String): String {
    return findVersion(name).get().toString()
}

fun VersionCatalog.getBundle(name: String): Any {
    return findBundle(name).get()
}

fun VersionCatalog.getPlugin(name: String): String {
    return (findPlugin(name).get() as Provider<PluginDependency>).get().pluginId
}

internal val Project.android: BaseExtension
    get() = extensions.findByName("android") as? BaseExtension
        ?: error("Project '$name' is not an Android module")

internal fun BaseExtension.kotlinOptions(block: KotlinJvmOptions.() -> Unit) {
    (this as ExtensionAware).extensions.configure("kotlinOptions", block)
}

internal fun DependencyHandler.implementation(dependencyNotation: Any): Dependency? =
    add("implementation", dependencyNotation)

@Suppress("UNCHECKED_CAST")
internal fun <T> Any.applyAs(block: T.() -> Unit) {
    (this as T).block()
}

internal fun createAddFlavorConstantLambda(): (ProductFlavor, String, String) -> Unit {
    return { productFlavor, constantName, constantValue ->
        productFlavor.manifestPlaceholders[constantName] = constantValue
        productFlavor.buildConfigField("String", constantName, "\"${constantValue}\"")
    }
}

internal fun createAddBuildTypeConstantLambda(): (BuildType, String, String) -> Unit {
    return { buildType, constantName, constantValue ->
        buildType.manifestPlaceholders[constantName] = constantValue
        buildType.buildConfigField("String", constantName, "\"${constantValue}\"")
    }
}

internal fun DefaultConfig.createAddDefaultConstantLambda(): (String, String) -> Unit {
    return { constantName, constantValue ->
        manifestPlaceholders[constantName] = constantValue
        buildConfigField("String", constantName, "\"${constantValue}\"")
    }
}


================================================
FILE: buildLogic/src/main/kotlin/zlc/season/buildlogic/base/MavenExtensions.kt
================================================
package zlc.season.buildlogic.base

import org.gradle.api.Project
import org.gradle.api.publish.PublishingExtension
import org.gradle.api.publish.maven.MavenPublication
import org.gradle.kotlin.dsl.apply
import org.gradle.kotlin.dsl.configure
import org.gradle.kotlin.dsl.create

fun Project.setupMaven() {
    afterEvaluate {
        plugins.withId("com.android.library") {
            configPublish("release")
        }
        plugins.withId("java") {
            configPublish("java")
        }
    }
}

private fun Project.configPublish(componentName: String) {
    apply(plugin = "maven-publish")
    configure<PublishingExtension> {
        repositories {
            mavenLocal()
        }
        publications {
            create<MavenPublication>("maven") {
                afterEvaluate {
                    from(components.getByName(componentName))
                }
            }
        }
    }
}

================================================
FILE: butterfly/.gitignore
================================================
/build

================================================
FILE: butterfly/build.gradle.kts
================================================
import zlc.season.buildlogic.base.androidLibrary

@Suppress("DSL_SCOPE_VIOLATION")
plugins {
    alias(libs.plugins.library)
    alias(libs.plugins.kotlin)
    alias(libs.plugins.kotlin.parcelize)
}

androidLibrary {
    namespace = "zlc.season.butterfly"
}

dependencies {
    api(project(":annotation"))
    api(libs.clarity)
    api(libs.kotlin.coroutines)
    api(libs.lifecycle.runtime.ktx)
    api(libs.fragment.ktx)
    api(libs.core.ktx)

    implementation(libs.bundles.unit.test)
}

================================================
FILE: butterfly/consumer-rules.pro
================================================


================================================
FILE: butterfly/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.kts.
#
# For more details, see
#   http://developer.android.com/guide/developing/tools/proguard.html

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
#   public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

================================================
FILE: butterfly/src/main/AndroidManifest.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="zlc.season.butterfly">

    <application />
</manifest>

================================================
FILE: butterfly/src/main/java/zlc/season/butterfly/Butterfly.kt
================================================
package zlc.season.butterfly

import android.content.Context
import androidx.core.os.bundleOf
import zlc.season.butterfly.entities.DestinationData
import zlc.season.butterfly.internal.ButterflyHelper

object Butterfly {
    fun of(context: Context = ButterflyHelper.context): DestinationHandler {
        return DestinationHandler(context)
    }

    // avoid inline
    val EVADE_LAMBDA: (String, Class<*>) -> Any = { identity, cls ->
        val real = identity.ifEmpty { cls.simpleName }
        var request = ButterflyCore.queryEvade(real)
        if (request.className.isEmpty()) {
            request = request.copy(className = cls.name)
        }
        ButterflyCore.dispatchEvade(request)
    }

    inline fun <reified T> evade(
        identity: String = "",
        noinline func: (String, Class<*>) -> Any = EVADE_LAMBDA
    ): T {
        return func(identity, T::class.java) as T
    }
}

================================================
FILE: butterfly/src/main/java/zlc/season/butterfly/ButterflyCore.kt
================================================
package zlc.season.butterfly

import android.content.Context
import android.os.Bundle
import zlc.season.butterfly.core.EvadeManager
import zlc.season.butterfly.core.InterceptorManager
import zlc.season.butterfly.core.ModuleManager
import zlc.season.butterfly.core.NavigatorManager
import zlc.season.butterfly.entities.DestinationData
import zlc.season.butterfly.entities.EvadeData
import zlc.season.butterfly.interceptor.Interceptor
import zlc.season.butterfly.module.Module

object ButterflyCore {
    private val moduleManager = ModuleManager()
    private val interceptorManager = InterceptorManager()
    private val navigatorManager = NavigatorManager()
    private val evadeManager = EvadeManager()

    fun getNavigatorManager() = navigatorManager

    fun addModuleName(moduleName: String) {
        try {
            val cls = Class.forName(moduleName)
            if (Module::class.java.isAssignableFrom(cls)) {
                val module = cls.getDeclaredConstructor().newInstance() as Module
                addModule(module)
            }
        } catch (ignore: Exception) {
            //ignore
        }
    }

    fun addModule(module: Module) = moduleManager.addModule(module)

    fun removeModule(module: Module) = moduleManager.removeModule(module)

    fun addInterceptor(interceptor: Interceptor) = interceptorManager.addInterceptor(interceptor)

    fun removeInterceptor(interceptor: Interceptor) =
        interceptorManager.removeInterceptor(interceptor)

    fun queryDestination(route: String): String = moduleManager.queryDestination(route)

    fun queryEvade(identity: String): EvadeData = moduleManager.queryEvade(identity)

    suspend fun dispatchNavigate(
        context: Context,
        destinationData: DestinationData,
        interceptorManager: InterceptorManager
    ): Result<Bundle> {
        // Use global interceptor before
        var tempDestinationData = if (destinationData.enableGlobalInterceptor) {
            this.interceptorManager.intercept(context, destinationData)
        } else {
            destinationData
        }

        // Then use current navigation interceptor
        tempDestinationData = interceptorManager.intercept(context, tempDestinationData)

        return navigatorManager.navigate(context, tempDestinationData)
    }

    fun popBack(context: Context, bundle: Bundle): DestinationData? {
        return navigatorManager.popBack(context, bundle)
    }

    fun dispatchEvade(evadeData: EvadeData): Any {
        return evadeManager.dispatch(evadeData)
    }
}

================================================
FILE: butterfly/src/main/java/zlc/season/butterfly/DestinationHandler.kt
================================================
package zlc.season.butterfly

import android.content.Context
import android.os.Bundle
import androidx.core.os.bundleOf
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import zlc.season.butterfly.core.InterceptorManager
import zlc.season.butterfly.entities.DestinationData
import zlc.season.butterfly.interceptor.DefaultInterceptor
import zlc.season.butterfly.interceptor.Interceptor
import zlc.season.butterfly.internal.ButterflyHelper.findActivity
import zlc.season.butterfly.internal.key
import zlc.season.butterfly.internal.logd
import zlc.season.butterfly.internal.parseRoute
import zlc.season.butterfly.internal.parseRouteParams
import zlc.season.butterfly.launcher.DestinationLauncher
import zlc.season.butterfly.launcher.DestinationLauncherManager

class DestinationHandler(
    private val context: Context,
    private var destinationData: DestinationData = DestinationData(),
    private val interceptorManager: InterceptorManager = InterceptorManager()
) {
    companion object {
        private val EMPTY_CALLBACK: (Result<Bundle>) -> Unit = {}
    }

    /**
     * Set params for destination
     */
    fun params(vararg pair: Pair<String, Any?>): DestinationHandler {
        return apply {
            destinationData.bundle.putAll(bundleOf(*pair))
        }
    }

    fun params(bundle: Bundle): DestinationHandler {
        return apply {
            destinationData.bundle.putAll(bundle)
        }
    }

    /**
     * Skip global interceptor for current navigation
     */
    fun skipGlobalInterceptor(): DestinationHandler {
        return configDestinationData {
            copy(enableGlobalInterceptor = false)
        }
    }

    /**
     * Add interceptor for current navigation
     */
    fun addInterceptor(interceptor: Interceptor): DestinationHandler {
        return apply { interceptorManager.addInterceptor(interceptor) }
    }

    fun addInterceptor(interceptor: suspend (Context, DestinationData) -> DestinationData): DestinationHandler {
        return apply {
            interceptorManager.addInterceptor(DefaultInterceptor(interceptor))
        }
    }

    fun container(containerViewId: Int): DestinationHandler {
        return configDestinationData {
            copy(containerViewId = containerViewId)
        }
    }

    fun container(containerViewTag: String): DestinationHandler {
        return configDestinationData {
            copy(containerViewTag = containerViewTag)
        }
    }

    fun tag(uniqueTag: String): DestinationHandler {
        return configDestinationData {
            copy(uniqueTag = uniqueTag)
        }
    }

    fun group(groupId: String): DestinationHandler {
        return configDestinationData {
            copy(groupId = groupId)
        }
    }

    fun clearTop(): DestinationHandler {
        return configDestinationData {
            copy(clearTop = true)
        }
    }

    fun singleTop(): DestinationHandler {
        return configDestinationData {
            copy(singleTop = true)
        }
    }

    fun asRoot(): DestinationHandler {
        return configDestinationData {
            copy(isRoot = true)
        }
    }

    fun disableBackStack(): DestinationHandler {
        return configDestinationData {
            copy(enableBackStack = false)
        }
    }

    fun addFlag(flag: Int): DestinationHandler {
        return configDestinationData {
            copy(flags = flags or flag)
        }
    }

    fun enterAnim(enterAnim: Int): DestinationHandler {
        return configDestinationData {
            copy(enterAnim = enterAnim)
        }
    }

    fun exitAnim(exitAnim: Int): DestinationHandler {
        return configDestinationData {
            copy(exitAnim = exitAnim)
        }
    }

    fun navigate(
        route: String,
        onResult: (Result<Bundle>) -> Unit = EMPTY_CALLBACK
    ) {
        navigate(route, bundleOf(), onResult)
    }

    fun navigate(
        route: String,
        vararg params: Pair<String, Any?>,
        onResult: (Result<Bundle>) -> Unit = EMPTY_CALLBACK
    ) {
        navigate(route, bundleOf(*params), onResult)
    }

    fun navigate(
        route: String,
        params: Bundle,
        onResult: (Result<Bundle>) -> Unit = EMPTY_CALLBACK
    ) {
        if (context is LifecycleOwner) {
            context.lifecycleScope.launch {
                if (onResult != EMPTY_CALLBACK) {
                    onResult(awaitNavigateResult(route, params))
                } else {
                    awaitNavigate(route, params)
                }
            }
        } else {
            "Navigate failed, context is not LifecycleOwner!".logd()
        }
    }

    suspend fun awaitNavigate(route: String, params: Bundle) {
        setupRouteAndParams(route, false, params)

        ButterflyCore.dispatchNavigate(context, destinationData, interceptorManager)
    }

    suspend fun awaitNavigateResult(route: String, params: Bundle): Result<Bundle> {
        setupRouteAndParams(route, true, params)

        return ButterflyCore.dispatchNavigate(context, destinationData, interceptorManager)
    }

    fun popBack(vararg result: Pair<String, Any?>): DestinationData? {
        return popBack(bundleOf(*result))
    }

    fun popBack(result: Bundle): DestinationData? {
        return ButterflyCore.popBack(context, result)
    }

    private suspend fun setupRouteAndParams(
        route: String,
        needResult: Boolean,
        extraParams: Bundle
    ) {
        withContext(Dispatchers.Default) {
            val parsedRoute = parseRoute(route)
            val destinationClassName = ButterflyCore.queryDestination(parsedRoute)
            val params = parseRouteParams(parsedRoute)

            configDestinationData {
                bundle.putAll(bundleOf(*params))
                bundle.putAll(extraParams)

                copy(
                    route = parsedRoute,
                    className = destinationClassName,
                    needResult = needResult
                )
            }
        }
    }

    fun getLauncher(context: Context): DestinationLauncher {
        val agileHandler = configDestinationData { copy(needResult = true) }

        val activity = context.findActivity()
            ?: throw IllegalStateException("No Activity founded!")

        val key = activity.key()
        var launcher = DestinationLauncherManager.getLauncher(key, destinationData.route)
        if (launcher == null) {
            launcher = DestinationLauncher(
                context,
                agileHandler.destinationData,
                agileHandler.interceptorManager
            )
            DestinationLauncherManager.addLauncher(key, launcher)
        }

        return launcher
    }

    private fun configDestinationData(block: DestinationData.() -> DestinationData): DestinationHandler {
        return apply {
            destinationData = destinationData.block()
        }
    }
}



================================================
FILE: butterfly/src/main/java/zlc/season/butterfly/action/Action.kt
================================================
package zlc.season.butterfly.action

import android.content.Context
import android.os.Bundle

interface Action {
    fun doAction(context: Context, route: String, data: Bundle = Bundle())
}

================================================
FILE: butterfly/src/main/java/zlc/season/butterfly/core/BackStackEntryManager.kt
================================================
package zlc.season.butterfly.core

import android.app.Activity
import android.os.Bundle
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.FragmentActivity
import zlc.season.butterfly.entities.BackStackEntry
import zlc.season.butterfly.entities.DestinationData
import zlc.season.butterfly.internal.ButterflyHelper
import zlc.season.butterfly.internal.ButterflyHelper.KEY_DESTINATION_DATA
import zlc.season.butterfly.internal.ButterflyHelper.getDestinationData
import zlc.season.butterfly.internal.key
import zlc.season.butterfly.navigator.fragment.observeFragmentDestroy
import zlc.season.claritypotion.ActivityLifecycleCallbacksAdapter

class BackStackEntryManager {
    companion object {
        private const val KEY_SAVE_STATE = "butterfly_back_stack_state"
    }

    /**
     * Activity as the key to save the BackStackEntry corresponding to each activity.
     *
     * like:
     * {
     *   {ActivityA} -> [BackStackEntryA1, BackStackEntryA2],
     *   {ActivityB} -> [BackStackEntryB1, BackStackEntryB2]
     * }
     */
    private val backStackEntryMap = mutableMapOf<String, MutableList<BackStackEntry>>()

    init {
        ButterflyHelper.application.registerActivityLifecycleCallbacks(object :
            ActivityLifecycleCallbacksAdapter() {
            override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
                // If activity recreated, then restore current activity's entry list.
                if (savedInstanceState != null) {
                    restoreEntryList(activity, savedInstanceState)
                } else {
                    // If activity is started by Butterfly, then add it into back stack list.
                    val data = activity.getDestinationData()
                    if (data != null) {
                        addEntry(activity, BackStackEntry(data))
                    }
                }

                // Observe fragment's destroy event to remove FragmentEntry.
                if (activity is FragmentActivity) {
                    activity.observeFragmentDestroy {
                        val uniqueTag = it.tag
                        if (!uniqueTag.isNullOrEmpty()) {
                            removeEntry(activity, uniqueTag)
                        }
                    }
                }
            }

            override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) =
                saveEntryList(activity, outState)

            override fun onActivityDestroyed(activity: Activity) = destroyEntryList(activity)
        })
    }

    @Suppress("deprecation")
    @Synchronized
    private fun restoreEntryList(activity: Activity, savedState: Bundle) {
        val data = savedState.getParcelableArrayList<DestinationData>(KEY_SAVE_STATE)
        if (data != null) {
            val entryList = data.map { BackStackEntry(it) }
            getEntryList(activity).addAll(entryList)
        }
    }

    @Synchronized
    private fun saveEntryList(activity: Activity, outState: Bundle) {
        val list = backStackEntryMap[activity.key()]
        if (!list.isNullOrEmpty()) {
            val savedData = list.mapTo(ArrayList()) { it.destinationData }
            outState.putParcelableArrayList(KEY_SAVE_STATE, savedData)
        }
    }

    @Synchronized
    private fun destroyEntryList(activity: Activity) {
        backStackEntryMap.remove(activity.key())
    }

    @Synchronized
    private fun removeEntry(activity: Activity, uniqueTag: String) {
        val find = getEntryList(activity).find { it.destinationData.uniqueTag == uniqueTag }
        if (find != null) {
            getEntryList(activity).remove(find)
        }
    }

    @Synchronized
    fun removeTopEntry(activity: Activity): BackStackEntry? {
        return getEntryList(activity).removeLastOrNull()
    }

    @Synchronized
    fun removeEntryList(activity: Activity, entryList: List<BackStackEntry>) {
        getEntryList(activity).removeAll(entryList)
    }

    @Synchronized
    fun addEntry(activity: Activity, entry: BackStackEntry) {
        val list = getEntryList(activity)
        list.add(entry)

//        if (isDialogEntry(entry)) {
//            list.add(entry)
//        } else {
//            val dialogEntry = list.firstOrNull { isDialogEntry(it) }
//            if (dialogEntry != null) {
//                val index = list.indexOf(dialogEntry)
//                list.add(index, entry)
//            } else {
//                list.add(entry)
//            }
//        }
    }

    @Synchronized
    fun getTopEntry(activity: Activity): BackStackEntry? {
        val entryList = getEntryList(activity)

        return if (entryList.isEmpty()) {
            null
        } else {
            return entryList.lastOrNull()
        }
    }

    @Synchronized
    fun getTopEntryList(activity: Activity, data: DestinationData): MutableList<BackStackEntry> {
        val result = mutableListOf<BackStackEntry>()

        val backStackList = getEntryList(activity)
        val index = backStackList.indexOfLast { it.destinationData.className == data.className }
        if (index != -1) {
            for (i in index until backStackList.size) {
                val entry = backStackList[i]
                result.add(entry)
            }
        }
        return result
    }

    @Synchronized
    fun getEntryList(activity: Activity): MutableList<BackStackEntry> {
        var backStackList = backStackEntryMap[activity.key()]
        if (backStackList == null) {
            backStackList = mutableListOf()
            backStackEntryMap[activity.key()] = backStackList
        }

        return backStackList
    }

    private fun isDialogEntry(entry: BackStackEntry): Boolean {
        val cls = Class.forName(entry.destinationData.className)
        return DialogFragment::class.java.isAssignableFrom(cls)
    }
}

================================================
FILE: butterfly/src/main/java/zlc/season/butterfly/core/EvadeManager.kt
================================================
package zlc.season.butterfly.core

import zlc.season.butterfly.entities.EvadeData
import zlc.season.butterfly.internal.logw
import java.lang.reflect.Method
import java.lang.reflect.Proxy
import java.util.Objects

class EvadeManager {
    private val implObjMap = mutableMapOf<String, Any>()

    fun dispatch(request: EvadeData): Any {
        val evadeClass = Class.forName(request.className)

        if (!check(request)) {
            "Evade -> $request not found!".logw()
            return createProxyObj(evadeClass)
        }

        val implClass = Class.forName(request.implClassName)
        val implObj = implClass.getImplObj(request)

        // check extend
        if (evadeClass.isAssignableFrom(implClass)) {
            return implObj
        }

        return createProxyObj(evadeClass, implObj) { method, args ->
            if (args == null) {
                val findMethod = implClass.getDeclaredMethod(method.name)
                findMethod.invoke(implObj)
            } else {
                val findMethod = implClass.getDeclaredMethod(method.name, *method.parameterTypes)
                findMethod.invoke(implObj, *args)
            }
        }
    }

    private fun check(request: EvadeData): Boolean {
        if (request.implClassName.isEmpty()) {
            return false
        }
        return true
    }

    private fun Class<*>.getImplObj(request: EvadeData): Any {
        return if (request.isSingleton) {
            realGetImplObj(request)
        } else {
            getDeclaredConstructor().newInstance()
        }
    }

    @Synchronized
    private fun Class<*>.realGetImplObj(request: EvadeData): Any {
        var find = implObjMap[request.implClassName]
        if (find == null) {
            val implObj = getDeclaredConstructor().newInstance()
            implObjMap[request.implClassName] = implObj
            find = implObj
        }
        return find!!
    }

    private fun createProxyObj(
        cls: Class<*>,
        implObj: Any = Any(),
        block: (Method, Array<Any>?) -> Any? = { _, _ -> }
    ): Any {
        return Proxy.newProxyInstance(
            Thread.currentThread().contextClassLoader,
            arrayOf(cls)
        ) { _, method, args ->
            try {
                if (method.name == "hashCode") {
                    Objects.hashCode(implObj)
                } else if (method.name == "toString") {
                    Objects.toString(implObj)
                } else if (method.name == "equals") {
                    val other = args[0]
                    if (Objects.hashCode(implObj) == other.hashCode()) {
                        true
                    } else {
                        Objects.equals(implObj, other)
                    }
                } else {
                    block(method, args)
                }
            } catch (e: Exception) {
                if (e is NoSuchMethodException) {
                    "Evade -> Method ${e.message} not found!".logw()
                } else {
                    e.logw()
                }
                Unit
            }
        }
    }
}

================================================
FILE: butterfly/src/main/java/zlc/season/butterfly/core/GroupEntryManager.kt
================================================
package zlc.season.butterfly.core

import android.app.Activity
import android.os.Bundle
import androidx.fragment.app.FragmentActivity
import zlc.season.butterfly.entities.DestinationData
import zlc.season.butterfly.entities.GroupEntry
import zlc.season.butterfly.internal.key
import zlc.season.butterfly.internal.logd
import zlc.season.butterfly.navigator.fragment.observeFragmentDestroy
import zlc.season.claritypotion.ActivityLifecycleCallbacksAdapter
import zlc.season.claritypotion.ClarityPotion.application

@Suppress("DEPRECATION")
class GroupEntryManager {
    private val groupEntryMap = mutableMapOf<String, MutableList<GroupEntry>>()

    companion object {
        private const val KEY_SAVE_STATE = "butterfly_group_state"
    }

    init {
        application.registerActivityLifecycleCallbacks(object :
            ActivityLifecycleCallbacksAdapter() {
            override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
                // If activity recreated, then restore current activity's entry list.
                if (savedInstanceState != null) {
                    restoreEntryList(activity, savedInstanceState)
                }

                // Observe fragment's destroy event to remove FragmentEntry.
                if (activity is FragmentActivity) {
                    activity.observeFragmentDestroy {
                        val uniqueTag = it.tag
                        if (!uniqueTag.isNullOrEmpty()) {
                            removeEntry(activity, uniqueTag)
                        }
                    }
                }
            }

            override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {
                saveEntryList(activity, outState)
            }

            override fun onActivityDestroyed(activity: Activity) {
                destroyEntryList(activity)
            }
        })
    }

    @Synchronized
    private fun restoreEntryList(activity: Activity, savedState: Bundle) {
        val data = savedState.getParcelableArrayList<DestinationData>(KEY_SAVE_STATE)
        if (data != null) {
            val entryList = data.map { GroupEntry(it) }
            getEntryList(activity).addAll(entryList)
        }
    }

    @Synchronized
    private fun saveEntryList(activity: Activity, outState: Bundle) {
        val entryList = groupEntryMap[activity.key()]
        if (!entryList.isNullOrEmpty()) {
            val savedData = entryList.mapTo(ArrayList()) { it.destinationData }
            outState.putParcelableArrayList(KEY_SAVE_STATE, savedData)
        }
    }

    @Synchronized
    private fun destroyEntryList(activity: Activity) {
        groupEntryMap.remove(activity.key())
    }

    @Synchronized
    private fun removeEntry(activity: Activity, uniqueTag: String) {
        val entryList = getEntryList(activity)
        val find = entryList.find { it.destinationData.uniqueTag == uniqueTag }
        if (find != null) {
            entryList.remove(find)
        }
    }

    @Synchronized
    fun addEntry(activity: Activity, groupEntry: GroupEntry) {
        val entryList = getEntryList(activity)
        entryList.add(groupEntry)
    }

    @Synchronized
    fun getGroupList(activity: Activity, groupId: String): List<GroupEntry> {
        val result = mutableListOf<GroupEntry>()
        val list = getEntryList(activity)
        list.forEach {
            if (it.destinationData.groupId == groupId) {
                result.add(it)
            }
        }
        return result
    }

    @Synchronized
    private fun getEntryList(activity: Activity): MutableList<GroupEntry> {
        var groupList = groupEntryMap[activity.key()]
        if (groupList == null) {
            groupList = mutableListOf()
            groupEntryMap[activity.key()] = groupList
        }
        return groupList
    }
}

================================================
FILE: butterfly/src/main/java/zlc/season/butterfly/core/InterceptorManager.kt
================================================
package zlc.season.butterfly.core

import android.content.Context
import zlc.season.butterfly.entities.DestinationData
import zlc.season.butterfly.interceptor.Interceptor

class InterceptorManager {
    private val interceptorList = mutableListOf<Interceptor>()

    fun addInterceptor(interceptor: Interceptor) {
        interceptorList.add(interceptor)
    }

    fun removeInterceptor(interceptor: Interceptor) {
        interceptorList.remove(interceptor)
    }

    suspend fun intercept(context: Context, destinationData: DestinationData): DestinationData {
        val temp = mutableListOf<Interceptor>()
        temp.addAll(interceptorList)

        var tempData = destinationData
        temp.forEach {
            if (it.shouldIntercept(context, tempData)) {
                tempData = it.intercept(context, tempData)
            }
        }

        return tempData
    }
}

================================================
FILE: butterfly/src/main/java/zlc/season/butterfly/core/ModuleManager.kt
================================================
package zlc.season.butterfly.core

import zlc.season.butterfly.entities.EvadeData
import zlc.season.butterfly.module.Module

class ModuleManager {
    private val modules = mutableListOf<Module>()

    fun addModule(vararg module: Module) {
        module.forEach {
            modules.add(it)
        }
    }

    fun removeModule(module: Module) {
        modules.remove(module)
    }

    fun queryDestination(route: String): String {
        var result = ""
        modules.forEach {
            val find = it.getDestination()[route]
            if (find != null) {
                result = find.name
                return@forEach
            }
        }
        return result
    }

    fun queryEvade(identity: String): EvadeData {
        var className = ""
        var implClassName = ""
        var isSingleton = true

        modules.forEach {
            if (className.isEmpty()) {
                val evadeMap = it.getEvade()
                val temp = evadeMap[identity]
                if (temp != null) {
                    className = temp.name
                }
            }
            if (implClassName.isEmpty()) {
                val implMap = it.getEvadeImpl()
                val temp = implMap[identity]
                if (temp != null) {
                    implClassName = temp.cls.name
                    isSingleton = temp.singleton
                }
            }

            if (className.isNotEmpty() && implClassName.isNotEmpty()) {
                return@forEach
            }
        }
        return EvadeData(identity, className, implClassName, isSingleton)
    }
}

================================================
FILE: butterfly/src/main/java/zlc/season/butterfly/core/NavigatorManager.kt
================================================
package zlc.season.butterfly.core

import android.app.Activity
import android.content.Context
import android.os.Bundle
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import zlc.season.butterfly.action.Action
import zlc.season.butterfly.entities.DestinationData
import zlc.season.butterfly.internal.logd
import zlc.season.butterfly.internal.logw
import zlc.season.butterfly.navigator.ActionNavigator
import zlc.season.butterfly.navigator.ActivityNavigator
import zlc.season.butterfly.navigator.fragment.DialogFragmentNavigator
import zlc.season.butterfly.navigator.ErrorNavigator
import zlc.season.butterfly.navigator.fragment.FragmentNavigator
import zlc.season.butterfly.navigator.Navigator

class NavigatorManager {
    companion object {
        private const val COMPOSE_DESTINATION_CLASS =
            "zlc.season.butterfly.compose.ComposeDestination"
        private const val COMPOSE_NAVIGATOR_CLASS = "zlc.season.butterfly.compose.ComposeNavigator"
    }

    private val navigatorMaps = LinkedHashMap<Class<*>, Navigator>()

    private val backStackEntryManager = BackStackEntryManager()
    private val groupEntryManager = GroupEntryManager()

    fun getBackStackEntryManager() = backStackEntryManager

    fun getGroupEntryManager() = groupEntryManager

    init {
        navigatorMaps.apply {
            putAll(
                listOf(
                    Action::class.java to ActionNavigator,
                    FragmentActivity::class.java to ActivityNavigator(backStackEntryManager),
                    DialogFragment::class.java to DialogFragmentNavigator(backStackEntryManager),
                    Fragment::class.java to FragmentNavigator(
                        backStackEntryManager,
                        groupEntryManager
                    )
                )
            )
            try {
                val composeDestinationCls = Class.forName(COMPOSE_DESTINATION_CLASS)
                val composeNavigatorCls = Class.forName(COMPOSE_NAVIGATOR_CLASS)
                val composeNavigator = composeNavigatorCls.getConstructor(
                    BackStackEntryManager::class.java,
                    GroupEntryManager::class.java
                ).newInstance(backStackEntryManager, groupEntryManager) as Navigator
                put(composeDestinationCls, composeNavigator)
            } catch (e: Exception) {
                e.logw()
            }

            put(Any::class.java, ErrorNavigator)
        }
    }

    suspend fun navigate(context: Context, data: DestinationData): Result<Bundle> {
        if (data.className.isEmpty()) {
            "Navigate failed! Could not find destination class: destination=$data".logd()
            return Result.failure(IllegalStateException("Destination class not found!"))
        }

        return findNavigator(data).navigate(context, data)
    }

    fun popBack(context: Context, result: Bundle): DestinationData? {
        val currentActivity = if (context is Activity) {
            context
        } else {
            "Pop back failed! Need an Activity context: currentContext=$context".logd()
            return null
        }

        val topEntry = backStackEntryManager.removeTopEntry(currentActivity)
        if (topEntry == null) {
            "Pop back failed! Current activity's backstack can not find any entry!".logd()
            return null
        }

        findNavigator(topEntry.destinationData).popBack(currentActivity, topEntry, result)

        return topEntry.destinationData
    }

    private fun findNavigator(data: DestinationData): Navigator {
        val cls = Class.forName(data.className)
        return navigatorMaps[navigatorMaps.keys.find { it.isAssignableFrom(cls) }]!!
    }
}

================================================
FILE: butterfly/src/main/java/zlc/season/butterfly/entities/BackStackEntry.kt
================================================
package zlc.season.butterfly.entities

data class BackStackEntry(val destinationData: DestinationData)

================================================
FILE: butterfly/src/main/java/zlc/season/butterfly/entities/DestinationData.kt
================================================
package zlc.season.butterfly.entities

import android.os.Bundle
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
import zlc.season.butterfly.internal.createDestinationDataTag

@Parcelize
data class DestinationData(
    val route: String = "",
    val className: String = "",
    val bundle: Bundle = Bundle(),

    val enterAnim: Int = 0,
    val exitAnim: Int = 0,
    val flags: Int = 0,
    val containerViewId: Int = 0,
    val containerViewTag: String = "",

    val needResult: Boolean = false,
    val enableBackStack: Boolean = true,
    val enableGlobalInterceptor: Boolean = true,

    val isRoot: Boolean = false,
    val clearTop: Boolean = false,
    val singleTop: Boolean = false,
    val useReplace: Boolean = false,

    val groupId: String = "",
    val uniqueTag: String = createDestinationDataTag()
) : Parcelable

================================================
FILE: butterfly/src/main/java/zlc/season/butterfly/entities/EvadeData.kt
================================================
package zlc.season.butterfly.entities

data class EvadeData(
    val identity: String,
    val className: String,
    val implClassName: String,
    val isSingleton: Boolean
) {
    override fun toString(): String {
        return """[identity="$identity", className="$className", implClassName="$implClassName", isSingleton="$isSingleton"]"""
    }
}


================================================
FILE: butterfly/src/main/java/zlc/season/butterfly/entities/GroupEntry.kt
================================================
package zlc.season.butterfly.entities

data class GroupEntry(val destinationData: DestinationData)

================================================
FILE: butterfly/src/main/java/zlc/season/butterfly/interceptor/DefaultInterceptor.kt
================================================
package zlc.season.butterfly.interceptor

import android.content.Context
import zlc.season.butterfly.entities.DestinationData

class DefaultInterceptor(
    private val interceptor: suspend (Context, DestinationData) -> DestinationData
) : Interceptor {
    override suspend fun shouldIntercept(
        context: Context,
        destinationData: DestinationData
    ): Boolean {
        return true
    }

    override suspend fun intercept(
        context: Context,
        destinationData: DestinationData
    ): DestinationData {
        return interceptor(context, destinationData)
    }
}

================================================
FILE: butterfly/src/main/java/zlc/season/butterfly/interceptor/Interceptor.kt
================================================
package zlc.season.butterfly.interceptor

import android.content.Context
import zlc.season.butterfly.entities.DestinationData

interface Interceptor {
    suspend fun shouldIntercept(context: Context, destinationData: DestinationData): Boolean
    suspend fun intercept(context: Context, destinationData: DestinationData): DestinationData
}

================================================
FILE: butterfly/src/main/java/zlc/season/butterfly/internal/ButterflyFragment.kt
================================================
package zlc.season.butterfly.internal

import android.app.Activity
import android.content.Intent
import android.os.Bundle
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import kotlinx.coroutines.suspendCancellableCoroutine
import zlc.season.butterfly.launcher.DestinationLauncherManager
import zlc.season.butterfly.navigator.fragment.addFragment
import zlc.season.butterfly.navigator.fragment.awaitFragmentResume
import zlc.season.butterfly.navigator.fragment.removeFragment

class ButterflyFragment : Fragment() {
    companion object {
        private const val KEY_ROUTE = "key_destination_route"
        private const val KEY_TAG = "key_destination_tag"

        suspend fun FragmentActivity.startActivityAndAwaitResult(
            route: String,
            intent: Intent
        ): Result<Bundle> {
            "Await activity result: currentActivity=$this, route=$route".logd()
            val fragment = ButterflyFragment().apply {
                arguments = Bundle().apply {
                    putString(KEY_ROUTE, route)
                }
            }

            "Await activity result: add fragment $fragment...".logd()
            addFragment(fragment)

            "Await activity result: waiting fragment resume...".logd()
            awaitFragmentResume(fragment)

            "Await activity result: waiting activity result...".logd()
            val result = fragment.startActivityAndAwaitResult(intent)

            "Await activity result: result=$result.".logd()
            return Result.success(result)
        }

        suspend fun FragmentActivity.awaitFragmentResult(
            route: String,
            uniqueTag: String
        ): Result<Bundle> {
            "Await fragment result: currentActivity=$this, route=$route, uniqueTag=$uniqueTag".logd()
            val fragment = ButterflyFragment().apply {
                arguments = Bundle().apply {
                    putString(KEY_ROUTE, route)
                    putString(KEY_TAG, uniqueTag)
                }
            }

            "Await fragment result: add fragment $fragment...".logd()
            addFragment(fragment)

            "Await fragment result: waiting fragment resume...".logd()
            awaitFragmentResume(fragment)

            "Await fragment result: waiting fragment result...".logd()
            val result = fragment.waitFragmentResult()

            "Await fragment result: result=$result.".logd()
            return Result.success(result)
        }
    }

    private val route by lazy { arguments?.getString(KEY_ROUTE) ?: "" }
    private val uniqueTag by lazy { arguments?.getString(KEY_TAG) ?: "" }

    private val viewModel by lazy { ViewModelProvider(this)[ButterflyViewModel::class.java] }
    private val launcher = registerForActivityResult(StartActivityForResult()) {
        val result = if (it.resultCode == Activity.RESULT_OK) {
            it.data?.extras ?: Bundle()
        } else {
            Bundle()
        }

        "ButterflyFragment[$this] onActivityResult: result=$result".logd()
        //set result for callback
        viewModel.callback.invoke(result)

        //set result for launcher
        val launcher = DestinationLauncherManager.getLauncher(requireActivity().key(), route)
        launcher?.flow?.tryEmit(Result.success(result))

        //clear current fragment
        activity?.removeFragment(this)
    }

    /**
     * Launch activity and wait result.
     */
    suspend fun startActivityAndAwaitResult(intent: Intent): Bundle = suspendCancellableCoroutine {
        viewModel.callback = { result ->
            it.resumeWith(Result.success(result))
            viewModel.callback = {}
        }

        "ButterflyFragment[$this] launch activity and wait result".logd()
        launcher.launch(intent)

        it.invokeOnCancellation {
            viewModel.callback = {}
        }
    }

    suspend fun waitFragmentResult(): Bundle = suspendCancellableCoroutine {
        viewModel.callback = { result ->
            it.resumeWith(Result.success(result))
            viewModel.callback = {}
        }

        "ButterflyFragment[$this] wait fragment result".logd()

        it.invokeOnCancellation {
            viewModel.callback = {}
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        "ButterflyFragment[$this] onCreate".logd()

        if (uniqueTag.isNotEmpty()) {
            parentFragmentManager.setFragmentResultListener(uniqueTag, this) { requestKey, result ->
                if (uniqueTag == requestKey) {
                    "ButterflyFragment[$this] onFragmentResult: result=$result".logd()

                    //set result for callback
                    viewModel.callback.invoke(result)

                    //set result for launcher
                    val launcher =
                        DestinationLauncherManager.getLauncher(requireActivity().key(), route)
                    launcher?.flow?.tryEmit(Result.success(result))

                    parentFragmentManager.clearFragmentResultListener(requestKey)

                    //clear current fragment
                    activity?.removeFragment(this)
                }
            }
        }
    }

    override fun onResume() {
        super.onResume()
        "ButterflyFragment[$this] onResume".logd()
    }

    override fun onDestroy() {
        super.onDestroy()
        "ButterflyFragment[$this] onDestroy".logd()

        viewModel.callback = {}

        if (uniqueTag.isNotEmpty()) {
            parentFragmentManager.clearFragmentResultListener(uniqueTag)
        }
    }

    class ButterflyViewModel : ViewModel() {
        var callback: ((Bundle) -> Unit) = {}
    }
}


================================================
FILE: butterfly/src/main/java/zlc/season/butterfly/internal/ButterflyHelper.kt
================================================
package zlc.season.butterfly.internal

import android.app.Activity
import android.app.Activity.RESULT_OK
import android.app.Application
import android.content.Context
import android.content.ContextWrapper
import android.content.Intent
import android.os.Bundle
import android.view.ViewGroup
import zlc.season.butterfly.entities.DestinationData
import zlc.season.claritypotion.ClarityPotion

object ButterflyHelper {
    internal const val KEY_DESTINATION_DATA = "key_butterfly_destination_data"

    val context: Context
        get() = activity ?: application

    val application: Application
        get() = ClarityPotion.application

    val activity: Activity?
        get() = ClarityPotion.activity

    fun Activity.setActivityResult(bundle: Bundle) {
        if (bundle.isEmpty) return
        setResult(RESULT_OK, Intent().apply { putExtras(bundle) })
    }

    fun Activity.contentView(): ViewGroup {
        return findViewById(android.R.id.content)
    }

    fun Context.findActivity(): Activity? {
        var context = this
        while (context is ContextWrapper) {
            if (context is Activity) return context
            context = context.baseContext
        }
        return null
    }

    @Suppress("deprecation")
    fun Activity.getDestinationData(): DestinationData? {
        return intent.getParcelableExtra(KEY_DESTINATION_DATA)
    }
}

================================================
FILE: butterfly/src/main/java/zlc/season/butterfly/internal/LogUtil.kt
================================================
package zlc.season.butterfly.internal

import android.util.Log

var enableLog = true
val TAG = "Butterfly"

fun <T> T.logd(tag: String = ""): T {
    if (enableLog) {
        val realTag = tag.ifEmpty { TAG }
        if (this is Throwable) {
            Log.d(realTag, this.message ?: "", this)
        } else {
            Log.d(realTag, this.toString())
        }
    }
    return this
}

fun <T> T.logw(tag: String = ""): T {
    if (enableLog) {
        val realTag = tag.ifEmpty { TAG }
        if (this is Throwable) {
            Log.w(realTag, this.message ?: "", this)
        } else {
            Log.w(realTag, this.toString())
        }
    }
    return this
}


================================================
FILE: butterfly/src/main/java/zlc/season/butterfly/internal/Util.kt
================================================
package zlc.season.butterfly.internal

import android.app.Activity
import androidx.core.net.toUri
import java.util.*

internal fun parseRoute(route: String): String {
    val index = route.indexOfFirst { it == '?' }
    return if (index > 0) {
        route.substring(0, index)
    } else {
        route
    }
}

internal fun parseRouteParams(route: String): Array<Pair<String, String?>> {
    val uri = route.toUri()
    val query = uri.query

    if (query != null) {
        val result = mutableListOf<Pair<String, String?>>()
        uri.queryParameterNames.forEach {
            val value = uri.getQueryParameter(it)
            result.add(it to value)
        }
        return result.toTypedArray()
    }
    return arrayOf()
}

internal fun createDestinationDataTag(): String {
    return UUID.randomUUID().toString().replace("-", "").uppercase(Locale.getDefault())
}

internal fun Activity.key(): String {
    return "${javaClass.canonicalName}@${hashCode()}"
}

================================================
FILE: butterfly/src/main/java/zlc/season/butterfly/launcher/DestinationLauncher.kt
================================================
package zlc.season.butterfly.launcher

import android.content.Context
import android.os.Bundle
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import zlc.season.butterfly.ButterflyCore
import zlc.season.butterfly.entities.DestinationData
import zlc.season.butterfly.core.InterceptorManager

class DestinationLauncher(
    val context: Context,
    val destinationData: DestinationData,
    val interceptorManager: InterceptorManager
) {
    val flow = MutableSharedFlow<Result<Bundle>>(extraBufferCapacity = 1)

    fun result(): Flow<Result<Bundle>> {
        return flow
    }

    suspend fun launch() {
        ButterflyCore.dispatchNavigate(context, destinationData, interceptorManager)
    }
}

================================================
FILE: butterfly/src/main/java/zlc/season/butterfly/launcher/DestinationLauncherManager.kt
================================================
package zlc.season.butterfly.launcher

import android.app.Activity
import android.os.Bundle
import zlc.season.butterfly.internal.ButterflyHelper.application
import zlc.season.butterfly.internal.key
import zlc.season.claritypotion.ActivityLifecycleCallbacksAdapter

object DestinationLauncherManager {
    private const val OLD_ACTIVITY_KEY = "old_activity_key"

    private val launcherMap = mutableMapOf<String, MutableList<DestinationLauncher>>()
    private val saveInstanceStateMap = mutableMapOf<String, Boolean>()

    init {
        application.registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacksAdapter() {
            override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
                if (savedInstanceState != null) {
                    val oldKey = savedInstanceState.getString(OLD_ACTIVITY_KEY)
                    if (!oldKey.isNullOrEmpty()) {
                        updateKey(oldKey, activity.key())
                    }
                }
            }

            override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {
                super.onActivitySaveInstanceState(activity, outState)
                val key = activity.key()
                if (containsKey(key)) {
                    outState.putString(OLD_ACTIVITY_KEY, key)
                }
                updateSaveInstanceState(key)
            }

            override fun onActivityDestroyed(activity: Activity) {
                super.onActivityDestroyed(activity)
                handleActivityDestroy(activity.key())
            }
        })
    }

    @Synchronized
    private fun updateSaveInstanceState(key: String) {
        saveInstanceStateMap[key] = true
    }

    @Synchronized
    private fun handleActivityDestroy(key: String) {
        if (saveInstanceStateMap[key] == null) {
            launcherMap.remove(key)
        }
        saveInstanceStateMap.remove(key)
    }

    @Synchronized
    private fun updateKey(oldKey: String, newKey: String) {
        val oldLauncher = launcherMap.remove(oldKey)
        oldLauncher?.let {
            launcherMap[newKey] = oldLauncher
        }
    }

    @Synchronized
    fun containsKey(key: String): Boolean {
        return launcherMap[key] != null
    }

    @Synchronized
    fun addLauncher(key: String, launcher: DestinationLauncher) {
        val list = launcherMap.getOrPut(key) { mutableListOf() }
        if (list.find { it.destinationData.route == launcher.destinationData.route } == null) {
            list.add(launcher)
        }
    }

    @Synchronized
    fun getLauncher(key: String, route: String): DestinationLauncher? {
        return launcherMap[key]?.find { it.destinationData.route == route }
    }
}

================================================
FILE: butterfly/src/main/java/zlc/season/butterfly/navigator/ActionNavigator.kt
================================================
package zlc.season.butterfly.navigator

import android.content.Context
import android.os.Bundle
import zlc.season.butterfly.action.Action
import zlc.season.butterfly.entities.DestinationData

object ActionNavigator : Navigator {
    override suspend fun navigate(context: Context, data: DestinationData): Result<Bundle> {
        return handleAction(context, data)
    }

    private fun handleAction(context: Context, request: DestinationData): Result<Bundle> {
        val action = createAction(request)
        action.doAction(context, request.route, request.bundle)
        return Result.success(Bundle.EMPTY)
    }

    private fun createAction(request: DestinationData): Action {
        val cls = Class.forName(request.className)
        return cls.getDeclaredConstructor().newInstance() as Action
    }
}

================================================
FILE: butterfly/src/main/java/zlc/season/butterfly/navigator/ActivityNavigator.kt
================================================
package zlc.season.butterfly.navigator

import android.app.Activity
import android.app.Application
import android.content.Context
import android.content.Intent
import android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
import android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP
import android.os.Bundle
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult.Companion.EXTRA_ACTIVITY_OPTIONS_BUNDLE
import androidx.core.app.ActivityOptionsCompat
import androidx.core.app.ActivityOptionsCompat.makeCustomAnimation
import androidx.fragment.app.FragmentActivity
import kotlinx.coroutines.suspendCancellableCoroutine
import zlc.season.butterfly.entities.BackStackEntry
import zlc.season.butterfly.core.BackStackEntryManager
import zlc.season.butterfly.entities.DestinationData
import zlc.season.butterfly.internal.ButterflyFragment.Companion.startActivityAndAwaitResult
import zlc.season.butterfly.internal.ButterflyHelper.KEY_DESTINATION_DATA
import zlc.season.butterfly.internal.ButterflyHelper.getDestinationData
import zlc.season.butterfly.internal.ButterflyHelper.setActivityResult
import zlc.season.claritypotion.ActivityLifecycleCallbacksAdapter

class ActivityNavigator(val backStackEntryManager: BackStackEntryManager) : Navigator {

    override suspend fun navigate(context: Context, data: DestinationData): Result<Bundle> {
        if (context is FragmentActivity) {
            return navigate(context, data)
        }

        val intent = createIntent(context, data)
        context.startActivity(intent, createActivityOptions(context, data)?.toBundle())
        context.awaitActivityCreated(data)
        return Result.success(Bundle.EMPTY)
    }

    private suspend fun navigate(
        activity: FragmentActivity,
        data: DestinationData
    ): Result<Bundle> {
        return if (!data.needResult) {
            val intent = createIntent(activity, data)
            activity.startActivity(intent, createActivityOptions(activity, data)?.toBundle())
            activity.awaitActivityCreated(data)
            Result.success(Bundle.EMPTY)
        } else {
            val intent = createIntent(activity, data)
            createActivityOptions(activity, data)?.toBundle()?.let {
                intent.putExtra(EXTRA_ACTIVITY_OPTIONS_BUNDLE, it)
            }
            activity.startActivityAndAwaitResult(data.route, intent)
        }
    }

    override fun popBack(activity: Activity, topEntry: BackStackEntry, bundle: Bundle) {
        with(activity) {
            if (topEntry.destinationData.needResult) {
                setActivityResult(bundle)
            }
            finish()
        }
    }

    private fun createIntent(context: Context, data: DestinationData): Intent {
        val intent = Intent().apply {
            putExtra(KEY_DESTINATION_DATA, data)
            setClassName(context.packageName, data.className)
            putExtras(data.bundle)

            if (data.clearTop) {
                addFlags(FLAG_ACTIVITY_CLEAR_TOP)
            } else if (data.singleTop) {
                addFlags(FLAG_ACTIVITY_SINGLE_TOP)
            }
            if (data.flags != 0) {
                addFlags(data.flags)
            }
            if (context !is Activity) {
                addFlags(FLAG_ACTIVITY_NEW_TASK)
            }
        }

        return intent
    }

    private fun createActivityOptions(
        context: Context, data: DestinationData
    ): ActivityOptionsCompat? {
        return if (data.enterAnim != 0 || data.exitAnim != 0) {
            makeCustomAnimation(context, data.enterAnim, data.exitAnim)
        } else {
            null
        }
    }

    private suspend fun Context.awaitActivityCreated(data: DestinationData) =
        suspendCancellableCoroutine {
            val application = this.applicationContext as Application
            val callback = object : ActivityLifecycleCallbacksAdapter() {
                override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
                    val destinationData = activity.getDestinationData()
                    if (destinationData?.uniqueTag == data.uniqueTag) {
                        application.unregisterActivityLifecycleCallbacks(this)
                        if (it.isActive) {
                            it.resumeWith(Result.success(Unit))
                        }
                    }
                }
            }

            application.registerActivityLifecycleCallbacks(callback)

            it.invokeOnCancellation {
                application.unregisterActivityLifecycleCallbacks(callback)
            }
        }

}

================================================
FILE: butterfly/src/main/java/zlc/season/butterfly/navigator/ErrorNavigator.kt
================================================
package zlc.season.butterfly.navigator

import android.content.Context
import android.os.Bundle
import zlc.season.butterfly.entities.DestinationData

object ErrorNavigator : Navigator {
    override suspend fun navigate(
        context: Context,
        data: DestinationData
    ): Result<Bundle> {
        return Result.failure(IllegalStateException("Invalid destination data: $data"))
    }
}

================================================
FILE: butterfly/src/main/java/zlc/season/butterfly/navigator/Navigator.kt
================================================
package zlc.season.butterfly.navigator

import android.app.Activity
import android.content.Context
import android.os.Bundle
import zlc.season.butterfly.entities.BackStackEntry
import zlc.season.butterfly.entities.DestinationData

interface Navigator {
    suspend fun navigate(context: Context, data: DestinationData): Result<Bundle>

    fun popBack(activity: Activity, topEntry: BackStackEntry, bundle: Bundle) {}
}

================================================
FILE: butterfly/src/main/java/zlc/season/butterfly/navigator/fragment/DialogFragmentNavigator.kt
================================================
package zlc.season.butterfly.navigator.fragment

import android.app.Activity
import android.content.Context
import android.os.Bundle
import androidx.fragment.app.FragmentActivity
import zlc.season.butterfly.core.BackStackEntryManager
import zlc.season.butterfly.entities.BackStackEntry
import zlc.season.butterfly.entities.DestinationData
import zlc.season.butterfly.internal.ButterflyFragment.Companion.awaitFragmentResult
import zlc.season.butterfly.navigator.Navigator

class DialogFragmentNavigator(val backStackEntryManager: BackStackEntryManager) : Navigator {

    override suspend fun navigate(context: Context, data: DestinationData): Result<Bundle> {
        return if (context is FragmentActivity) {
            navigate(context, data)
        } else {
            Result.success(Bundle.EMPTY)
        }
    }

    private suspend fun navigate(
        activity: FragmentActivity,
        destinationData: DestinationData
    ): Result<Bundle> {
        if (destinationData.enableBackStack) {
            backStackEntryManager.addEntry(activity, BackStackEntry(destinationData))
        }

        activity.showDialogFragment(destinationData)

        return if (destinationData.needResult) {
            activity.awaitFragmentResult(destinationData.route, destinationData.uniqueTag)
        } else {
            Result.success(Bundle.EMPTY)
        }
    }

    override fun popBack(activity: Activity, topEntry: BackStackEntry, bundle: Bundle) {
        if (activity !is FragmentActivity) return
        with(activity) {
            val find = findDialogFragment(topEntry.destinationData) ?: return
            if (topEntry.destinationData.needResult) {
                setFragmentResult(topEntry.destinationData.uniqueTag, bundle)
            }
            find.dismissAllowingStateLoss()
        }
    }
}




================================================
FILE: butterfly/src/main/java/zlc/season/butterfly/navigator/fragment/FragmentHelper.kt
================================================
package zlc.season.butterfly.navigator.fragment

import android.os.Bundle
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentResultListener
import androidx.fragment.app.FragmentTransaction
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.isActive
import kotlinx.coroutines.suspendCancellableCoroutine
import zlc.season.butterfly.entities.DestinationData

private fun FragmentActivity.createFragment(data: DestinationData): Fragment {
    val fragment = supportFragmentManager.fragmentFactory.instantiate(
        classLoader, data.className
    )
    fragment.arguments = data.bundle
    return fragment
}

internal fun FragmentActivity.createAndShowFragment(data: DestinationData): Fragment {
    val fragment = createFragment(data)
    with(supportFragmentManager.beginTransaction()) {
        setCustomAnimations(data.enterAnim, data.exitAnim, 0, 0)
        if (data.useReplace) {
            replace(findContainerViewId(data), fragment, data.uniqueTag)
        } else {
            add(findContainerViewId(data), fragment, data.uniqueTag)
        }

        commitAllowingStateLoss()
    }
    return fragment
}

/**
 * Finds the target container view id for a destination within a FragmentActivity.
 *
 * @param data Information about the destination, including potential container view id or tag.
 * @return The id of the container view to use, or [android.R.id.content] if no specific container is found.
 */
private fun FragmentActivity.findContainerViewId(data: DestinationData): Int {
    var result = 0
    if (data.containerViewId != 0) {
        result = data.containerViewId
    } else if (data.containerViewTag.isNotEmpty()) {
        val containerView = window.decorView.findViewWithTag<ViewGroup>(data.containerViewTag)
        if (containerView != null && containerView.id != View.NO_ID) {
            result = containerView.id
        }
    }

    return if (result != 0) result else android.R.id.content
}

internal fun FragmentActivity.createDialogFragment(request: DestinationData): DialogFragment {
    return createFragment(request) as DialogFragment
}

internal fun FragmentActivity.showDialogFragment(request: DestinationData): DialogFragment {
    val dialogFragment = createDialogFragment(request)
    dialogFragment.show(supportFragmentManager, request.uniqueTag)
    return dialogFragment
}

internal fun FragmentActivity.findFragment(data: DestinationData): Fragment? {
    return supportFragmentManager.findFragmentByTag(data.uniqueTag)
}

internal fun FragmentActivity.findDialogFragment(request: DestinationData): DialogFragment? {
    return supportFragmentManager.findFragmentByTag(request.uniqueTag) as? DialogFragment
}

internal fun FragmentActivity.addFragment(fragment: Fragment) {
    commit { add(fragment, fragment.javaClass.name) }
}

internal fun FragmentActivity.removeFragment(fragment: Fragment) {
    commit { remove(fragment) }
}

internal fun FragmentActivity.removeFragment(tag: String) {
    val find = supportFragmentManager.findFragmentByTag(tag)
    if (find != null) {
        commit { remove(find) }
    }
}

internal fun FragmentActivity.hideFragment(fragment: Fragment) {
    commit { hide(fragment) }
}

internal fun FragmentActivity.showFragment(fragment: Fragment) {
    commit { show(fragment) }
}

private fun FragmentActivity.commit(block: FragmentTransaction.() -> Unit) {
    val transaction = supportFragmentManager.beginTransaction()
    transaction.block()
    transaction.commitAllowingStateLoss()
}

internal fun FragmentActivity.observeFragmentDestroy(block: (Fragment) -> Unit) {
    val fragmentLifecycleCallbacks = object : FragmentManager.FragmentLifecycleCallbacks() {
        override fun onFragmentDestroyed(fm: FragmentManager, f: Fragment) {
            block(f)
        }
    }
    supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, false)
    lifecycle.addObserver(object : DefaultLifecycleObserver {
        override fun onDestroy(owner: LifecycleOwner) {
            supportFragmentManager.unregisterFragmentLifecycleCallbacks(fragmentLifecycleCallbacks)
            lifecycle.removeObserver(this)
        }
    })
}

suspend fun FragmentActivity.awaitFragmentResume(fragment: Fragment) =
    suspendCancellableCoroutine {
        if (fragment.isResumed) {
            it.resumeWith(Result.success(Unit))
        } else {
            val callback = object : FragmentManager.FragmentLifecycleCallbacks() {
                override fun onFragmentResumed(fm: FragmentManager, f: Fragment) {
                    if (fragment === f) {
                        supportFragmentManager.unregisterFragmentLifecycleCallbacks(this)
                        if (it.isActive) {
                            it.resumeWith(Result.success(Unit))
                        }
                    }
                }
            }

            val lifecycleObserver = object : DefaultLifecycleObserver {
                override fun onDestroy(owner: LifecycleOwner) {
                    supportFragmentManager.unregisterFragmentLifecycleCallbacks(callback)
                    lifecycle.removeObserver(this)
                }
            }

            lifecycle.addObserver(lifecycleObserver)
            supportFragmentManager.registerFragmentLifecycleCallbacks(callback, false)

            it.invokeOnCancellation {
                supportFragmentManager.unregisterFragmentLifecycleCallbacks(callback)
                lifecycle.removeObserver(lifecycleObserver)
            }
        }
    }

internal fun FragmentActivity.awaitFragmentResult(fragment: Fragment, requestKey: String) =
    callbackFlow {
        val listener = FragmentResultListener { key, result ->
            if (requestKey == key) {
                trySend(Result.success(result))
                close()
                supportFragmentManager.clearFragmentResultListener(requestKey)
            }
        }
        if (!isDestroyed && isActive) {
            supportFragmentManager.setFragmentResultListener(
                requestKey,
                fragment,
                listener
            )
        }
        awaitClose {
            supportFragmentManager.clearFragmentResultListener(requestKey)
        }
    }

internal fun FragmentActivity.setFragmentResult(requestKey: String, bundle: Bundle) {
    if (bundle.isEmpty) return
    supportFragmentManager.setFragmentResult(requestKey, bundle)
}


================================================
FILE: butterfly/src/main/java/zlc/season/butterfly/navigator/fragment/FragmentNavigator.kt
================================================
package zlc.season.butterfly.navigator.fragment

import android.app.Activity
import android.content.Context
import android.os.Bundle
import androidx.fragment.app.FragmentActivity
import zlc.season.butterfly.core.BackStackEntryManager
import zlc.season.butterfly.core.GroupEntryManager
import zlc.season.butterfly.entities.BackStackEntry
import zlc.season.butterfly.entities.DestinationData
import zlc.season.butterfly.internal.ButterflyFragment.Companion.awaitFragmentResult
import zlc.season.butterfly.internal.ButterflyHelper.setActivityResult
import zlc.season.butterfly.navigator.Navigator

class FragmentNavigator(
    private val backStackEntryManager: BackStackEntryManager,
    private val groupEntryManager: GroupEntryManager
) : Navigator {

    private val navigatorContext = NavigatorContext(backStackEntryManager, groupEntryManager)

    override suspend fun navigate(context: Context, data: DestinationData): Result<Bundle> {
        return if (context is FragmentActivity) {
            navigate(context, data)
        } else {
            Result.success(Bundle.EMPTY)
        }
    }

    private suspend fun navigate(
        activity: FragmentActivity,
        request: DestinationData
    ): Result<Bundle> {
        navigatorContext.navigate(activity, request)

        return if (request.needResult) {
            activity.awaitFragmentResult(request.route, request.uniqueTag)
        } else {
            Result.success(Bundle.EMPTY)
        }
    }

    override fun popBack(activity: Activity, topEntry: BackStackEntry, bundle: Bundle) {
        if (activity !is FragmentActivity) return
        activity.apply {
            findFragment(topEntry.destinationData)?.let {
                if (topEntry.destinationData.needResult) {
                    setFragmentResult(topEntry.destinationData.uniqueTag, bundle)
                }
                removeFragment(it)
            }
        }
    }
}

================================================
FILE: butterfly/src/main/java/zlc/season/butterfly/navigator/fragment/FragmentParamUpdatable.kt
================================================
package zlc.season.butterfly.navigator.fragment

import android.os.Bundle

interface FragmentParamUpdatable {
    fun onParamsUpdate(newParams: Bundle)
}

================================================
FILE: butterfly/src/main/java/zlc/season/butterfly/navigator/fragment/NavigatorContext.kt
================================================
package zlc.season.butterfly.navigator.fragment

import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import zlc.season.butterfly.core.BackStackEntryManager
import zlc.season.butterfly.core.GroupEntryManager
import zlc.season.butterfly.entities.DestinationData
import zlc.season.butterfly.navigator.fragment.backstack.ClearTopBackstackNavigator
import zlc.season.butterfly.navigator.fragment.backstack.SingleTopBackstackNavigator
import zlc.season.butterfly.navigator.fragment.backstack.StandardBackstackNavigator
import zlc.season.butterfly.navigator.fragment.group.GroupNavigator

class NavigatorContext(
    backStackEntryManager: BackStackEntryManager,
    groupEntryManager: GroupEntryManager
) {
    private val standardBackstackNavigator = StandardBackstackNavigator(backStackEntryManager)
    private val clearTopBackstackNavigator = ClearTopBackstackNavigator(backStackEntryManager)
    private val singleTopBackstackNavigator = SingleTopBackstackNavigator(backStackEntryManager)

    private val groupNavigator = GroupNavigator(groupEntryManager)

    suspend fun navigate(
        activity: FragmentActivity,
        request: DestinationData
    ): Fragment {
        return if (request.groupId.isNotEmpty()) {
            groupNavigate(activity, request)
        } else {
            backstackNavigate(activity, request)
        }
    }

    private suspend fun backstackNavigate(
        activity: FragmentActivity,
        request: DestinationData
    ): Fragment {
        return if (request.clearTop) {
            clearTopBackstackNavigator.navigate(activity, request)
        } else if (request.singleTop) {
            singleTopBackstackNavigator.navigate(activity, request)
        } else {
            standardBackstackNavigator.navigate(activity, request)
        }
    }

    private fun groupNavigate(
        activity: FragmentActivity,
        request: DestinationData
    ): Fragment {
        return groupNavigator.navigate(activity, request)
    }
}

================================================
FILE: butterfly/src/main/java/zlc/season/butterfly/navigator/fragment/backstack/BackstackNavigator.kt
================================================
package zlc.season.butterfly.navigator.fragment.backstack

import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import zlc.season.butterfly.core.BackStackEntryManager
import zlc.season.butterfly.entities.DestinationData

interface BackstackNavigator {
    suspend fun navigate(
        activity: FragmentActivity,
        destinationData: DestinationData
    ): Fragment
}

================================================
FILE: butterfly/src/main/java/zlc/season/butterfly/navigator/fragment/backstack/BackstackNavigatorHelper.kt
================================================
package zlc.season.butterfly.navigator.fragment.backstack

import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import zlc.season.butterfly.entities.DestinationData
import zlc.season.butterfly.navigator.fragment.findFragment
import zlc.season.butterfly.navigator.fragment.showFragment
import zlc.season.butterfly.navigator.fragment.FragmentParamUpdatable
import zlc.season.butterfly.navigator.fragment.awaitFragmentResume
import zlc.season.butterfly.navigator.fragment.createAndShowFragment

class BackstackNavigatorHelper {

    suspend fun createAndShowFragment(
        activity: FragmentActivity,
        destinationData: DestinationData
    ): Fragment {
        val fragment = activity.createAndShowFragment(destinationData)
        activity.awaitFragmentResume(fragment)
        return fragment
    }

    suspend fun showFragmentAndUpdateArguments(
        activity: FragmentActivity,
        oldData: DestinationData,
        newData: DestinationData
    ): Fragment {
        val target = activity.findFragment(oldData)
        return if (target == null) {
            createAndShowFragment(activity, newData)
        } else {
            if (target is FragmentParamUpdatable) {
                target.onParamsUpdate(newData.bundle)
            }
            activity.showFragment(target)
            activity.awaitFragmentResume(target)
            target
        }
    }
}

================================================
FILE: butterfly/src/main/java/zlc/season/butterfly/navigator/fragment/backstack/ClearTopBackstackNavigator.kt
================================================
package zlc.season.butterfly.navigator.fragment.backstack

import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import zlc.season.butterfly.core.BackStackEntryManager
import zlc.season.butterfly.entities.BackStackEntry
import zlc.season.butterfly.entities.DestinationData
import zlc.season.butterfly.navigator.fragment.removeFragment

class ClearTopBackstackNavigator(
    val backStackEntryManager: BackStackEntryManager
) : BackstackNavigator {
    private val backstackNavigatorHelper = BackstackNavigatorHelper()

    override suspend fun navigate(
        activity: FragmentActivity,
        destinationData: DestinationData
    ): Fragment {
        // Get the entry list above on the target Destination, including the target Destination.
        val topEntryList = backStackEntryManager.getTopEntryList(activity, destinationData)

        // If the list is empty, create and show a new Fragment.
        return if (topEntryList.isEmpty()) {
            val fragment = backstackNavigatorHelper.createAndShowFragment(
                activity,
                destinationData
            )

            // Add fragment to back stack.
            if (destinationData.enableBackStack) {
                backStackEntryManager.addEntry(activity, BackStackEntry(destinationData))
            }
            fragment
        } else {
            // Remove all the Fragment above the target Destination.
            val targetEntry = topEntryList.removeFirst()
            topEntryList.forEach {
                activity.removeFragment(it.destinationData.uniqueTag)
            }
            backStackEntryManager.removeEntryList(activity, topEntryList)

            // Show the target Fragment and update its arguments.
            backstackNavigatorHelper.showFragmentAndUpdateArguments(
                activity,
                targetEntry.destinationData,
                destinationData
            )
        }
    }
}

================================================
FILE: butterfly/src/main/java/zlc/season/butterfly/navigator/fragment/backstack/SingleTopBackstackNavigator.kt
================================================
package zlc.season.butterfly.navigator.fragment.backstack

import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import zlc.season.butterfly.core.BackStackEntryManager
import zlc.season.butterfly.entities.BackStackEntry
import zlc.season.butterfly.entities.DestinationData

class SingleTopBackstackNavigator(
    val backStackEntryManager: BackStackEntryManager
) : BackstackNavigator {
    private val backstackNavigatorHelper = BackstackNavigatorHelper()

    override suspend fun navigate(
        activity: FragmentActivity,
        destinationData: DestinationData
    ): Fragment {
        val topEntry = backStackEntryManager.getTopEntry(activity)

        // If topEntry is not null and the target Destination is the same as the topEntry,
        // show the topEntry's fragment and update its arguments.
        return if (
            topEntry != null &&
            topEntry.destinationData.className == destinationData.className
        ) {
            backstackNavigatorHelper.showFragmentAndUpdateArguments(
                activity,
                topEntry.destinationData,
                destinationData
            )
        } else {
            // Create and show a new Fragment.
            val fragment = backstackNavigatorHelper.createAndShowFragment(
                activity,
                destinationData
            )

            // Add fragment to back stack.
            if (destinationData.enableBackStack) {
                backStackEntryManager.addEntry(activity, BackStackEntry(destinationData))
            }

            fragment
        }
    }
}

================================================
FILE: butterfly/src/main/java/zlc/season/butterfly/navigator/fragment/backstack/StandardBackstackNavigator.kt
================================================
package zlc.season.butterfly.navigator.fragment.backstack

import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import zlc.season.butterfly.core.BackStackEntryManager
import zlc.season.butterfly.entities.BackStackEntry
import zlc.season.butterfly.entities.DestinationData
import zlc.season.butterfly.navigator.fragment.awaitFragmentResume
import zlc.season.butterfly.navigator.fragment.createAndShowFragment

class StandardBackstackNavigator(
    val backStackEntryManager: BackStackEntryManager
) : BackstackNavigator {
    override suspend fun navigate(
        activity: FragmentActivity,
        destinationData: DestinationData
    ): Fragment {
        val fragment = activity.createAndShowFragment(destinationData)
        activity.awaitFragmentResume(fragment)

        if (destinationData.enableBackStack) {
            backStackEntryManager.addEntry(activity, BackStackEntry(destinationData))
        }

        return fragment
    }
}

================================================
FILE: butterfly/src/main/java/zlc/season/butterfly/navigator/fragment/group/GroupNavigator.kt
================================================
package zlc.season.butterfly.navigator.fragment.group

import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import zlc.season.butterfly.core.GroupEntryManager
import zlc.season.butterfly.entities.DestinationData
import zlc.season.butterfly.entities.GroupEntry
import zlc.season.butterfly.navigator.fragment.FragmentParamUpdatable
import zlc.season.butterfly.navigator.fragment.createAndShowFragment
import zlc.season.butterfly.navigator.fragment.findFragment
import zlc.season.butterfly.navigator.fragment.hideFragment
import zlc.season.butterfly.navigator.fragment.showFragment

class GroupNavigator(private val groupEntryManager: GroupEntryManager) {

    fun navigate(
        activity: FragmentActivity,
        request: DestinationData
    ): Fragment {
        val list = groupEntryManager.getGroupList(activity, request.groupId)
        list.forEach { entity ->
            activity.findFragment(entity.destinationData)?.also {
                activity.hideFragment(it)
            }
        }

        val targetEntry = list.find { it.destinationData.className == request.className }
        val targetFragment = targetEntry?.run {
            activity.findFragment(targetEntry.destinationData)
        }

        return if (targetFragment == null) {
            if (targetEntry == null) {
                groupEntryManager.addEntry(activity, GroupEntry(request))
            }
            activity.createAndShowFragment(request)
        } else {
            // pass new arguments to fragment
            if (targetFragment is FragmentParamUpdatable) {
                targetFragment.onParamsUpdate(request.bundle)
            }

            activity.showFragment(targetFragment)
            targetFragment
        }
    }
}

================================================
FILE: butterfly/src/test/java/zlc/season/butterfly/ExampleUnitTest.kt
================================================
package zlc.season.butterfly

import org.junit.Test

import org.junit.Assert.*

/**
 * Example local unit test, which will execute on the development machine (host).
 *
 * See [testing documentation](http://d.android.com/tools/testing).
 */
class ExampleUnitTest {
    @Test
    fun addition_isCorrect() {
        assertEquals(4, 2 + 2)
    }
}

================================================
FILE: butterfly-compose/.gitignore
================================================
/build

================================================
FILE: butterfly-compose/build.gradle.kts
================================================
import zlc.season.buildlogic.base.androidLibrary
import zlc.season.buildlogic.base.enableCompose

@Suppress("DSL_SCOPE_VIOLATION")
plugins {
    alias(libs.plugins.library)
    alias(libs.plugins.kotlin)
    alias(libs.plugins.kotlin.parcelize)
}

androidLibrary {
    namespace = "zlc.season.butterfly.compose"

    enableCompose()
}

dependencies {
    api(project(":butterfly"))
    api(libs.compose.ui)
    api(libs.compose.runtime)
    api(libs.compose.viewmodel)

    implementation(libs.bundles.unit.test)
}

================================================
FILE: butterfly-compose/consumer-rules.pro
================================================


================================================
FILE: butterfly-compose/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.kts.
#
# For more details, see
#   http://developer.android.com/guide/developing/tools/proguard.html

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
#   public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

================================================
FILE: butterfly-compose/src/main/AndroidManifest.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

</manifest>

================================================
FILE: butterfly-compose/src/main/java/zlc/season/butterfly/compose/ComposeDestination.kt
================================================
package zlc.season.butterfly.compose

import android.os.Bundle
import androidx.compose.runtime.Composable

open class ComposeDestination(
    open val composable: (@Composable () -> Unit)? = null,
    open val paramsComposable: (@Composable (Bundle) -> Unit)? = null,
    open val viewModelComposable: (@Composable (Any) -> Unit)? = null,
    open val paramsViewModelComposable: (@Composable (Bundle, Any) -> Unit)? = null,
    open val viewModelClass: String = ""
) {
    constructor() : this(
        null,
        null,
        null,
        null,
        ""
    )
}

================================================
FILE: butterfly-compose/src/main/java/zlc/season/butterfly/compose/ComposeNavigator.kt
================================================
package zlc.season.butterfly.compose

import android.app.Activity
import android.content.Context
import android.os.Bundle
import androidx.activity.ComponentActivity
import zlc.season.butterfly.compose.Utils.isComposeEntry
import zlc.season.butterfly.compose.navigator.ComposeBackStackNavigator
import zlc.season.butterfly.compose.navigator.ComposeGroupNavigator
import zlc.season.butterfly.compose.navigator.ComposeNavigatorHelper
import zlc.season.butterfly.core.BackStackEntryManager
import zlc.season.butterfly.core.GroupEntryManager
import zlc.season.butterfly.entities.BackStackEntry
import zlc.season.butterfly.entities.DestinationData
import zlc.season.butterfly.internal.ButterflyHelper.application
import zlc.season.butterfly.internal.ButterflyHelper.contentView
import zlc.season.butterfly.internal.logd
import zlc.season.butterfly.navigator.Navigator
import zlc.season.claritypotion.ActivityLifecycleCallbacksAdapter

class ComposeNavigator(
    private val backStackEntryManager: BackStackEntryManager,
    private val groupEntryManager: GroupEntryManager
) : Navigator {

    private val composeGroupNavigator = ComposeGroupNavigator(groupEntryManager)
    private val composeBackStackNavigator = ComposeBackStackNavigator(backStackEntryManager)

    init {
        application.registerActivityLifecycleCallbacks(object :
            ActivityLifecycleCallbacksAdapter() {
            override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
                super.onActivityCreated(activity, savedInstanceState)
                if (savedInstanceState != null && activity is ComponentActivity) {
                    activity.contentView().post {
                        val composeViewModel = ComposeViewModel.getInstance(activity.viewModelStore)
                        composeViewModel.getAllDestinationDataFlows().forEach {
                            it.value?.let {
                                navigateDirectly(activity, it)
                            }
                        }
                    }
                }
            }
        })
    }

    override fun popBack(activity: Activity, topEntry: BackStackEntry, bundle: Bundle) {
        if (activity !is ComponentActivity) {
            "Compose pop back failed! Need ComponentActivity, current activity is: $activity".logd()
            return
        }

        // Clear the ViewModel for the pop back page
        ComposeViewModel.getInstance(activity.viewModelStore)
            .clear(topEntry.destinationData.uniqueTag)

        // Clear the view content for the pop back page
        ComposeNavigatorHelper.clearComposeView(activity, topEntry.destinationData)

        with(activity) {
            val newTopEntry = backStackEntryManager.getTopEntry(this)

            if (newTopEntry != null && isComposeEntry(newTopEntry)) {
                // If new top entry is compose entry, launch new top entry directly
                navigateDirectly(activity, newTopEntry.destinationData)
            }
        }
    }

    override suspend fun navigate(context: Context, data: DestinationData): Result<Bundle> {
        return if (context is ComponentActivity) {
            navigate(context, data)
            Result.success(Bundle.EMPTY)
        } else {
            "Compose navigation failed! Need ComponentActivity, current activity is: $context".logd()
            Result.failure(IllegalStateException("Invalid activity!"))
        }
    }

    private fun navigateDirectly(activity: ComponentActivity, data: DestinationData) {
        ComposeNavigatorHelper.navigate(activity, data)
    }

    private fun navigate(activity: ComponentActivity, data: DestinationData) {
        if (data.groupId.isNotEmpty()) {
            composeGroupNavigator.navigate(activity, data)
        } else {
            composeBackStackNavigator.navigate(activity, data)
        }
    }
}

================================================
FILE: butterfly-compose/src/main/java/zlc/season/butterfly/compose/ComposeViewModel.kt
================================================
package zlc.season.butterfly.compose

import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelStore
import androidx.lifecycle.get
import kotlinx.coroutines.flow.MutableStateFlow
import zlc.season.butterfly.entities.DestinationData

/**
 * This ViewModel is used to help Compose Destination Page in creating a ViewModel
 * based on the fallback stack lifecycle.
 */
class ComposeViewModel : ViewModel() {
    /**
     * Save [ComposeViewId : Flow]
     */
    private val destinationDataFlows = mutableMapOf<Int, MutableStateFlow<DestinationData?>>()

    /**
     * Save [destinationDataTag : ViewModelStore]
     */
    private val viewModelStores = mutableMapOf<String, ViewModelStore>()

    fun getAllDestinationDataFlows(): List<MutableStateFlow<DestinationData?>> {
        return destinationDataFlows.values.toList()
    }

    fun getDestinationDataFlow(
        viewId: Int,
        data: DestinationData? = null
    ): MutableStateFlow<DestinationData?> {
        var flow = destinationDataFlows[viewId]
        if (flow == null) {
            flow = MutableStateFlow(data)
            destinationDataFlows[viewId] = flow
        }
        return flow
    }

    fun getViewModelStore(destinationDataTag: String): ViewModelStore {
        var viewModelStore = viewModelStores[destinationDataTag]
        if (viewModelStore == null) {
            viewModelStore = ViewModelStore()
            viewModelStores[destinationDataTag] = viewModelStore
        }
        return viewModelStore
    }

    fun clear(destinationDataTag: String) {
        val viewModelStore = viewModelStores.remove(destinationDataTag)
        viewModelStore?.clear()
    }

    override fun onCleared() {
        // clear flow
        destinationDataFlows.values.forEach {
            it.value = null
        }
        destinationDataFlows.clear()

        // clear viewModelStore
        viewModelStores.values.forEach {
            it.clear()
        }
        viewModelStores.clear()
    }

    override fun toString(): String {
        val sb = StringBuilder("ComposeViewModel{")
        sb.append(Integer.toHexString(System.identityHashCode(this)))
        sb.append("} ViewModelStores (")
        val viewModelStoreIterator: Iterator<String> = viewModelStores.keys.iterator()
        while (viewModelStoreIterator.hasNext()) {
            sb.append(viewModelStoreIterator.next())
            if (viewModelStoreIterator.hasNext()) {
                sb.append(", ")
            }
        }
        sb.append(')')
        return sb.toString()
    }

    companion object {
        private val FACTORY: ViewModelProvider.Factory = object : ViewModelProvider.Factory {
            @Suppress("UNCHECKED_CAST")
            override fun <T : ViewModel> create(modelClass: Class<T>): T {
                return ComposeViewModel() as T
            }
        }

        fun getInstance(viewModelStore: ViewModelStore): ComposeViewModel {
            return ViewModelProvider(viewModelStore, FACTORY).get()
        }
    }
}


================================================
FILE: butterfly-compose/src/main/java/zlc/season/butterfly/compose/DestinationViewModelStoreOwner.kt
================================================
package zlc.season.butterfly.compose

import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelStore
import androidx.lifecycle.ViewModelStoreOwner
import zlc.season.butterfly.entities.DestinationData

class DestinationViewModelStoreOwner(
    private val composeViewModel: ComposeViewModel,
    private val destinationData: DestinationData
) : ViewModelStoreOwner {
    override val viewModelStore: ViewModelStore
        get() = composeViewModel.getViewModelStore(destinationData.uniqueTag)

    @Suppress("unchecked_cast")
    fun getViewModel(composable: ComposeDestination): ViewModel {
        val viewModelClass = Class.forName(composable.viewModelClass) as Class<ViewModel>
        return ViewModelProvider(this)[viewModelClass]
    }
}

================================================
FILE: butterfly-compose/src/main/java/zlc/season/butterfly/compose/Utils.kt
================================================
package zlc.season.butterfly.compose

import android.app.Activity
import android.view.ViewGroup
import androidx.compose.ui.platform.ComposeView
import zlc.season.butterfly.entities.BackStackEntry
import zlc.season.butterfly.entities.DestinationData
import zlc.season.butterfly.entities.GroupEntry
import zlc.season.butterfly.internal.ButterflyHelper.contentView

object Utils {
    const val COMPOSE_VIEW_TAG = "Butterfly_Compose_View_Tag"

    fun isComposeEntry(entry: BackStackEntry): Boolean {
        val cls = Class.forName(entry.destinationData.className)
        return ComposeDestination::class.java.isAssignableFrom(cls)
    }

    fun isComposeEntry(entry: GroupEntry): Boolean {
        val cls = Class.forName(entry.destinationData.className)
        return ComposeDestination::class.java.isAssignableFrom(cls)
    }

    fun isActivityEntry(entry: BackStackEntry): Boolean {
        val cls = Class.forName(entry.destinationData.className)
        return Activity::class.java.isAssignableFrom(cls)
    }

    fun Activity.findContainerView(data: DestinationData): ViewGroup {
        var result: ViewGroup? = null
        if (data.containerViewId != 0) {
            result = findViewById(data.containerViewId)
        } else if (data.containerViewTag.isNotEmpty()) {
            result = window.decorView.findViewWithTag(data.containerViewTag)
        }
        if (result == null) {
            result = contentView()
        }
        return result
    }

    fun Activity.getComposeView(data: DestinationData): ComposeView? {
        val containerView = findContainerView(data)
        return containerView.findViewWithTag(COMPOSE_VIEW_TAG)
    }

    fun Activity.clearContainerView(data: DestinationData) {
        val composeView = getComposeView(data)
        composeView?.setContent { }
    }

    fun BackStackEntry.hasCustomContainer() =
        destinationData.containerViewId != 0 || destinationData.containerViewTag.isNotEmpty()

    fun BackStackEntry.hasSameCustomContainer(other: BackStackEntry) =
        destinationData.containerViewId == other.destinationData.containerViewId &&
                destinationData.containerViewTag == other.destinationData.containerViewTag
}

================================================
FILE: butterfly-compose/src/main/java/zlc/season/butterfly/compose/navigator/ComposeBackStackNavigator.kt
================================================
package zlc.season.butterfly.compose.navigator

import androidx.activity.ComponentActivity
import zlc.season.butterfly.compose.ComposeViewModel
import zlc.season.butterfly.core.BackStackEntryManager
import zlc.season.butterfly.entities.BackStackEntry
import zlc.season.butterfly.entities.DestinationData

class ComposeBackStackNavigator(private val backStackEntryManager: BackStackEntryManager) {

    fun navigate(activity: ComponentActivity, data: DestinationData) {
        if (data.clearTop) {
            clearTopNavigate(activity, data)
        } else if (data.singleTop) {
            singleTopNavigate(activity, data)
        } else {
            standardNavigate(activity, data)
        }
    }

    private fun standardNavigate(activity: ComponentActivity, data: DestinationData) {
        if (data.enableBackStack) {
            backStackEntryManager.addEntry(activity, BackStackEntry(data))
        }

        ComposeNavigatorHelper.navigate(activity, data)
    }

    private fun clearTopNavigate(activity: ComponentActivity, data: DestinationData) {
        val topEntryList = backStackEntryManager.getTopEntryList(activity, data)
        return if (topEntryList.isEmpty()) {
            standardNavigate(activity, data)
        } else {
            // do not remove target entry.
            val topEntry = topEntryList.removeFirst()
            backStackEntryManager.removeEntryList(activity, topEntryList)

            // Clear removed entry resources.
            topEntryList.forEach { each ->
                // Clear ViewModel
                ComposeViewModel.getInstance(activity.viewModelStore)
                    .clear(each.destinationData.uniqueTag)

                // Clear view content
                ComposeNavigatorHelper.clearComposeView(activity, each.destinationData)
            }

            // update old destination's bundle data
            val newData = topEntry.destinationData.copy(bundle = data.bundle)
            ComposeNavigatorHelper.navigate(activity, newData)
        }
    }

    private fun singleTopNavigate(activity: ComponentActivity, data: DestinationData) {
        val topEntry = backStackEntryManager.getTopEntry(activity)
        return if (topEntry != null && topEntry.destinationData.className == data.className) {
            // update old destination's bundle data
            val newData = topEntry.destinationData.copy(bundle = data.bundle)
            ComposeNavigatorHelper.navigate(activity, newData)
        } else {
            standardNavigate(activity, data)
        }
    }
}

================================================
FILE: butterfly-compose/src/main/java/zlc/season/butterfly/compose/navigator/ComposeGroupNavigator.kt
================================================
package zlc.season.butterfly.compose.navigator

import androidx.activity.ComponentActivity
import zlc.season.butterfly.core.GroupEntryManager
import zlc.season.butterfly.entities.DestinationData
import zlc.season.butterfly.entities.GroupEntry

class ComposeGroupNavigator(private val groupEntryManager: GroupEntryManager) {

    fun navigate(activity: ComponentActivity, data: DestinationData) {
        val groupEntryList = groupEntryManager.getGroupList(activity, data.groupId)
        val find = groupEntryList.find { it.destinationData.className == data.className }
        if (find == null) {
            groupEntryManager.addEntry(activity, GroupEntry(data))
        }

        ComposeNavigatorHelper.navigate(activity, data)
    }
}

================================================
FILE: butterfly-compose/src/main/java/zlc/season/butterfly/compose/navigator/ComposeNavigatorHelper.kt
================================================
package zlc.season.butterfly.compose.navigator

import android.view.View
import android.view.ViewGroup
import androidx.activity.ComponentActivity
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.platform.ComposeView
import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner
import zlc.season.butterfly.compose.ComposeDestination
import zlc.season.butterfly.compose.ComposeViewModel
import zlc.season.butterfly.compose.DestinationViewModelStoreOwner
import zlc.season.butterfly.compose.Utils
import zlc.season.butterfly.compose.Utils.findContainerView
import zlc.season.butterfly.entities.DestinationData

object ComposeNavigatorHelper {

    fun clearComposeView(activity: ComponentActivity, data: DestinationData) {
        val containerView = activity.findContainerView(data)
        val composeView = containerView.findViewWithTag<ComposeView>(Utils.COMPOSE_VIEW_TAG)
        if (composeView != null) {
            val composeViewModel = ComposeViewModel.getInstance(activity.viewModelStore)
            val destinationDataFlow = composeViewModel.getDestinationDataFlow(composeView.id)
            destinationDataFlow.value = null
        }
    }

    fun navigate(activity: ComponentActivity, data: DestinationData) {
        val containerView = activity.findContainerView(data)
        var composeView = containerView.findViewWithTag<ComposeView>(Utils.COMPOSE_VIEW_TAG)
        if (composeView == null) {
            composeView = createComposeView(containerView)

            val composeViewModel = ComposeViewModel.getInstance(activity.viewModelStore)
            val destinationDataFlow = composeViewModel.getDestinationDataFlow(composeView.id)
            destinationDataFlow.value = data

            composeView.setContent {
                val destinationData by destinationDataFlow.collectAsState()
                destinationData?.let {
                    val viewModelStoreOwner = DestinationViewModelStoreOwner(composeViewModel, it)

                    val composeDestination = createComposeDestination(it)

                    CompositionLocalProvider(LocalViewModelStoreOwner provides viewModelStoreOwner) {
                        when {
                            composeDestination.paramsViewModelComposable != null -> {
                                val viewModel = viewModelStoreOwner.getViewModel(composeDestination)
                                composeDestination.paramsViewModelComposable?.invoke(
                                    data.bundle,
                                    viewModel
                                )
                            }

                            composeDestination.viewModelComposable != null -> {
                                val viewModel = viewModelStoreOwner.getViewModel(composeDestination)
                                composeDestination.viewModelComposable?.invoke(viewModel)
                            }

                            composeDestination.paramsComposable != null -> {
                                composeDestination.paramsComposable?.invoke(data.bundle)
                            }

                            else -> {
                                composeDestination.composable?.invoke()
                            }
                        }
                    }
                }
            }
        } else {
            val composeViewModel = ComposeViewModel.getInstance(activity.viewModelStore)
            val destinationDataFlow = composeViewModel.getDestinationDataFlow(composeView.id)
            destinationDataFlow.value = data
        }
    }

    private fun createComposeView(containerView: ViewGroup): ComposeView {
        val composeView = ComposeView(containerView.context).apply {
            id = View.generateViewId()
            tag = Utils.COMPOSE_VIEW_TAG
        }
        containerView.addView(
            composeView,
            ViewGroup.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.MATCH_PARENT
            )
        )
        return composeView
    }

    private fun createComposeDestination(data: DestinationData): ComposeDestination {
        return Class.forName(data.className)
            .getDeclaredConstructor()
            .newInstance() as ComposeDestination
    }
}

================================================
FILE: butterfly-compose/src/test/java/zlc/season/butterfly/compose/ExampleUnitTest.kt
================================================
package zlc.season.butterfly.compose

import org.junit.Test

import org.junit.Assert.*

/**
 * Example local unit test, which will execute on the development machine (host).
 *
 * See [testing documentation](http://d.android.com/tools/testing).
 */
class ExampleUnitTest {
    @Test
    fun addition_isCorrect() {
        assertEquals(4, 2 + 2)
    }
}

================================================
FILE: compiler/.gitignore
================================================
/build

================================================
FILE: compiler/build.gradle.kts
================================================
@file:Suppress("DSL_SCOPE_VIOLATION", "UnstableApiUsage")

plugins {
    kotlin("jvm")
}

group = "com.github.ssseasonnn"

dependencies {
    implementation(project(":annotation"))
    implementation(libs.kotlin.poet)
    implementation("com.google.devtools.ksp:symbol-processing-api:1.9.20-1.0.14")
}

================================================
FILE: compiler/src/main/java/zlc/season/butterfly/compiler/Entities.kt
================================================
package zlc.season.butterfly.compiler

data class EvadeImplInfo(val className: String, val singleton: Boolean)

data class ComposeDestinationInfo(
    val packageName: String,
    val methodName: String,
    val hasBundle: Boolean,
    val viewModelName: String,
)

================================================
FILE: compiler/src/main/java/zlc/season/butterfly/compiler/ProcessorProvider.kt
================================================
package zlc.season.butterfly.compiler

import com.google.devtools.ksp.processing.Dependencies
import com.google.devtools.ksp.processing.Resolver
import com.google.devtools.ksp.processing.SymbolProcessor
import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
import com.google.devtools.ksp.processing.SymbolProcessorProvider
import com.google.devtools.ksp.symbol.KSAnnotated
import com.google.devtools.ksp.symbol.KSFile
import zlc.season.butterfly.annotation.Destination
import zlc.season.butterfly.annotation.Evade
import zlc.season.butterfly.annotation.EvadeImpl
import zlc.season.butterfly.compiler.generator.ComposableGenerator
import zlc.season.butterfly.compiler.generator.ModuleClassGenerator
import zlc.season.butterfly.compiler.utils.BUTTERFLY_LOG_ENABLE
import zlc.season.butterfly.compiler.utils.DEFAULT_GENERATE_COMPOSABLE_PACKAGE_NAME
import zlc.season.butterfly.compiler.utils.DEFAULT_GENERATE_MODULE_PACKAGE
import zlc.season.butterfly.compiler.utils.TEMP_FILE_NAME
import zlc.season.butterfly.compiler.utils.composeDestinationClassName
import zlc.season.butterfly.compiler.utils.getGenerateModuleClassName
import zlc.season.butterfly.compiler.visitor.DestinationAnnotationVisitor
import zlc.season.butterfly.compiler.visitor.EvadeAnnotationVisitor
import zlc.season.butterfly.compiler.visitor.EvadeImplAnnotationVisitor
import java.io.File

class ProcessorProvider : SymbolProcessorProvider {
    override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor {
        environment.options.forEach {
            environment.logc("options: $it")
        }
        return ButterflySymbolProcessor(environment)
    }
}

private class ButterflySymbolProcessor(private val environment: SymbolProcessorEnvironment) : SymbolProcessor {
    private val destinationMap = mutableMapOf<String, String>()
    private val evadeMap = mutableMapOf<String, String>()
    private val evadeImplMap = mutableMapOf<String, EvadeImplInfo>()

    private val composableList = mutableListOf<ComposeDestinationInfo>()

    private val sourceFileList = mutableListOf<KSFile>()

    private var packageName = DEFAULT_GENERATE_MODULE_PACKAGE

    override fun process(resolver: Resolver): List<KSAnnotated> {
        processDestinationSymbols(resolver, sourceFileList)
        processEvadeSymbols(resolver, sourceFileList)
        processEvadeImplSymbols(resolver, sourceFileList)

        return emptyList()
    }

    private fun processDestinationSymbols(
        resolver: Resolver,
        sourcesFile: MutableList<KSFile>
    ) {
        environment.logt("Start process Destination symbols...")
        val destinationSymbols = resolver.getSymbolsWithAnnotation(Destination::class.qualifiedName!!)
        environment.logc("find destination list: ${destinationSymbols.toList()}")

        val visitor = DestinationAnnotationVisitor(environment, resolver, destinationMap, composableList, sourcesFile)
        destinationSymbols.toList().forEach {
            it.accept(visitor, Unit)
        }
        environment.logc("process Destination symbols end.")
    }

    private fun processEvadeSymbols(
        resolver: Resolver,
        sourcesFile: MutableList<KSFile>
    ) {
        environment.logt("Start process Evade symbols...")
        val evadeSymbols = resolver.getSymbolsWithAnnotation(Evade::class.qualifiedName!!)
        environment.logc("find evade list: ${evadeSymbols.toList()}")

        val visitor = EvadeAnnotationVisitor(environment, evadeMap, sourcesFile)
        evadeSymbols.toList().forEach {
            it.accept(visitor, Unit)
        }
        environment.logc("process Evade symbols end.")
    }

    private fun processEvadeImplSymbols(
        resolver: Resolver,
        sourcesFile: MutableList<KSFile>
    ) {
        environment.logt("Start process EvadeImpl symbols...")
        val evadeImplSymbols = resolver.getSymbolsWithAnnotation(EvadeImpl::class.qualifiedName!!)
        environment.logc("find evade impl list: ${evadeImplSymbols.toList()}")

        val visitor = EvadeImplAnnotationVisitor(environment, evadeImplMap, sourcesFile)
        evadeImplSymbols.toList().forEach {
            it.accept(visitor, Unit)
        }
        environment.logc("process EvadeImpl symbols end.")
    }

    override fun finish() {
        // create an empty temp file to get current module name
        val tempOutputFile = environment.codeGenerator.createNewFile(
            Dependencies(true),
            packageName = packageName,
            fileName = TEMP_FILE_NAME,
        )
        tempOutputFile.close()

        val tempFile = environment.codeGenerator.generatedFile.find { it.name.startsWith(TEMP_FILE_NAME) }
        tempFile?.let {
            // generate composable class file first.
            if (composableList.isNotEmpty()) {
                generateComposeDestinationClass()
            }

            // generate module class file.
            generateModuleClass(it)
        }
    }

    private fun generateComposeDestinationClass() {
        environment.logt("Generate compose destination classes...")
        val composableGenerator = ComposableGenerator()
        composableList.forEach { composableInfo ->
            val composableClassName = composeDestinationClassName(composableInfo.methodName)
            environment.logc("generate compose destination class file: $composableClassName")

            val composableClassFile = environment.codeGenerator.createNewFile(
                Dependencies(true),
                packageName = DEFAULT_GENERATE_COMPOSABLE_PACKAGE_NAME,
                fileName = composeDestinationClassName(composableInfo.methodName)
            )
            val composableClassContent = composableGenerator.createFileSpec(composableInfo).toString()
            composableClassFile.write(composableClassContent.toByteArray())
            composableClassFile.close()
        }
    }

    private fun generateModuleClass(tempFile: File) {
        val moduleClassName = getGenerateModuleClassName(tempFile.absolutePath)
        environment.logt("Generate module class file: $moduleClassName")

        val moduleClassFile = environment.codeGenerator.createNewFile(
            Dependencies(true, *sourceFileList.toTypedArray()),
            packageName = packageName,
            fileName = moduleClassName
        )
        val moduleClassGenerator = ModuleClassGenerator(packageName, moduleClassName, destinationMap, evadeMap, evadeImplMap)
        val moduleClassContent = moduleClassGenerator.generate().toString()
        moduleClassFile.write(moduleClassContent.toByteArray())
        moduleClassFile.close()
    }
}

internal fun SymbolProcessorEnvironment.logt(log: String) {
    if (isEnableLog()) {
        logger.warn("==== $log")
    }
}

internal fun SymbolProcessorEnvironment.logc(log: String) {
    if (isEnableLog()) {
        logger.warn("---- $log")
    }
}

internal fun SymbolProcessorEnvironment.loge(log: String) {
    logger.error(log)
}

private fun SymbolProcessorEnvironment.isEnableLog(): Boolean {
    val value = options[BUTTERFLY_LOG_ENABLE]
    return value == "true"
}

================================================
FILE: compiler/src/main/java/zlc/season/butterfly/compiler/generator/ComposableGenerator.kt
================================================
package zlc.season.butterfly.compiler.generator

import com.squareup.kotlinpoet.AnnotationSpec
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.FileSpec
import com.squareup.kotlinpoet.KModifier
import com.squareup.kotlinpoet.LambdaTypeName.Companion.get
import com.squareup.kotlinpoet.ParameterSpec
import com.squareup.kotlinpoet.PropertySpec
import com.squareup.kotlinpoet.TypeSpec
import com.squareup.kotlinpoet.asTypeName
import zlc.season.butterfly.compiler.ComposeDestinationInfo
import zlc.season.butterfly.compiler.utils.DEFAULT_GENERATE_COMPOSABLE_PACKAGE_NAME
import zlc.season.butterfly.compiler.utils.composeDestinationClassName
import zlc.season.butterfly.compiler.generator.ComposableHelper.composableLambdaType
import zlc.season.butterfly.compiler.generator.ComposableHelper.paramsComposableLambdaType
import zlc.season.butterfly.compiler.generator.ComposableHelper.paramsViewModelComposableLambdaType
import zlc.season.butterfly.compiler.generator.ComposableHelper.viewModelComposableLambdaType
import zlc.season.butterfly.compiler.utils.COMPOSE_DESTINATION_CLASS

/**
Generated file:

package zlc.season.butterfly.compose

import android.os.Bundle
import androidx.compose.runtime.Composable
import kotlin.Any
import kotlin.String
import kotlin.Unit
import zlc.season.butterfly.compose.ComposeDestination
import zlc.season.compose.dashboard.DashboardScreen

public class DashboardScreenComposeDestination : ComposeDestination() {
public override val composable: @Composable (() -> Unit)? =
@Composable {
DashboardScreen()
}

public override val paramsComposable: @Composable ((Bundle) -> Unit)? =
@Composable { bundle ->
DashboardScreen(bundle)
}

public override val viewModelComposable: @Composable ((Any) -> Unit)? =
@Composable { viewModel ->
DashboardScreen(viewModel as zlc.season.compose.dashboard.DashboardViewModel)
}

public override val paramsViewModelComposable: @Composable ((Bundle, Any) -> Unit)? =
@Composable { bundle, viewModel ->
DashboardScreen(
bundle, viewModel as zlc.season.compose.dashboard.DashboardViewModel
)
}

public override val viewModelClass: String = "zlc.season.compose.dashboard.DashboardViewModel"
}
 */
internal object ComposableHelper {
    private val composeAnnotationCls = ClassName("androidx.compose.runtime", "Composable")
    private val composeAnnotation = AnnotationSpec.builder(composeAnnotationCls).build()

    private val bundleCls = ClassName("android.os", "Bundle")
    private val bundleParams = ParameterSpec.unnamed(bundleCls)

    private val anyParams = ParameterSpec.unnamed(Any::class)

    private val unitType = Unit::class.asTypeName()

    val composableLambdaType = get(returnType = unitType).copy(annotations = arrayListOf(composeAnnotation), nullable = true)

    val viewModelComposableLambdaType = get(parameters = listOf(anyParams), returnType = unitType)
        .copy(annotations = arrayListOf(composeAnnotation), nullable = true)

    val paramsComposableLambdaType = get(parameters = listOf(bundleParams), returnType = unitType)
        .copy(annotations = arrayListOf(composeAnnotation), nullable = true)

    val paramsViewModelComposableLambdaType =
        get(parameters = listOf(bundleParams, anyParams), returnType = unitType)
            .copy(annotations = arrayListOf(composeAnnotation), nullable = true)

    val superCls = ClassName(DEFAULT_GENERATE_COMPOSABLE_PACKAGE_NAME, COMPOSE_DESTINATION_CLASS)
}

internal class ComposableGenerator {
    fun createFileSpec(composeDestinationInfo: ComposeDestinationInfo): FileSpec {
        val classBuilder = TypeSpec.classBuilder(composeDestinationClassName(composeDestinationInfo.methodName))
            .superclass(ComposableHelper.superCls)
            .apply {
                if (composeDestinationInfo.hasBundle) {
                    if (composeDestinationInfo.viewModelName.isNotEmpty()) {
                        addProperty(
                            PropertySpec.builder("paramsViewModelComposable", paramsViewModelComposableLambdaType)
                                .addModifiers(KModifier.OVERRIDE)
                                .initializer(
                                    """@Composable { bundle, viewModel -> ${composeDestinationInfo.methodName}(bundle, viewModel as ${composeDestinationInfo.viewModelName}) }""".trimIndent()
                                )
                                .build()
                        )
                        addProperty(
                            PropertySpec.builder("viewModelClass", String::class)
                                .addModifiers(KModifier.OVERRIDE)
                                .initializer(
                                    """ "${composeDestinationInfo.viewModelName}" """.trimIndent()
                                )
                                .build()
                        )
                    } else {
                        addProperty(
                            PropertySpec.builder("paramsComposable", paramsComposableLambdaType)
                                .addModifiers(KModifier.OVERRIDE)
                                .initializer(
                                    """@Composable { bundle -> ${composeDestinationInfo.methodName}(bundle) }""".trimIndent()
                                )
                                .build()
                        )
                    }
                } else {
                    if (composeDestinationInfo.viewModelName.isNotEmpty()) {
                        addProperty(
                            PropertySpec.builder("viewModelComposable", viewModelComposableLambdaType)
                                .addModifiers(KModifier.OVERRIDE)
                                .initializer(
                                    """@Composable { viewModel -> ${composeDestinationInfo.methodName}(viewModel as ${composeDestinationInfo.viewModelName}) }""".trimIndent()
                                )
                                .build()
                        )
                        addProperty(
                            PropertySpec.builder("viewModelClass", String::class)
                                .addModifiers(KModifier.OVERRIDE)
                                .initializer(
                                    """ "${composeDestinationInfo.viewModelName}" """.trimIndent()
                                )
                                .build()
                        )
                    } else {
                        addProperty(
                            PropertySpec.builder("composable", composableLambdaType)
                                .addModifiers(KModifier.OVERRIDE)
                                .initializer(
                                    """@Composable { ${composeDestinationInfo.methodName}() }""".trimIndent()
                                )
                                .build()
                        )
                    }
                }
            }

        return FileSpec.builder(DEFAULT_GENERATE_COMPOSABLE_PACKAGE_NAME, composeDestinationClassName(composeDestinationInfo.methodName))
            .addType(classBuilder.build())
            .addImport(ClassName(composeDestinationInfo.packageName, composeDestinationInfo.methodName), "")
            .addImport(ComposableHelper.superCls, "")
            .build()
    }
}

================================================
FILE: compiler/src/main/java/zlc/season/butterfly/compiler/generator/ModuleClassGenerator.kt
================================================
package zlc.season.butterfly.compiler.generator

import com.squareup.kotlinpoet.CodeBlock
import com.squareup.kotlinpoet.FileSpec
import com.squareup.kotlinpoet.FunSpec
import com.squareup.kotlinpoet.KModifier
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
import com.squareup.kotlinpoet.PropertySpec
import com.squareup.kotlinpoet.TypeSpec
import com.squareup.kotlinpoet.asClassName
import com.squareup.kotlinpoet.typeNameOf
import zlc.season.butterfly.annotation.EvadeData
import zlc.season.butterfly.compiler.EvadeImplInfo
import zlc.season.butterfly.module.Module

/**
 * public class ButterflyModuleApp() : Module {
 *  public val destinationMap: HashMap<String, String> = hashMapOf<String, String>()
 *  public val evadeMap: HashMap<String, String> = hashMapOf<String, String>()
 *  public val evadeImplMap: HashMap<String, EvadeData> = hashMapOf<String, EvadeData>()
 *
 *  init {
 *      destinationMap["/path/foo"] = "zlc.season.butterflydemo.MainActivity"
 *      destinationMap["/path/bar"] = "zlc.season.butterflydemo.TestActivity"
 *  }
 *
 *   *  init {
 *      evadeMap["/path/foo"] = "zlc.season.butterflydemo.Foo"
 *      evadeMap["path/bar"] = "zlc.season.butterflydemo.Bar"
 *  }
 *
 *   *  init {
 *      evadeImplMap["/path/foo"] = EvadeData("zlc.season.butterflydemo.Foo",false)
 *      evadeImplMap["/path/bar"] = EvadeData("zlc.season.butterflydemo.Bar",false)
 *  }
 *
 *  public override fun getDestination(): HashMap<String, String> = destinationMap
 *  public override fun getEvade(): HashMap<String, String> = evadeMap
 *  public override fun getEvadeImpl(): HashMap<String, EvadeData> = evadeImplMap
 * }
 */
internal class ModuleClassGenerator(
    private val packageName: String,
    private val className: String,
    private val destinationMap: Map<String, String>,
    private val evadeMap: Map<String, String>,
    private val evadeImplMap: Map<String, EvadeImplInfo>,
) {
    private val moduleClass = Module::class.asClassName()

    private val mapClass = typeNameOf<HashMap<String, Class<*>>>()
    private val mapDataClass = HashMap::class.asClassName().parameterizedBy(String::class.asClassName(), EvadeData::class.asClassName())

    fun generate(): FileSpec {
        val companion = TypeSpec.companionObjectBuilder()
            .addFunction(
                FunSpec.builder("doNothing")
                    .returns(moduleClass)
                    .addStatement("return ${className}()")
                    .build()
            )
            .build()
        val classBuilder = TypeSpec.classBuilder(className)
            .addSuperinterface(moduleClass)
            .primaryConstructor(FunSpec.constructorBuilder().build())
            .addProperty(
                PropertySpec.builder("destinationMap", mapClass)
                    .initializer("hashMapOf<String,  Class<*>>()")
                    .build()
            )
            .addProperty(
                PropertySpec.builder("evadeMap", mapClass)
                    .initializer("hashMapOf<String, Class<*>>()")
                    .build()
            )
            .addProperty(
                PropertySpec.builder("evadeImplMap", mapDataClass)
                    .initializer("hashMapOf<String, EvadeData>()")
                    .build()
            )
            .addInitializerBlock(
                generateDestinationMapBlock()
            )
            .addInitializerBlock(
                generateEvadeMapBlock()
            )
            .addInitializerBlock(
                generateEvadeImplMapBlock()
            )
            .addFunction(
                FunSpec.builder("getDestination")
                    .addModifiers(KModifier.OVERRIDE)
                    .addStatement("return destinationMap")
                    .returns(mapClass)
                    .build()
            )
            .addFunction(
                FunSpec.builder("getEvade")
                    .addModifiers(KModifier.OVERRIDE)
                    .addStatement("return evadeMap")
                    .returns(mapClass)
                    .build()
            )
            .addFunction(
                FunSpec.builder("getEvadeImpl")
                    .addModifiers(KModifier.OVERRIDE)
                    .addStatement("return evadeImplMap")
                    .returns(mapDataClass)
                    .build()
            )
            .addType(companion)

        return FileSpec.builder(packageName, className)
            .addType(classBuilder.build())
            .build()
    }

    private fun generateDestinationMapBlock(): CodeBlock {
        val builder = CodeBlock.Builder()
        destinationMap.forEach { (k, v) ->
            builder.addStatement("""destinationMap["$k"] = ${v}::class.java """)
        }
        return builder.build()
    }

    private fun generateEvadeMapBlock(): CodeBlock {
        val builder = CodeBlock.Builder()
        evadeMap.forEach { (k, v) ->
            builder.addStatement("""evadeMap["$k"] = ${v}::class.java """)
        }
        return builder.build()
    }

    private fun generateEvadeImplMapBlock(): CodeBlock {
        val builder = CodeBlock.Builder()
        evadeImplMap.forEach { (k, v) ->
            builder.addStatement("""evadeImplMap["$k"] = EvadeData(cls=${(v.className)}::class.java, singleton=${v.singleton}) """)
        }
        return builder.build()
    }
}

================================================
FILE: compiler/src/main/java/zlc/season/butterfly/compiler/utils/KspUtils.kt
================================================
package zlc.season.butterfly.compiler.utils

import com.google.devtools.ksp.symbol.KSAnnotation
import com.google.devtools.ksp.symbol.KSClassDeclaration
import com.google.devtools.ksp.symbol.KSFunctionDeclaration
import com.google.devtools.ksp.symbol.KSType

const val BUTTERFLY_LOG_ENABLE = "butterfly.log.enable"

/**
 * Get class full name by KSType.
 * eg: com.example.Test
 */
internal fun KSType.getClassFullName(): String {
    val ksClassDeclaration = declaration as KSClassDeclaration
    val packageName = ksClassDeclaration.packageName.asString()
    val className = ksClassDeclaration.simpleName.asString()
    return "$packageName.$className"
}

/**
 * Get class full name by KSClassDeclaration.
 * eg: com.example.Test
 */
internal fun KSClassDeclaration.getClassFullName(): String {
    return "${packageName.asString()}.${simpleName.asString()}"
}

/**
 * Get annotation's value by key
 */
@Suppress("UNCHECKED_CAST")
internal fun <T> KSAnnotation.getValue(key: String, defaultValue: T): T {
    return arguments.find { it.name?.asString() == key }?.value as? T ?: defaultValue
}

internal fun KSFunctionDeclaration.getAnnotationByName(name: String): KSAnnotation? {
    return annotations.toList().find { it.shortName.asString() == name }
}

internal fun KSClassDeclaration.getAnnotationByName(name: String): KSAnnotation? {
    return annotations.toList().find { it.shortName.asString() == name }
}

================================================
FILE: compiler/src/main/java/zlc/season/butterfly/compiler/utils/Util.kt
================================================
package zlc.season.butterfly.compiler.utils

import java.util.Locale
import java.io.File.separatorChar as s

/**
 * default package name for generate module class.
 */
const val DEFAULT_GENERATE_MODULE_PACKAGE = "zlc.season.butterfly.module"
const val DEFAULT_GENERATE_COMPOSABLE_PACKAGE_NAME = "zlc.season.butterfly.compose"

const val TEMP_FILE_NAME = "Temp"

const val BUNDLE_CLASS_NAME = "android.os.Bundle"
const val VIEW_MODEL_CLASS_NAME = "androidx.lifecycle.ViewModel"

const val DESTINATION_NAME = "Destination"
const val EVADE_NAME = "Evade"
const val EVADE_IMPL_NAME = "EvadeImpl"

const val COMPOSE_DESTINATION_CLASS = "ComposeDestination"

const val DESTINATION_ROUTE_KEY = "route"
const val EVADE_IDENTITY_KEY = "identity"
const val EVADE_SINGLETON_KEY = "singleton"

const val EVADE_IMPL_SUFFIX = "Impl"

/**
 * get generate module class name. eg: ButterflyHomeModule
 */
internal fun getGenerateModuleClassName(generateDir: String): String {
    return try {
        val kspGenDir = "${s}build${s}generated${s}ksp"
        val pathIndex = generateDir.lastIndexOf(kspGenDir)
        val subStr = generateDir.substring(0, pathIndex)
        val lastIndex = subStr.lastIndexOf(s)
        val result = subStr.substring(lastIndex + 1)
        "Butterfly${result.camelCase()}Module"
    } catch (e: Exception) {
        "ButterflyDefaultModule"
    }
}


/**
 * The name of each Composable function Class.
 */
internal fun composeDestinationFullClassName(methodName: String): String {
    return "$DEFAULT_GENERATE_COMPOSABLE_PACKAGE_NAME.${composeDestinationClassName(methodName)}"
}

/**
 * The name of generated ComposeDestination class.
 * eg:
 * @Destination("path")
 * @Composable
 * fun Test(){}
 *
 * will generate class:
 * class TestComposeDestination: ComposeDestination {}
 */
internal fun composeDestinationClassName(methodName: String): String {
    return "${methodName}${COMPOSE_DESTINATION_CLASS}"
}

internal fun String.camelCase(): String {
    val words: List<String> = split("[\\W_]+".toRegex())
    val builder = StringBuilder()
    words.forEach {
        val word = if (it.isEmpty()) it else it[0].uppercase() + it.substring(1).lowercase()
        builder.append(word)
    }

    return builder.toString()
}

internal fun String.cap(): String {
    return replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() }
}

================================================
FILE: compiler/src/main/java/zlc/season/butterfly/compiler/visitor/DestinationAnnotationVisitor.kt
================================================
package zlc.season.butterfly.compiler.visitor

import com.google.devtools.ksp.getClassDeclarationByName
import com.google.devtools.ksp.processing.Resolver
import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
import com.google.devtools.ksp.symbol.KSClassDeclaration
import com.google.devtools.ksp.symbol.KSFile
import com.google.devtools.ksp.symbol.KSFunctionDeclaration
import com.google.devtools.ksp.symbol.KSVisitorVoid
import zlc.season.butterfly.compiler.ComposeDestinationInfo
import zlc.season.butterfly.compiler.logc
import zlc.season.butterfly.compiler.loge
import zlc.season.butterfly.compiler.utils.BUNDLE_CLASS_NAME
import zlc.season.butterfly.compiler.utils.DESTINATION_NAME
import zlc.season.butterfly.compiler.utils.DESTINATION_ROUTE_KEY
import zlc.season.butterfly.compiler.utils.VIEW_MODEL_CLASS_NAME
import zlc.season.butterfly.compiler.utils.composeDestinationFullClassName
import zlc.season.butterfly.compiler.utils.getAnnotationByName
import zlc.season.butterfly.compiler.utils.getClassFullName
import zlc.season.butterfly.compiler.utils.getValue

class DestinationAnnotationVisitor(
    private val environment: SymbolProcessorEnvironment,
    private val resolver: Resolver,
    private val destinationMap: MutableMap<String, String>,
    private val composeList: MutableList<ComposeDestinationInfo>,
    private val sourcesFile: MutableList<KSFile>
) : KSVisitorVoid() {

    private val bundleClassType = resolver.getClassDeclarationByName(BUNDLE_CLASS_NAME)!!.asStarProjectedType()
    private val viewModelClassType = resolver.getClassDeclarationByName(VIEW_MODEL_CLASS_NAME)!!.asStarProjectedType()

    override fun visitClassDeclaration(classDeclaration: KSClassDeclaration, data: Unit) {
        environment.logc("process destination: $classDeclaration")
        val annotation = classDeclaration.getAnnotationByName(DESTINATION_NAME)
        if (annotation != null) {
            val routeValue = annotation.getValue(DESTINATION_ROUTE_KEY, "")
            val className = classDeclaration.getClassFullName()
            if (routeValue.isNotEmpty()) {
                environment.logc("destination processed: [route='$routeValue', target='$className']")
                destinationMap[routeValue] = className

                // add file to dependency
                sourcesFile.add(classDeclaration.containingFile!!)
            } else {
                environment.loge("[$classDeclaration] route not found!")
            }
        }
    }

    override fun visitFunctionDeclaration(function: KSFunctionDeclaration, data: Unit) {
        environment.logc("process compose destination: $function")
        val annotation = function.getAnnotationByName(DESTINATION_NAME)
        if (annotation != null) {
            val packageName = function.packageName.asString()
            val methodName = function.simpleName.asString()
            val routeValue = annotation.getValue(DESTINATION_ROUTE_KEY, "")
            if (routeValue.isNotEmpty()) {
                if (function.parameters.isNotEmpty()) {
                    when (function.parameters.size) {
                        1 -> {
                            val parameterKsType = function.parameters[0].type.resolve()
                            if (bundleClassType.isAssignableFrom(parameterKsType)) {
                                composeList.add(ComposeDestinationInfo(packageName, methodName, true, ""))
                            } else if (viewModelClassType.isAssignableFrom(parameterKsType)) {
                                val viewModelClassName = parameterKsType.getClassFullName()
                                composeList.add(ComposeDestinationInfo(packageName, methodName, false, viewModelClassName))
                            } else {
                                environment.loge("[$function] invalid parameter! Compose only support Bundle or ViewModel type!")
                            }
                        }

                        2 -> {
                            val firstParameterKsType = function.parameters[0].type.resolve()
                            val secondParameterKsType = function.parameters[1].type.resolve()
                            val isBundleFirst = bundleClassType.isAssignableFrom(firstParameterKsType)
                            val isViewModelSecond = viewModelClassType.isAssignableFrom(secondParameterKsType)

                            if (isBundleFirst && isViewModelSecond) {
                                val viewModelClassName = secondParameterKsType.getClassFullName()
                                composeList.add(ComposeDestinationInfo(packageName, methodName, true, viewModelClassName))
                            } else {
                                if (!isBundleFirst) {
                                    environment.loge("[$function] first parameter type must be Bundle!")
                                } else {
                                    environment.loge("[$function] second parameter type must be ViewModel!")
                                }
                            }
                        }

                        else -> {
                            environment.loge("[$function] invalid parameter size! Compose only support max 2 parameters!")
                        }
                    }
                } else {
                    composeList.add(ComposeDestinationInfo(packageName, methodName, false, ""))
                }

                val targetClassName = composeDestinationFullClassName(methodName)
                environment.logc("compose destination processed: [route='$routeValue', target='$targetClassName']")
                destinationMap[routeValue] = targetClassName

                // add file to dependency
                sourcesFile.add(function.containingFile!!)
            } else {
                environment.loge("[$function] route not found!")
            }
        }
    }
}

================================================
FILE: compiler/src/main/java/zlc/season/butterfly/compiler/visitor/EvadeAnnotationVisitor.kt
================================================
package zlc.season.butterfly.compiler.visitor

import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
import com.google.devtools.ksp.symbol.ClassKind
import com.google.devtools.ksp.symbol.KSClassDeclaration
import com.google.devtools.ksp.symbol.KSFile
import com.google.devtools.ksp.symbol.KSVisitorVoid
import zlc.season.butterfly.compiler.utils.EVADE_IDENTITY_KEY
import zlc.season.butterfly.compiler.utils.getClassFullName
import zlc.season.butterfly.compiler.utils.getValue
import zlc.season.butterfly.compiler.logc
import zlc.season.butterfly.compiler.loge
import zlc.season.butterfly.compiler.utils.EVADE_NAME
import zlc.season.butterfly.compiler.utils.getAnnotationByName

class EvadeAnnotationVisitor(
    private val environment: SymbolProcessorEnvironment,
    private val evadeMap: MutableMap<String, String>,
    private val sourcesFile: MutableList<KSFile>
) : KSVisitorVoid() {
    override fun visitClassDeclaration(classDeclaration: KSClassDeclaration, data: Unit) {
        if (classDeclaration.classKind == ClassKind.INTERFACE) {
            environment.logc("process evade: $classDeclaration")
            val annotation = classDeclaration.getAnnotationByName(EVADE_NAME)
            if (annotation != null) {
                val identityValue = annotation.getValue(EVADE_IDENTITY_KEY, "")
                val realKey = identityValue.ifEmpty { classDeclaration.simpleName.asString() }
                val targetClassName = classDeclaration.getClassFullName()

                environment.logc("evade processed: [identity='$realKey', target='$targetClassName']")
                evadeMap[realKey] = targetClassName

                // add file to dependency
                sourcesFile.add(classDeclaration.containingFile!!)
            }
        } else {
            environment.loge("[$classDeclaration] invalid evade. @Evade must be annotated at an interface!")
        }
    }
}

================================================
FILE: compiler/src/main/java/zlc/season/butterfly/compiler/visitor/EvadeImplAnnotationVisitor.kt
================================================
package zlc.season.butterfly.compiler.visitor

import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
import com.google.devtools.ksp.symbol.ClassKind
import com.google.devtools.ksp.symbol.KSClassDeclaration
import com.google.devtools.ksp.symbol.KSFile
import com.google.devtools.ksp.symbol.KSVisitorVoid
import zlc.season.butterfly.compiler.utils.EVADE_IDENTITY_KEY
import zlc.season.butterfly.compiler.utils.EVADE_IMPL_SUFFIX
import zlc.season.butterfly.compiler.utils.EVADE_SINGLETON_KEY
import zlc.season.butterfly.compiler.EvadeImplInfo
import zlc.season.butterfly.compiler.utils.getClassFullName
import zlc.season.butterfly.compiler.utils.getValue
import zlc.season.butterfly.compiler.logc
import zlc.season.butterfly.compiler.loge
import zlc.season.butterfly.compiler.utils.EVADE_IMPL_NAME
import zlc.season.butterfly.compiler.utils.EVADE_NAME
import zlc.season.butterfly.compiler.utils.getAnnotationByName

class EvadeImplAnnotationVisitor(
    private val environment: SymbolProcessorEnvironment,
    private val evadeImplMap: MutableMap<String, EvadeImplInfo>,
    private val sourcesFile: MutableList<KSFile>
) : KSVisitorVoid() {
    override fun visitClassDeclaration(classDeclaration: KSClassDeclaration, data: Unit) {
        if (classDeclaration.classKind == ClassKind.CLASS) {
            environment.logc("process evade impl: $classDeclaration")
            val annotation = classDeclaration.getAnnotationByName(EVADE_IMPL_NAME)
            if (annotation != null) {
                val isSingleton = annotation.getValue(EVADE_SINGLETON_KEY, true)
                val identityValue = annotation.getValue(EVADE_IDENTITY_KEY, "")
                val classSimpleName = classDeclaration.simpleName.asString()

                if (identityValue.isEmpty() && !classSimpleName.endsWith(EVADE_IMPL_SUFFIX)) {
                    environment.loge("[$classDeclaration] invalid evade impl. If your @EvadeImpl class does not provide identity value, then the class name must end with Impl!")
                } else {
                    val realKey = identityValue.ifEmpty {
                        val index = classSimpleName.lastIndexOf(EVADE_IMPL_SUFFIX)
                        classSimpleName.substring(0, index)
                    }
                    val targetClassName = classDeclaration.getClassFullName()
                    environment.logc("evade impl processed: [identity='$realKey', target='$targetClassName']")
                    evadeImplMap[realKey] = EvadeImplInfo(targetClassName, isSingleton)

                    // add file to dependency
                    sourcesFile.add(classDeclaration.containingFile!!)
                }
            }
        } else {
            environment.loge("[$classDeclaration] invalid evade impl. @EvadeImpl must be annotated at an class!")
        }
    }
}

================================================
FILE: compiler/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider
================================================
zlc.season.butterfly.compiler.ProcessorProvider

================================================
FILE: gradle/libs.versions.toml
================================================
[versions]
# android
sdk-min-version = "23"
sdk-target-version = "34"
sdk-compile-version = "34"

# library
gradle = "8.1.4"
kover = "0.7.3"
detekt = "1.23.0"

ksp = "1.9.20-1.0.14"
poet = "1.11.0"
auto-service = "1.0"

# kotlin
kotlin = "1.9.20"
kotlinx-coroutines = "1.7.2"
kotlinx-serialization-json = "1.5.1"

# android
appcompat = "1.6.1"
fragment-ktx = "1.6.2"
activity-ktx = "1.8.1"
core-ktx = "1.12.0"
arch-lifecycle = "2.6.2"
arch-lifecycle-extension = "2.2.0"
hilt = "2.46.1"

# ui
material = "1.10.0"
constraint-layout = "2.1.4"

# compose
# compose compiler
compose-compiler = "1.5.5"

activity-compose = "1.8.1"
compose = "1.5.4"
compose-material3 = "1.1.2"
accompanist = "0.31.3-beta"

# http
retrofit = "2.9.0"
retrofit-kotlinx-serialization-json = "1.0.0"
okhttp-logging = "4.11.0"
coil = "2.4.0"
exoplayer = "2.19.1"

# leak
leakcanary = "2.9.1"

# season
butterfly = "1.0.1"
clarity = "1.0.6"
yasha = "1.1.4"
bracer = "1.0.7"

# test
arch-test = "2.2.0"
junit = "4.13.2"
mockk = "1.13.5"
truth = "1.1.5"
robolectric = "4.10.3"
turbine = "1.0.0"
slf4j = "2.0.7"

# android test
androidx-junit = "1.1.5"
androidx-test-core = "1.5.0"
androidx-test-runner = "1.5.2"
androidx-test-rules = "1.5.0"
espresso-core = "3.5.1"

[libraries]
# tools
kotlin-poet = { module = "com.squareup:kotlinpoet", version.ref = "poet" }
auto-service = { module = "com.google.auto.service:auto-service", version.ref = "auto-service" }


# plugin
android-gradle = { module = "com.android.tools.build:gradle", version.ref = "gradle" }
android-gradle-api = { module = "com.android.tools.build:gradle-api", version.ref = "gradle" }
kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }

# kotlin
kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8", version.ref = "kotlin" }
kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin" }
kotlin-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinx-coroutines" }
kotlin-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization-json" }

# android
appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" }
core-ktx = { module = "androidx.core:core-ktx", version.ref = "core-ktx" }
activity-ktx = { module = "androidx.activity:activity-ktx", version.ref = "activity-ktx" }
fragment-ktx = { module = "androidx.fragment:fragment-ktx", version.ref = "fragment-ktx" }
constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version.ref = "constraint-layout" }
material = { module = "com.google.android.material:material", version.ref = "material" }
accompanist-systemuicontroller = { module = "com.google.accompanist:accompanist-systemuicontroller", version.ref = "accompanist" }
accompanist-flowlayout = { module = "com.google.accompanist:accompanist-flowlayout", version.ref = "accompanist" }
accompanist-permission = { module = "com.google.accompanist:accompanist-permissions", version.ref = "accompanist" }

# lifecycle
lifecycle-runtime-ktx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "arch-lifecycle" }
lifecycle-viewmodel-ktx = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "arch-lifecycle" }
lifecycle-extensions = { module = "androidx.lifecycle:lifecycle-extensions", version.ref = "arch-lifecycle-extension" }

# compose
compose-activity = { module = "androidx.activity:activity-compose", version.ref = "activity-compose" }
compose-viewmodel = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "arch-lifecycle" }
compose-lifecycle = { module = "androidx.lifecycle:lifecycle-runtime-compose", version.ref = "arch-lifecycle" }
compose-animation = { module = "androidx.compose.animation:animation", version.ref = "compose" }
compose-foundation = { module = "androidx.compose.foundation:foundation", version.ref = "compose" }
compose-material = { module = "androidx.compose.material:material", version.ref = "compose" }
compose-material-icons-core = { module = "androidx.compose.material:material-icons-core", version.ref = "compose" }
compose-material-icons-extended = { module = "androidx.compose.material:material-icons-extended", version.ref = "compose" }
compose-runtime = { module = "androidx.compose.runtime:runtime", version.ref = "compose" }
compose-runtime-livedata = { module = "androidx.compose.runtime:runtime-livedata", version.ref = "compose" }
compose-runtime-saveable = { module = "androidx.compose.runtime:runtime-saveable", version.ref = "compose" }
compose-ui = { module = "androidx.compose.ui:ui", version.ref = "compose" }
compose-ui-viewbinding = { module = "androidx.compose.ui:ui-viewbinding", version.ref = "compose" }
compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling", version.ref = "compose" }
compose-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview", version.ref = "compose" }
compose-ui-test = { module = "androidx.compose.ui:ui-test", version.ref = "compose" }
compose-ui-test-junit = { module = "androidx.compose.ui:ui-test-junit4", version.ref = "compose" }
compose-ui-test-manifest = { module = "androidx.compose.ui:ui-test-manifest", version.ref = "compose" }
compose-material3 = { module = "androidx.compose.material3:material3", version.ref = "compose-material3" }

# hilt
hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hilt" }
hilt-compiler = { module = "com.google.dagger:hilt-android-compiler", version.ref = "hilt" }

# retrofit
retrofit-core = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
retrofit-kotlin-serialization = { module = "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter", version.ref = "retrofit-kotlinx-serialization-json" }
okhttp-logging = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp-logging" }

# detekt
detekt-formatting = { module = "io.gitlab.arturbosch.detekt:detekt-formatting", version.ref = "detekt" }

# Coil
coil-kt = { module = "io.coil-kt:coil", version.ref = "coil" }
coil-kt-compose = { module = "io.coil-kt:coil-compose", version.ref = "coil" }
coil-kt-svg = { module = "io.coil-kt:coil-svg", version.ref = "coil" }

# video
exoplayer = { module = "com.google.android.exoplayer:exoplayer", version.ref = "exoplayer" }

# leak
leakcanary = { module = "com.squareup.leakcanary:leakcanary-android", version.ref = "leakcanary" }

# season
yasha = { module = "com.github.ssseasonnn:Yasha", version.ref = "yasha" }
bracer = { module = "com.github.ssseasonnn:Bracer", version.ref = "bracer" }
clarity = { module = "com.github.ssseasonnn:ClarityPotion", version.ref = "clarity" }

# test
junit4 = { module = "junit:junit", version.ref = "junit" }
kotlin-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinx-coroutines" }
mockk = { module = "io.mockk:mockk", version.ref = "mockk" }
mockk-agent = { module = "io.mockk:mockk-agent-jvm", version.ref = "mockk" }
truth = { module = "com.google.truth:truth", version.ref = "truth" }
robolectric = { module = "org.robolectric:robolectric", version.ref = "robolectric" }
turbine = { module = "app.cash.turbine:turbine", version.ref = "turbine" }
slf4j = { module = "org.slf4j:slf4j-nop", version.ref = "slf4j" }

# android test
core-testing = { module = "androidx.arch.core:core-testing", version.ref = "arch-test" }
hilt-testing = { module = "com.google.dagger:hilt-android-testing", version.ref = "hilt" }
androidx-junit = { module = "androidx.test.ext:junit", version.ref = "androidx-junit" }
androidx-junit-ktx = { module = "androidx.test.ext:junit-ktx", version.ref = "androidx-junit" }
androidx-test-core = { module = "androidx.test:core", version.ref = "androidx-test-core" }
androidx-test-runner = { module = "androidx.test:runner", version.ref = "androidx-test-runner" }
androidx-test-rules = { module = "androidx.test:rules", version.ref = "androidx-test-rules" }
espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "espresso-core" }

[bundles]
season = [
    "yasha",
    "bracer",
    "clarity"
]

compose = [
    "compose-activity",
    "compose-lifecycle",
    "compose-runtime",
    "compose-viewmodel",
    "compose-ui",
    "compose-material3",
    "compose-ui-tooling",
    "compose-ui-tooling-preview"
]

kotlin = [
    "kotlin-stdlib",
    "kotlin-coroutines",
    "kotlin-serialization-json"
]
android = [
    "appcompat",
    "core-ktx",
    "activity-ktx",
    "fragment-ktx",
    "constraintlayout",
    "material",
    "lifecycle-runtime-ktx",
    "lifecycle-viewmodel-ktx",
    "accompanist-systemuicontroller"
]
retrofit = [
    "retrofit-core",
    "retrofit-kotlin-serialization",
    "okhttp-logging"
]
coil = [
    "coil-kt",
    "coil-kt-compose",
    "coil-kt-svg"
]
unit-test = [
    "junit4",
    "kotlin-coroutines-test",
    "mockk",
    "mockk-agent",
    "truth",
    "turbine",
    "slf4j"
]
android-test = [
    "core-testing",
    "compose-ui-test",
    "compose-ui-test-junit",
    "compose-ui-test-manifest",
    "androidx-junit",
    "androidx-junit-ktx",
    "androidx-test-core",
    "androidx-test-runner",
    "androidx-test-rules",
    "espresso-core",
    "mockk",
    "mockk-agent",
    "truth",
    "slf4j"
]

[plugins]
application = { id = "com.android.application", version.ref = "gradle" }
library = { id = "com.android.library", version.ref = "gradle" }
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
kotlin = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
kotlin-parcelize = { id = "org.jetbrains.kotlin.plugin.parcelize", version.ref = "kotlin" }
kotlin-kover = { id = "org.jetbrains.kotlinx.kover", version.ref = "kover" }
kapt = { id = "org.jetbrains.kotlin.kapt", version.ref = "kotlin" }
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" }
butterfly = { id = "io.github.ssseasonnn.butterfly", version.ref = "butterfly" }

================================================
FILE: gradle/wrapper/gradle-wrapper.properties
================================================
#Mon Feb 07 17:16:51 CST 2022
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME


================================================
FILE: gradle.properties
================================================
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app"s APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official
# Enables namespacing of each library's R class so that its R class includes only the
# resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library
android.nonTransitiveRClass=true

================================================
FILE: gradlew
================================================
#!/usr/bin/env sh

#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

##############################################################################
##
##  Gradle start up script for UN*X
##
##############################################################################

# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
    ls=`ls -ld "$PRG"`
    link=`expr "$ls" : '.*-> \(.*\)$'`
    if expr "$link" : '/.*' > /dev/null; then
        PRG="$link"
    else
        PRG=`dirname "$PRG"`"/$link"
    fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null

APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`

# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'

# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"

warn () {
    echo "$*"
}

die () {
    echo
    echo "$*"
    echo
    exit 1
}

# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
  CYGWIN* )
    cygwin=true
    ;;
  Darwin* )
    darwin=true
    ;;
  MINGW* )
    msys=true
    ;;
  NONSTOP* )
    nonstop=true
    ;;
esac

CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar


# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
        # IBM's JDK on AIX uses strange locations for the executables
        JAVACMD="$JAVA_HOME/jre/sh/java"
    else
        JAVACMD="$JAVA_HOME/bin/java"
    fi
    if [ ! -x "$JAVACMD" ] ; then
        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME

Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
    fi
else
    JAVACMD="java"
    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.

Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi

# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
    MAX_FD_LIMIT=`ulimit -H -n`
    if [ $? -eq 0 ] ; then
        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
            MAX_FD="$MAX_FD_LIMIT"
        fi
        ulimit -n $MAX_FD
        if [ $? -ne 0 ] ; then
            warn "Could not set maximum file descriptor limit: $MAX_FD"
        fi
    else
        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
    fi
fi

# For Darwin, add options to spec
Download .txt
gitextract_u_htjb9w/

├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── README.zh.md
├── annotation/
│   ├── .gitignore
│   ├── build.gradle.kts
│   └── src/
│       └── main/
│           └── java/
│               └── zlc/
│                   └── season/
│                       └── butterfly/
│                           ├── annotation/
│                           │   ├── Annotations.kt
│                           │   └── EvadeData.kt
│                           └── module/
│                               └── Module.kt
├── build.gradle.kts
├── buildLogic/
│   ├── .gitignore
│   ├── build.gradle.kts
│   ├── gradle/
│   │   └── wrapper/
│   │       ├── gradle-wrapper.jar
│   │       └── gradle-wrapper.properties
│   ├── gradlew
│   ├── gradlew.bat
│   ├── settings.gradle.kts
│   └── src/
│       └── main/
│           └── kotlin/
│               ├── BuildLogicPlugin.kt
│               └── zlc/
│                   └── season/
│                       └── buildlogic/
│                           └── base/
│                               ├── AndroidExtensions.kt
│                               ├── BaseExtensions.kt
│                               └── MavenExtensions.kt
├── butterfly/
│   ├── .gitignore
│   ├── build.gradle.kts
│   ├── consumer-rules.pro
│   ├── proguard-rules.pro
│   └── src/
│       ├── main/
│       │   ├── AndroidManifest.xml
│       │   └── java/
│       │       └── zlc/
│       │           └── season/
│       │               └── butterfly/
│       │                   ├── Butterfly.kt
│       │                   ├── ButterflyCore.kt
│       │                   ├── DestinationHandler.kt
│       │                   ├── action/
│       │                   │   └── Action.kt
│       │                   ├── core/
│       │                   │   ├── BackStackEntryManager.kt
│       │                   │   ├── EvadeManager.kt
│       │                   │   ├── GroupEntryManager.kt
│       │                   │   ├── InterceptorManager.kt
│       │                   │   ├── ModuleManager.kt
│       │                   │   └── NavigatorManager.kt
│       │                   ├── entities/
│       │                   │   ├── BackStackEntry.kt
│       │                   │   ├── DestinationData.kt
│       │                   │   ├── EvadeData.kt
│       │                   │   └── GroupEntry.kt
│       │                   ├── interceptor/
│       │                   │   ├── DefaultInterceptor.kt
│       │                   │   └── Interceptor.kt
│       │                   ├── internal/
│       │                   │   ├── ButterflyFragment.kt
│       │                   │   ├── ButterflyHelper.kt
│       │                   │   ├── LogUtil.kt
│       │                   │   └── Util.kt
│       │                   ├── launcher/
│       │                   │   ├── DestinationLauncher.kt
│       │                   │   └── DestinationLauncherManager.kt
│       │                   └── navigator/
│       │                       ├── ActionNavigator.kt
│       │                       ├── ActivityNavigator.kt
│       │                       ├── ErrorNavigator.kt
│       │                       ├── Navigator.kt
│       │                       └── fragment/
│       │                           ├── DialogFragmentNavigator.kt
│       │                           ├── FragmentHelper.kt
│       │                           ├── FragmentNavigator.kt
│       │                           ├── FragmentParamUpdatable.kt
│       │                           ├── NavigatorContext.kt
│       │                           ├── backstack/
│       │                           │   ├── BackstackNavigator.kt
│       │                           │   ├── BackstackNavigatorHelper.kt
│       │                           │   ├── ClearTopBackstackNavigator.kt
│       │                           │   ├── SingleTopBackstackNavigator.kt
│       │                           │   └── StandardBackstackNavigator.kt
│       │                           └── group/
│       │                               └── GroupNavigator.kt
│       └── test/
│           └── java/
│               └── zlc/
│                   └── season/
│                       └── butterfly/
│                           └── ExampleUnitTest.kt
├── butterfly-compose/
│   ├── .gitignore
│   ├── build.gradle.kts
│   ├── consumer-rules.pro
│   ├── proguard-rules.pro
│   └── src/
│       ├── main/
│       │   ├── AndroidManifest.xml
│       │   └── java/
│       │       └── zlc/
│       │           └── season/
│       │               └── butterfly/
│       │                   └── compose/
│       │                       ├── ComposeDestination.kt
│       │                       ├── ComposeNavigator.kt
│       │                       ├── ComposeViewModel.kt
│       │                       ├── DestinationViewModelStoreOwner.kt
│       │                       ├── Utils.kt
│       │                       └── navigator/
│       │                           ├── ComposeBackStackNavigator.kt
│       │                           ├── ComposeGroupNavigator.kt
│       │                           └── ComposeNavigatorHelper.kt
│       └── test/
│           └── java/
│               └── zlc/
│                   └── season/
│                       └── butterfly/
│                           └── compose/
│                               └── ExampleUnitTest.kt
├── compiler/
│   ├── .gitignore
│   ├── build.gradle.kts
│   └── src/
│       └── main/
│           ├── java/
│           │   └── zlc/
│           │       └── season/
│           │           └── butterfly/
│           │               └── compiler/
│           │                   ├── Entities.kt
│           │                   ├── ProcessorProvider.kt
│           │                   ├── generator/
│           │                   │   ├── ComposableGenerator.kt
│           │                   │   └── ModuleClassGenerator.kt
│           │                   ├── utils/
│           │                   │   ├── KspUtils.kt
│           │                   │   └── Util.kt
│           │                   └── visitor/
│           │                       ├── DestinationAnnotationVisitor.kt
│           │                       ├── EvadeAnnotationVisitor.kt
│           │                       └── EvadeImplAnnotationVisitor.kt
│           └── resources/
│               └── META-INF/
│                   └── services/
│                       └── com.google.devtools.ksp.processing.SymbolProcessorProvider
├── gradle/
│   ├── libs.versions.toml
│   └── wrapper/
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── jitpack.yml
├── plugin/
│   ├── .gitignore
│   ├── build.gradle.kts
│   └── src/
│       └── main/
│           ├── java/
│           │   └── zlc/
│           │       └── season/
│           │           └── butterfly/
│           │               └── plugin/
│           │                   ├── ButterflyPlugin.kt
│           │                   ├── ModuleClassVisitorFactory.kt
│           │                   └── Utils.kt
│           └── resources/
│               └── META-INF/
│                   └── gradle-plugins/
│                       └── io.github.ssseasonnn.butterfly.properties
├── samples/
│   ├── app/
│   │   ├── .gitignore
│   │   ├── build.gradle.kts
│   │   ├── proguard-rules.pro
│   │   └── src/
│   │       └── main/
│   │           ├── AndroidManifest.xml
│   │           ├── java/
│   │           │   └── zlc/
│   │           │       └── season/
│   │           │           └── butterflydemo/
│   │           │               ├── ComposeBottomNavigationActivity.kt
│   │           │               ├── ComposeDemoActivity.kt
│   │           │               ├── DemoApplication.kt
│   │           │               ├── DestinationTestActivity.kt
│   │           │               ├── EvadeTestActivity.kt
│   │           │               ├── FragmentBottomNavigationActivity.kt
│   │           │               ├── FragmentDemoActivity.kt
│   │           │               ├── Home.kt
│   │           │               └── MainActivity.kt
│   │           └── res/
│   │               ├── drawable/
│   │               │   ├── ic_dashboard_black_24dp.xml
│   │               │   ├── ic_home_black_24dp.xml
│   │               │   ├── ic_launcher_background.xml
│   │               │   └── ic_notifications_black_24dp.xml
│   │               ├── drawable-v24/
│   │               │   └── ic_launcher_foreground.xml
│   │               ├── layout/
│   │               │   ├── activity_compose_bottom_navigation.xml
│   │               │   ├── activity_compose_demo.xml
│   │               │   ├── activity_destination_test.xml
│   │               │   ├── activity_evade_test.xml
│   │               │   ├── activity_fragment_bottom_navigation.xml
│   │               │   ├── activity_fragment_demo.xml
│   │               │   └── activity_main.xml
│   │               ├── menu/
│   │               │   └── bottom_nav_menu.xml
│   │               ├── mipmap-anydpi-v26/
│   │               │   ├── ic_launcher.xml
│   │               │   └── ic_launcher_round.xml
│   │               ├── values/
│   │               │   ├── colors.xml
│   │               │   ├── dimens.xml
│   │               │   ├── strings.xml
│   │               │   └── themes.xml
│   │               └── xml/
│   │                   └── network_security_config.xml
│   └── modules/
│       ├── base/
│       │   ├── .gitignore
│       │   ├── build.gradle.kts
│       │   ├── consumer-rules.pro
│       │   ├── proguard-rules.pro
│       │   └── src/
│       │       └── main/
│       │           ├── AndroidManifest.xml
│       │           └── java/
│       │               └── zlc/
│       │                   └── season/
│       │                       └── base/
│       │                           ├── BaseActivity.kt
│       │                           ├── BaseFragment.kt
│       │                           └── Destinations.kt
│       ├── compose/
│       │   ├── compose_dashboard/
│       │   │   ├── .gitignore
│       │   │   ├── build.gradle.kts
│       │   │   ├── consumer-rules.pro
│       │   │   ├── proguard-rules.pro
│       │   │   └── src/
│       │   │       └── main/
│       │   │           ├── AndroidManifest.xml
│       │   │           └── java/
│       │   │               └── zlc/
│       │   │                   └── season/
│       │   │                       └── compose/
│       │   │                           └── dashboard/
│       │   │                               ├── DashboardScreen.kt
│       │   │                               └── DashboardViewModel.kt
│       │   ├── compose_home/
│       │   │   ├── .gitignore
│       │   │   ├── build.gradle.kts
│       │   │   ├── consumer-rules.pro
│       │   │   ├── proguard-rules.pro
│       │   │   └── src/
│       │   │       └── main/
│       │   │           ├── AndroidManifest.xml
│       │   │           └── java/
│       │   │               └── zlc/
│       │   │                   └── season/
│       │   │                       └── compose/
│       │   │                           └── home/
│       │   │                               └── HomeScreen.kt
│       │   └── compose_notifications/
│       │       ├── .gitignore
│       │       ├── build.gradle.kts
│       │       ├── consumer-rules.pro
│       │       ├── proguard-rules.pro
│       │       └── src/
│       │           └── main/
│       │               ├── AndroidManifest.xml
│       │               └── java/
│       │                   └── zlc/
│       │                       └── season/
│       │                           └── compose/
│       │                               └── notifications/
│       │                                   ├── NotificationsScreen.kt
│       │                                   └── NotificationsViewModel.kt
│       ├── feature1/
│       │   ├── .gitignore
│       │   ├── build.gradle.kts
│       │   ├── consumer-rules.pro
│       │   ├── proguard-rules.pro
│       │   └── src/
│       │       └── main/
│       │           ├── AndroidManifest.xml
│       │           ├── java/
│       │           │   └── zlc/
│       │           │       └── season/
│       │           │           └── feature1/
│       │           │               ├── AFragment.kt
│       │           │               ├── BFragment.kt
│       │           │               ├── CFragment.kt
│       │           │               ├── TestAction.kt
│       │           │               ├── TestActivity.kt
│       │           │               ├── TestBottomSheetDialogFragment.kt
│       │           │               ├── TestDialogFragment.kt
│       │           │               ├── TestFragment.kt
│       │           │               └── TestResultActivity.kt
│       │           └── res/
│       │               ├── layout/
│       │               │   ├── activity_test.xml
│       │               │   ├── activity_test_result.xml
│       │               │   ├── dialog_test.xml
│       │               │   ├── fragment_common.xml
│       │               │   └── fragment_test.xml
│       │               └── values/
│       │                   ├── colors.xml
│       │                   └── strings.xml
│       ├── feature2/
│       │   ├── .gitignore
│       │   ├── build.gradle.kts
│       │   ├── consumer-rules.pro
│       │   ├── proguard-rules.pro
│       │   └── src/
│       │       └── main/
│       │           ├── AndroidManifest.xml
│       │           └── java/
│       │               └── zlc/
│       │                   └── season/
│       │                       └── bar/
│       │                           ├── Colors.kt
│       │                           ├── ComposeScreenA.kt
│       │                           ├── ComposeScreenB.kt
│       │                           ├── ComposeScreenC.kt
│       │                           └── ScreenViewModels.kt
│       └── normal/
│           ├── dashboard/
│           │   ├── .gitignore
│           │   ├── build.gradle.kts
│           │   ├── consumer-rules.pro
│           │   ├── proguard-rules.pro
│           │   └── src/
│           │       └── main/
│           │           ├── AndroidManifest.xml
│           │           ├── java/
│           │           │   └── zlc/
│           │           │       └── season/
│           │           │           └── dashboard/
│           │           │               ├── DashboardFragment.kt
│           │           │               └── DashboardViewModel.kt
│           │           └── res/
│           │               └── layout/
│           │                   └── fragment_dashboard.xml
│           ├── home/
│           │   ├── .gitignore
│           │   ├── build.gradle.kts
│           │   ├── consumer-rules.pro
│           │   ├── proguard-rules.pro
│           │   └── src/
│           │       └── main/
│           │           ├── AndroidManifest.xml
│           │           ├── java/
│           │           │   └── zlc/
│           │           │       └── season/
│           │           │           └── home/
│           │           │               ├── HomeFragment.kt
│           │           │               └── HomeImpl.kt
│           │           └── res/
│           │               └── layout/
│           │                   └── fragment_home.xml
│           └── notifications/
│               ├── .gitignore
│               ├── build.gradle.kts
│               ├── consumer-rules.pro
│               ├── proguard-rules.pro
│               └── src/
│                   └── main/
│                       ├── AndroidManifest.xml
│                       ├── java/
│                       │   └── zlc/
│                       │       └── season/
│                       │           └── notifications/
│                       │               ├── NotificationsFragment.kt
│                       │               └── NotificationsViewModel.kt
│                       └── res/
│                           └── layout/
│                               └── fragment_notifications.xml
└── settings.gradle.kts
Condensed preview — 221 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (340K chars).
[
  {
    "path": ".gitignore",
    "chars": 231,
    "preview": "*.iml\n.idea\n.gradle\n/local.properties\n/.idea/caches\n/.idea/libraries\n/.idea/modules.xml\n/.idea/workspace.xml\n/.idea/navE"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 226,
    "preview": "# Changelog\nAll notable changes to this project will be documented in this file.\n\n## [1.2.0] - 2022-11-19\n### Support Co"
  },
  {
    "path": "LICENSE",
    "chars": 11357,
    "preview": "                                 Apache License\n                           Version 2.0, January 2004\n                   "
  },
  {
    "path": "README.md",
    "chars": 4982,
    "preview": "![](Butterfly.png)\n\n[![](https://jitpack.io/v/ssseasonnn/Butterfly.svg)](https://jitpack.io/#ssseasonnn/Butterfly)\n\n# Bu"
  },
  {
    "path": "README.zh.md",
    "chars": 3627,
    "preview": "![](Butterfly.png)\n\n[![](https://jitpack.io/v/ssseasonnn/Butterfly.svg)](https://jitpack.io/#ssseasonnn/Butterfly)\n\n# Bu"
  },
  {
    "path": "annotation/.gitignore",
    "chars": 6,
    "preview": "/build"
  },
  {
    "path": "annotation/build.gradle.kts",
    "chars": 61,
    "preview": "plugins {\n    id(\"kotlin\")\n}\n\ngroup = \"com.github.ssseasonnn\""
  },
  {
    "path": "annotation/src/main/java/zlc/season/butterfly/annotation/Annotations.kt",
    "chars": 487,
    "preview": "package zlc.season.butterfly.annotation\n\n@Retention(AnnotationRetention.BINARY)\n@Target(AnnotationTarget.CLASS, Annotati"
  },
  {
    "path": "annotation/src/main/java/zlc/season/butterfly/annotation/EvadeData.kt",
    "chars": 104,
    "preview": "package zlc.season.butterfly.annotation\n\ndata class EvadeData(val cls: Class<*>, val singleton: Boolean)"
  },
  {
    "path": "annotation/src/main/java/zlc/season/butterfly/module/Module.kt",
    "chars": 244,
    "preview": "package zlc.season.butterfly.module\n\nimport zlc.season.butterfly.annotation.EvadeData\n\ninterface Module {\n    fun getDes"
  },
  {
    "path": "build.gradle.kts",
    "chars": 637,
    "preview": "@file:Suppress(\"DSL_SCOPE_VIOLATION\", \"UnstableApiUsage\")\n\nplugins {\n    id(\"build.logic\")\n    alias(libs.plugins.applic"
  },
  {
    "path": "buildLogic/.gitignore",
    "chars": 6,
    "preview": "/build"
  },
  {
    "path": "buildLogic/build.gradle.kts",
    "chars": 428,
    "preview": "plugins {\n    `kotlin-dsl`\n}\n\njava {\n    sourceCompatibility = JavaVersion.VERSION_17\n    targetCompatibility = JavaVers"
  },
  {
    "path": "buildLogic/gradle/wrapper/gradle-wrapper.properties",
    "chars": 221,
    "preview": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributi"
  },
  {
    "path": "buildLogic/gradlew",
    "chars": 8474,
    "preview": "#!/bin/sh\n\n#\n# Copyright © 2015-2021 the original authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"Lice"
  },
  {
    "path": "buildLogic/gradlew.bat",
    "chars": 2776,
    "preview": "@rem\n@rem Copyright 2015 the original author or authors.\n@rem\n@rem Licensed under the Apache License, Version 2.0 (the \""
  },
  {
    "path": "buildLogic/settings.gradle.kts",
    "chars": 232,
    "preview": "@file:Suppress(\"DSL_SCOPE_VIOLATION\", \"UnstableApiUsage\")\n\nenableFeaturePreview(\"TYPESAFE_PROJECT_ACCESSORS\")\n\ndependenc"
  },
  {
    "path": "buildLogic/src/main/kotlin/BuildLogicPlugin.kt",
    "chars": 264,
    "preview": "import org.gradle.api.Plugin\nimport org.gradle.api.Project\nimport zlc.season.buildlogic.base.setupMaven\n\nclass BuildLogi"
  },
  {
    "path": "buildLogic/src/main/kotlin/zlc/season/buildlogic/base/AndroidExtensions.kt",
    "chars": 2393,
    "preview": "@file:Suppress(\"UnstableApiUsage\")\n\npackage zlc.season.buildlogic.base\n\nimport com.android.build.api.dsl.CommonExtension"
  },
  {
    "path": "buildLogic/src/main/kotlin/zlc/season/buildlogic/base/BaseExtensions.kt",
    "chars": 3241,
    "preview": "@file:Suppress(\"TooManyFunctions\", \"UnstableApiUsage\")\n\npackage zlc.season.buildlogic.base\n\nimport com.android.build.gra"
  },
  {
    "path": "buildLogic/src/main/kotlin/zlc/season/buildlogic/base/MavenExtensions.kt",
    "chars": 912,
    "preview": "package zlc.season.buildlogic.base\n\nimport org.gradle.api.Project\nimport org.gradle.api.publish.PublishingExtension\nimpo"
  },
  {
    "path": "butterfly/.gitignore",
    "chars": 6,
    "preview": "/build"
  },
  {
    "path": "butterfly/build.gradle.kts",
    "chars": 491,
    "preview": "import zlc.season.buildlogic.base.androidLibrary\n\n@Suppress(\"DSL_SCOPE_VIOLATION\")\nplugins {\n    alias(libs.plugins.libr"
  },
  {
    "path": "butterfly/consumer-rules.pro",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "butterfly/proguard-rules.pro",
    "chars": 754,
    "preview": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguar"
  },
  {
    "path": "butterfly/src/main/AndroidManifest.xml",
    "chars": 176,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package="
  },
  {
    "path": "butterfly/src/main/java/zlc/season/butterfly/Butterfly.kt",
    "chars": 903,
    "preview": "package zlc.season.butterfly\n\nimport android.content.Context\nimport androidx.core.os.bundleOf\nimport zlc.season.butterfl"
  },
  {
    "path": "butterfly/src/main/java/zlc/season/butterfly/ButterflyCore.kt",
    "chars": 2541,
    "preview": "package zlc.season.butterfly\n\nimport android.content.Context\nimport android.os.Bundle\nimport zlc.season.butterfly.core.E"
  },
  {
    "path": "butterfly/src/main/java/zlc/season/butterfly/DestinationHandler.kt",
    "chars": 7052,
    "preview": "package zlc.season.butterfly\n\nimport android.content.Context\nimport android.os.Bundle\nimport androidx.core.os.bundleOf\ni"
  },
  {
    "path": "butterfly/src/main/java/zlc/season/butterfly/action/Action.kt",
    "chars": 189,
    "preview": "package zlc.season.butterfly.action\n\nimport android.content.Context\nimport android.os.Bundle\n\ninterface Action {\n    fun"
  },
  {
    "path": "butterfly/src/main/java/zlc/season/butterfly/core/BackStackEntryManager.kt",
    "chars": 5873,
    "preview": "package zlc.season.butterfly.core\n\nimport android.app.Activity\nimport android.os.Bundle\nimport androidx.fragment.app.Dia"
  },
  {
    "path": "butterfly/src/main/java/zlc/season/butterfly/core/EvadeManager.kt",
    "chars": 3109,
    "preview": "package zlc.season.butterfly.core\n\nimport zlc.season.butterfly.entities.EvadeData\nimport zlc.season.butterfly.internal.l"
  },
  {
    "path": "butterfly/src/main/java/zlc/season/butterfly/core/GroupEntryManager.kt",
    "chars": 3854,
    "preview": "package zlc.season.butterfly.core\n\nimport android.app.Activity\nimport android.os.Bundle\nimport androidx.fragment.app.Fra"
  },
  {
    "path": "butterfly/src/main/java/zlc/season/butterfly/core/InterceptorManager.kt",
    "chars": 884,
    "preview": "package zlc.season.butterfly.core\n\nimport android.content.Context\nimport zlc.season.butterfly.entities.DestinationData\ni"
  },
  {
    "path": "butterfly/src/main/java/zlc/season/butterfly/core/ModuleManager.kt",
    "chars": 1607,
    "preview": "package zlc.season.butterfly.core\n\nimport zlc.season.butterfly.entities.EvadeData\nimport zlc.season.butterfly.module.Mod"
  },
  {
    "path": "butterfly/src/main/java/zlc/season/butterfly/core/NavigatorManager.kt",
    "chars": 3790,
    "preview": "package zlc.season.butterfly.core\n\nimport android.app.Activity\nimport android.content.Context\nimport android.os.Bundle\ni"
  },
  {
    "path": "butterfly/src/main/java/zlc/season/butterfly/entities/BackStackEntry.kt",
    "chars": 102,
    "preview": "package zlc.season.butterfly.entities\n\ndata class BackStackEntry(val destinationData: DestinationData)"
  },
  {
    "path": "butterfly/src/main/java/zlc/season/butterfly/entities/DestinationData.kt",
    "chars": 848,
    "preview": "package zlc.season.butterfly.entities\n\nimport android.os.Bundle\nimport android.os.Parcelable\nimport kotlinx.parcelize.Pa"
  },
  {
    "path": "butterfly/src/main/java/zlc/season/butterfly/entities/EvadeData.kt",
    "chars": 352,
    "preview": "package zlc.season.butterfly.entities\n\ndata class EvadeData(\n    val identity: String,\n    val className: String,\n    va"
  },
  {
    "path": "butterfly/src/main/java/zlc/season/butterfly/entities/GroupEntry.kt",
    "chars": 98,
    "preview": "package zlc.season.butterfly.entities\n\ndata class GroupEntry(val destinationData: DestinationData)"
  },
  {
    "path": "butterfly/src/main/java/zlc/season/butterfly/interceptor/DefaultInterceptor.kt",
    "chars": 595,
    "preview": "package zlc.season.butterfly.interceptor\n\nimport android.content.Context\nimport zlc.season.butterfly.entities.Destinatio"
  },
  {
    "path": "butterfly/src/main/java/zlc/season/butterfly/interceptor/Interceptor.kt",
    "chars": 340,
    "preview": "package zlc.season.butterfly.interceptor\n\nimport android.content.Context\nimport zlc.season.butterfly.entities.Destinatio"
  },
  {
    "path": "butterfly/src/main/java/zlc/season/butterfly/internal/ButterflyFragment.kt",
    "chars": 5916,
    "preview": "package zlc.season.butterfly.internal\n\nimport android.app.Activity\nimport android.content.Intent\nimport android.os.Bundl"
  },
  {
    "path": "butterfly/src/main/java/zlc/season/butterfly/internal/ButterflyHelper.kt",
    "chars": 1371,
    "preview": "package zlc.season.butterfly.internal\n\nimport android.app.Activity\nimport android.app.Activity.RESULT_OK\nimport android."
  },
  {
    "path": "butterfly/src/main/java/zlc/season/butterfly/internal/LogUtil.kt",
    "chars": 673,
    "preview": "package zlc.season.butterfly.internal\n\nimport android.util.Log\n\nvar enableLog = true\nval TAG = \"Butterfly\"\n\nfun <T> T.lo"
  },
  {
    "path": "butterfly/src/main/java/zlc/season/butterfly/internal/Util.kt",
    "chars": 970,
    "preview": "package zlc.season.butterfly.internal\n\nimport android.app.Activity\nimport androidx.core.net.toUri\nimport java.util.*\n\nin"
  },
  {
    "path": "butterfly/src/main/java/zlc/season/butterfly/launcher/DestinationLauncher.kt",
    "chars": 736,
    "preview": "package zlc.season.butterfly.launcher\n\nimport android.content.Context\nimport android.os.Bundle\nimport kotlinx.coroutines"
  },
  {
    "path": "butterfly/src/main/java/zlc/season/butterfly/launcher/DestinationLauncherManager.kt",
    "chars": 2742,
    "preview": "package zlc.season.butterfly.launcher\n\nimport android.app.Activity\nimport android.os.Bundle\nimport zlc.season.butterfly."
  },
  {
    "path": "butterfly/src/main/java/zlc/season/butterfly/navigator/ActionNavigator.kt",
    "chars": 812,
    "preview": "package zlc.season.butterfly.navigator\n\nimport android.content.Context\nimport android.os.Bundle\nimport zlc.season.butter"
  },
  {
    "path": "butterfly/src/main/java/zlc/season/butterfly/navigator/ActivityNavigator.kt",
    "chars": 4672,
    "preview": "package zlc.season.butterfly.navigator\n\nimport android.app.Activity\nimport android.app.Application\nimport android.conten"
  },
  {
    "path": "butterfly/src/main/java/zlc/season/butterfly/navigator/ErrorNavigator.kt",
    "chars": 396,
    "preview": "package zlc.season.butterfly.navigator\n\nimport android.content.Context\nimport android.os.Bundle\nimport zlc.season.butter"
  },
  {
    "path": "butterfly/src/main/java/zlc/season/butterfly/navigator/Navigator.kt",
    "chars": 417,
    "preview": "package zlc.season.butterfly.navigator\n\nimport android.app.Activity\nimport android.content.Context\nimport android.os.Bun"
  },
  {
    "path": "butterfly/src/main/java/zlc/season/butterfly/navigator/fragment/DialogFragmentNavigator.kt",
    "chars": 1823,
    "preview": "package zlc.season.butterfly.navigator.fragment\n\nimport android.app.Activity\nimport android.content.Context\nimport andro"
  },
  {
    "path": "butterfly/src/main/java/zlc/season/butterfly/navigator/fragment/FragmentHelper.kt",
    "chars": 6758,
    "preview": "package zlc.season.butterfly.navigator.fragment\n\nimport android.os.Bundle\nimport android.view.View\nimport android.view.V"
  },
  {
    "path": "butterfly/src/main/java/zlc/season/butterfly/navigator/fragment/FragmentNavigator.kt",
    "chars": 1920,
    "preview": "package zlc.season.butterfly.navigator.fragment\n\nimport android.app.Activity\nimport android.content.Context\nimport andro"
  },
  {
    "path": "butterfly/src/main/java/zlc/season/butterfly/navigator/fragment/FragmentParamUpdatable.kt",
    "chars": 153,
    "preview": "package zlc.season.butterfly.navigator.fragment\n\nimport android.os.Bundle\n\ninterface FragmentParamUpdatable {\n    fun on"
  },
  {
    "path": "butterfly/src/main/java/zlc/season/butterfly/navigator/fragment/NavigatorContext.kt",
    "chars": 2009,
    "preview": "package zlc.season.butterfly.navigator.fragment\n\nimport androidx.fragment.app.Fragment\nimport androidx.fragment.app.Frag"
  },
  {
    "path": "butterfly/src/main/java/zlc/season/butterfly/navigator/fragment/backstack/BackstackNavigator.kt",
    "chars": 403,
    "preview": "package zlc.season.butterfly.navigator.fragment.backstack\n\nimport androidx.fragment.app.Fragment\nimport androidx.fragmen"
  },
  {
    "path": "butterfly/src/main/java/zlc/season/butterfly/navigator/fragment/backstack/BackstackNavigatorHelper.kt",
    "chars": 1415,
    "preview": "package zlc.season.butterfly.navigator.fragment.backstack\n\nimport androidx.fragment.app.Fragment\nimport androidx.fragmen"
  },
  {
    "path": "butterfly/src/main/java/zlc/season/butterfly/navigator/fragment/backstack/ClearTopBackstackNavigator.kt",
    "chars": 1952,
    "preview": "package zlc.season.butterfly.navigator.fragment.backstack\n\nimport androidx.fragment.app.Fragment\nimport androidx.fragmen"
  },
  {
    "path": "butterfly/src/main/java/zlc/season/butterfly/navigator/fragment/backstack/SingleTopBackstackNavigator.kt",
    "chars": 1615,
    "preview": "package zlc.season.butterfly.navigator.fragment.backstack\n\nimport androidx.fragment.app.Fragment\nimport androidx.fragmen"
  },
  {
    "path": "butterfly/src/main/java/zlc/season/butterfly/navigator/fragment/backstack/StandardBackstackNavigator.kt",
    "chars": 976,
    "preview": "package zlc.season.butterfly.navigator.fragment.backstack\n\nimport androidx.fragment.app.Fragment\nimport androidx.fragmen"
  },
  {
    "path": "butterfly/src/main/java/zlc/season/butterfly/navigator/fragment/group/GroupNavigator.kt",
    "chars": 1763,
    "preview": "package zlc.season.butterfly.navigator.fragment.group\n\nimport androidx.fragment.app.Fragment\nimport androidx.fragment.ap"
  },
  {
    "path": "butterfly/src/test/java/zlc/season/butterfly/ExampleUnitTest.kt",
    "chars": 344,
    "preview": "package zlc.season.butterfly\n\nimport org.junit.Test\n\nimport org.junit.Assert.*\n\n/**\n * Example local unit test, which wi"
  },
  {
    "path": "butterfly-compose/.gitignore",
    "chars": 6,
    "preview": "/build"
  },
  {
    "path": "butterfly-compose/build.gradle.kts",
    "chars": 514,
    "preview": "import zlc.season.buildlogic.base.androidLibrary\nimport zlc.season.buildlogic.base.enableCompose\n\n@Suppress(\"DSL_SCOPE_V"
  },
  {
    "path": "butterfly-compose/consumer-rules.pro",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "butterfly-compose/proguard-rules.pro",
    "chars": 754,
    "preview": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguar"
  },
  {
    "path": "butterfly-compose/src/main/AndroidManifest.xml",
    "chars": 121,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n</manifest"
  },
  {
    "path": "butterfly-compose/src/main/java/zlc/season/butterfly/compose/ComposeDestination.kt",
    "chars": 569,
    "preview": "package zlc.season.butterfly.compose\n\nimport android.os.Bundle\nimport androidx.compose.runtime.Composable\n\nopen class Co"
  },
  {
    "path": "butterfly-compose/src/main/java/zlc/season/butterfly/compose/ComposeNavigator.kt",
    "chars": 3883,
    "preview": "package zlc.season.butterfly.compose\n\nimport android.app.Activity\nimport android.content.Context\nimport android.os.Bundl"
  },
  {
    "path": "butterfly-compose/src/main/java/zlc/season/butterfly/compose/ComposeViewModel.kt",
    "chars": 3061,
    "preview": "package zlc.season.butterfly.compose\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.ViewModelProvider\nim"
  },
  {
    "path": "butterfly-compose/src/main/java/zlc/season/butterfly/compose/DestinationViewModelStoreOwner.kt",
    "chars": 801,
    "preview": "package zlc.season.butterfly.compose\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.ViewModelProvider\nim"
  },
  {
    "path": "butterfly-compose/src/main/java/zlc/season/butterfly/compose/Utils.kt",
    "chars": 2205,
    "preview": "package zlc.season.butterfly.compose\n\nimport android.app.Activity\nimport android.view.ViewGroup\nimport androidx.compose."
  },
  {
    "path": "butterfly-compose/src/main/java/zlc/season/butterfly/compose/navigator/ComposeBackStackNavigator.kt",
    "chars": 2551,
    "preview": "package zlc.season.butterfly.compose.navigator\n\nimport androidx.activity.ComponentActivity\nimport zlc.season.butterfly.c"
  },
  {
    "path": "butterfly-compose/src/main/java/zlc/season/butterfly/compose/navigator/ComposeGroupNavigator.kt",
    "chars": 739,
    "preview": "package zlc.season.butterfly.compose.navigator\n\nimport androidx.activity.ComponentActivity\nimport zlc.season.butterfly.c"
  },
  {
    "path": "butterfly-compose/src/main/java/zlc/season/butterfly/compose/navigator/ComposeNavigatorHelper.kt",
    "chars": 4422,
    "preview": "package zlc.season.butterfly.compose.navigator\n\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.a"
  },
  {
    "path": "butterfly-compose/src/test/java/zlc/season/butterfly/compose/ExampleUnitTest.kt",
    "chars": 352,
    "preview": "package zlc.season.butterfly.compose\n\nimport org.junit.Test\n\nimport org.junit.Assert.*\n\n/**\n * Example local unit test, "
  },
  {
    "path": "compiler/.gitignore",
    "chars": 6,
    "preview": "/build"
  },
  {
    "path": "compiler/build.gradle.kts",
    "chars": 301,
    "preview": "@file:Suppress(\"DSL_SCOPE_VIOLATION\", \"UnstableApiUsage\")\n\nplugins {\n    kotlin(\"jvm\")\n}\n\ngroup = \"com.github.ssseasonnn"
  },
  {
    "path": "compiler/src/main/java/zlc/season/butterfly/compiler/Entities.kt",
    "chars": 264,
    "preview": "package zlc.season.butterfly.compiler\n\ndata class EvadeImplInfo(val className: String, val singleton: Boolean)\n\ndata cla"
  },
  {
    "path": "compiler/src/main/java/zlc/season/butterfly/compiler/ProcessorProvider.kt",
    "chars": 7110,
    "preview": "package zlc.season.butterfly.compiler\n\nimport com.google.devtools.ksp.processing.Dependencies\nimport com.google.devtools"
  },
  {
    "path": "compiler/src/main/java/zlc/season/butterfly/compiler/generator/ComposableGenerator.kt",
    "chars": 7340,
    "preview": "package zlc.season.butterfly.compiler.generator\n\nimport com.squareup.kotlinpoet.AnnotationSpec\nimport com.squareup.kotli"
  },
  {
    "path": "compiler/src/main/java/zlc/season/butterfly/compiler/generator/ModuleClassGenerator.kt",
    "chars": 5386,
    "preview": "package zlc.season.butterfly.compiler.generator\n\nimport com.squareup.kotlinpoet.CodeBlock\nimport com.squareup.kotlinpoet"
  },
  {
    "path": "compiler/src/main/java/zlc/season/butterfly/compiler/utils/KspUtils.kt",
    "chars": 1416,
    "preview": "package zlc.season.butterfly.compiler.utils\n\nimport com.google.devtools.ksp.symbol.KSAnnotation\nimport com.google.devtoo"
  },
  {
    "path": "compiler/src/main/java/zlc/season/butterfly/compiler/utils/Util.kt",
    "chars": 2387,
    "preview": "package zlc.season.butterfly.compiler.utils\n\nimport java.util.Locale\nimport java.io.File.separatorChar as s\n\n/**\n * defa"
  },
  {
    "path": "compiler/src/main/java/zlc/season/butterfly/compiler/visitor/DestinationAnnotationVisitor.kt",
    "chars": 5933,
    "preview": "package zlc.season.butterfly.compiler.visitor\n\nimport com.google.devtools.ksp.getClassDeclarationByName\nimport com.googl"
  },
  {
    "path": "compiler/src/main/java/zlc/season/butterfly/compiler/visitor/EvadeAnnotationVisitor.kt",
    "chars": 1914,
    "preview": "package zlc.season.butterfly.compiler.visitor\n\nimport com.google.devtools.ksp.processing.SymbolProcessorEnvironment\nimpo"
  },
  {
    "path": "compiler/src/main/java/zlc/season/butterfly/compiler/visitor/EvadeImplAnnotationVisitor.kt",
    "chars": 2833,
    "preview": "package zlc.season.butterfly.compiler.visitor\n\nimport com.google.devtools.ksp.processing.SymbolProcessorEnvironment\nimpo"
  },
  {
    "path": "compiler/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider",
    "chars": 47,
    "preview": "zlc.season.butterfly.compiler.ProcessorProvider"
  },
  {
    "path": "gradle/libs.versions.toml",
    "chars": 10200,
    "preview": "[versions]\n# android\nsdk-min-version = \"23\"\nsdk-target-version = \"34\"\nsdk-compile-version = \"34\"\n\n# library\ngradle = \"8."
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "chars": 230,
    "preview": "#Mon Feb 07 17:16:51 CST 2022\ndistributionBase=GRADLE_USER_HOME\ndistributionUrl=https\\://services.gradle.org/distributio"
  },
  {
    "path": "gradle.properties",
    "chars": 1358,
    "preview": "# Project-wide Gradle settings.\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will ov"
  },
  {
    "path": "gradlew",
    "chars": 5766,
    "preview": "#!/usr/bin/env sh\n\n#\n# Copyright 2015 the original author or authors.\n#\n# Licensed under the Apache License, Version 2.0"
  },
  {
    "path": "gradlew.bat",
    "chars": 2674,
    "preview": "@rem\n@rem Copyright 2015 the original author or authors.\n@rem\n@rem Licensed under the Apache License, Version 2.0 (the \""
  },
  {
    "path": "jitpack.yml",
    "chars": 18,
    "preview": "jdk:\n  - openjdk17"
  },
  {
    "path": "plugin/.gitignore",
    "chars": 6,
    "preview": "/build"
  },
  {
    "path": "plugin/build.gradle.kts",
    "chars": 1256,
    "preview": "plugins {\n    id(\"kotlin\")\n    id(\"groovy\")\n    id(\"java-gradle-plugin\")\n    id(\"maven-publish\")\n    id(\"com.gradle.plug"
  },
  {
    "path": "plugin/src/main/java/zlc/season/butterfly/plugin/ButterflyPlugin.kt",
    "chars": 1766,
    "preview": "package zlc.season.butterfly.plugin\n\nimport com.android.build.api.instrumentation.FramesComputationMode\nimport com.andro"
  },
  {
    "path": "plugin/src/main/java/zlc/season/butterfly/plugin/ModuleClassVisitorFactory.kt",
    "chars": 3024,
    "preview": "package zlc.season.butterfly.plugin\n\nimport com.android.build.api.instrumentation.AsmClassVisitorFactory\nimport com.andr"
  },
  {
    "path": "plugin/src/main/java/zlc/season/butterfly/plugin/Utils.kt",
    "chars": 97,
    "preview": "package zlc.season.butterfly.plugin\n\nfun String.log() {\n    println(\"[Butterfly Plugin] $this\")\n}"
  },
  {
    "path": "plugin/src/main/resources/META-INF/gradle-plugins/io.github.ssseasonnn.butterfly.properties",
    "chars": 64,
    "preview": "implementation-class=zlc.season.butterfly.plugin.ButterflyPlugin"
  },
  {
    "path": "samples/app/.gitignore",
    "chars": 6,
    "preview": "/build"
  },
  {
    "path": "samples/app/build.gradle.kts",
    "chars": 1406,
    "preview": "@file:Suppress(\"DSL_SCOPE_VIOLATION\", \"UnstableApiUsage\")\n\nimport zlc.season.buildlogic.base.androidApplication\nimport z"
  },
  {
    "path": "samples/app/proguard-rules.pro",
    "chars": 1169,
    "preview": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguar"
  },
  {
    "path": "samples/app/src/main/AndroidManifest.xml",
    "chars": 1709,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:to"
  },
  {
    "path": "samples/app/src/main/java/zlc/season/butterflydemo/ComposeBottomNavigationActivity.kt",
    "chars": 1614,
    "preview": "package zlc.season.butterflydemo\n\nimport android.os.Bundle\nimport androidx.appcompat.app.AppCompatActivity\nimport zlc.se"
  },
  {
    "path": "samples/app/src/main/java/zlc/season/butterflydemo/ComposeDemoActivity.kt",
    "chars": 1133,
    "preview": "package zlc.season.butterflydemo\n\nimport android.os.Bundle\nimport androidx.appcompat.app.AppCompatActivity\nimport zlc.se"
  },
  {
    "path": "samples/app/src/main/java/zlc/season/butterflydemo/DemoApplication.kt",
    "chars": 256,
    "preview": "package zlc.season.butterflydemo\n\nimport android.app.Application\n\n//import zlc.season.compose.TestModule\n\nclass DemoAppl"
  },
  {
    "path": "samples/app/src/main/java/zlc/season/butterflydemo/DestinationTestActivity.kt",
    "chars": 2575,
    "preview": "package zlc.season.butterflydemo\n\nimport android.annotation.SuppressLint\nimport android.os.Bundle\nimport androidx.appcom"
  },
  {
    "path": "samples/app/src/main/java/zlc/season/butterflydemo/EvadeTestActivity.kt",
    "chars": 1225,
    "preview": "package zlc.season.butterflydemo\n\nimport android.os.Bundle\nimport androidx.activity.compose.setContent\nimport androidx.a"
  },
  {
    "path": "samples/app/src/main/java/zlc/season/butterflydemo/FragmentBottomNavigationActivity.kt",
    "chars": 1718,
    "preview": "package zlc.season.butterflydemo\n\nimport android.os.Bundle\nimport androidx.appcompat.app.AppCompatActivity\nimport zlc.se"
  },
  {
    "path": "samples/app/src/main/java/zlc/season/butterflydemo/FragmentDemoActivity.kt",
    "chars": 2063,
    "preview": "package zlc.season.butterflydemo\n\nimport android.annotation.SuppressLint\nimport android.os.Bundle\nimport zlc.season.base"
  },
  {
    "path": "samples/app/src/main/java/zlc/season/butterflydemo/Home.kt",
    "chars": 431,
    "preview": "package zlc.season.butterflydemo\n\nimport androidx.fragment.app.FragmentManager\nimport zlc.season.butterfly.annotation.Ev"
  },
  {
    "path": "samples/app/src/main/java/zlc/season/butterflydemo/MainActivity.kt",
    "chars": 1343,
    "preview": "package zlc.season.butterflydemo\n\nimport android.os.Bundle\nimport androidx.appcompat.app.AppCompatActivity\nimport zlc.se"
  },
  {
    "path": "samples/app/src/main/res/drawable/ic_dashboard_black_24dp.xml",
    "chars": 352,
    "preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n  "
  },
  {
    "path": "samples/app/src/main/res/drawable/ic_home_black_24dp.xml",
    "chars": 310,
    "preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n  "
  },
  {
    "path": "samples/app/src/main/res/drawable/ic_launcher_background.xml",
    "chars": 5606,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:wi"
  },
  {
    "path": "samples/app/src/main/res/drawable/ic_notifications_black_24dp.xml",
    "chars": 464,
    "preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n  "
  },
  {
    "path": "samples/app/src/main/res/drawable-v24/ic_launcher_foreground.xml",
    "chars": 1702,
    "preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:aapt=\"http://schemas.android.com/aapt\"\n    "
  },
  {
    "path": "samples/app/src/main/res/layout/activity_compose_bottom_navigation.xml",
    "chars": 1321,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas."
  },
  {
    "path": "samples/app/src/main/res/layout/activity_compose_demo.xml",
    "chars": 1752,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmln"
  },
  {
    "path": "samples/app/src/main/res/layout/activity_destination_test.xml",
    "chars": 3274,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns"
  },
  {
    "path": "samples/app/src/main/res/layout/activity_evade_test.xml",
    "chars": 1348,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    andro"
  },
  {
    "path": "samples/app/src/main/res/layout/activity_fragment_bottom_navigation.xml",
    "chars": 1321,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas."
  },
  {
    "path": "samples/app/src/main/res/layout/activity_fragment_demo.xml",
    "chars": 1731,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmln"
  },
  {
    "path": "samples/app/src/main/res/layout/activity_main.xml",
    "chars": 2706,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns"
  },
  {
    "path": "samples/app/src/main/res/menu/bottom_nav_menu.xml",
    "chars": 608,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <item\n    "
  },
  {
    "path": "samples/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml",
    "chars": 272,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <b"
  },
  {
    "path": "samples/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml",
    "chars": 272,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <b"
  },
  {
    "path": "samples/app/src/main/res/values/colors.xml",
    "chars": 194,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"black\">#FF000000</color>\n    <color name=\"white\">#FF"
  },
  {
    "path": "samples/app/src/main/res/values/dimens.xml",
    "chars": 210,
    "preview": "<resources>\n    <!-- Default screen margins, per the Android Design guidelines. -->\n    <dimen name=\"activity_horizontal"
  },
  {
    "path": "samples/app/src/main/res/values/strings.xml",
    "chars": 236,
    "preview": "<resources>\n    <string name=\"app_name\">ButterflyDemo</string>\n\n    <string name=\"title_home\">Home</string>\n    <string "
  },
  {
    "path": "samples/app/src/main/res/values/themes.xml",
    "chars": 842,
    "preview": "<resources xmlns:tools=\"http://schemas.android.com/tools\">\n    <!-- Base application theme. -->\n    <style name=\"Theme.B"
  },
  {
    "path": "samples/app/src/main/res/xml/network_security_config.xml",
    "chars": 292,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<network-security-config>\n    <base-config cleartextTrafficPermitted=\"true\">\n    "
  },
  {
    "path": "samples/modules/base/.gitignore",
    "chars": 6,
    "preview": "/build"
  },
  {
    "path": "samples/modules/base/build.gradle.kts",
    "chars": 489,
    "preview": "import zlc.season.buildlogic.base.androidLibrary\n\n@Suppress(\"DSL_SCOPE_VIOLATION\")\nplugins {\n    alias(libs.plugins.libr"
  },
  {
    "path": "samples/modules/base/consumer-rules.pro",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "samples/modules/base/proguard-rules.pro",
    "chars": 754,
    "preview": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguar"
  },
  {
    "path": "samples/modules/base/src/main/AndroidManifest.xml",
    "chars": 62,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest>\n\n</manifest>"
  },
  {
    "path": "samples/modules/base/src/main/java/zlc/season/base/BaseActivity.kt",
    "chars": 545,
    "preview": "package zlc.season.base\n\nimport android.os.Bundle\nimport androidx.appcompat.app.AppCompatActivity\n\nopen class BaseActivi"
  },
  {
    "path": "samples/modules/base/src/main/java/zlc/season/base/BaseFragment.kt",
    "chars": 1534,
    "preview": "package zlc.season.base\n\nimport android.content.Context\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimpo"
  },
  {
    "path": "samples/modules/base/src/main/java/zlc/season/base/Destinations.kt",
    "chars": 1384,
    "preview": "package zlc.season.base\n\nobject Destinations {\n    private const val HOST = \"butterfly_demo\"\n\n    const val DESTINATION_"
  },
  {
    "path": "samples/modules/compose/compose_dashboard/.gitignore",
    "chars": 6,
    "preview": "/build"
  },
  {
    "path": "samples/modules/compose/compose_dashboard/build.gradle.kts",
    "chars": 755,
    "preview": "import zlc.season.buildlogic.base.androidLibrary\nimport zlc.season.buildlogic.base.enableCompose\n\n@Suppress(\"DSL_SCOPE_V"
  },
  {
    "path": "samples/modules/compose/compose_dashboard/consumer-rules.pro",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "samples/modules/compose/compose_dashboard/proguard-rules.pro",
    "chars": 754,
    "preview": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguar"
  },
  {
    "path": "samples/modules/compose/compose_dashboard/src/main/AndroidManifest.xml",
    "chars": 62,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest>\n\n</manifest>"
  },
  {
    "path": "samples/modules/compose/compose_dashboard/src/main/java/zlc/season/compose/dashboard/DashboardScreen.kt",
    "chars": 1283,
    "preview": "package zlc.season.compose.dashboard\n\nimport android.os.Bundle\nimport androidx.compose.foundation.layout.Box\nimport andr"
  },
  {
    "path": "samples/modules/compose/compose_dashboard/src/main/java/zlc/season/compose/dashboard/DashboardViewModel.kt",
    "chars": 534,
    "preview": "package zlc.season.compose.dashboard\n\nimport androidx.lifecycle.LiveData\nimport androidx.lifecycle.MutableLiveData\nimpor"
  },
  {
    "path": "samples/modules/compose/compose_home/.gitignore",
    "chars": 6,
    "preview": "/build"
  },
  {
    "path": "samples/modules/compose/compose_home/build.gradle.kts",
    "chars": 750,
    "preview": "import zlc.season.buildlogic.base.androidLibrary\nimport zlc.season.buildlogic.base.enableCompose\n\n@Suppress(\"DSL_SCOPE_V"
  },
  {
    "path": "samples/modules/compose/compose_home/consumer-rules.pro",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "samples/modules/compose/compose_home/proguard-rules.pro",
    "chars": 754,
    "preview": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguar"
  },
  {
    "path": "samples/modules/compose/compose_home/src/main/AndroidManifest.xml",
    "chars": 101,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest>\n\n    <application>\n\n    </application>\n\n</manifest>"
  },
  {
    "path": "samples/modules/compose/compose_home/src/main/java/zlc/season/compose/home/HomeScreen.kt",
    "chars": 745,
    "preview": "package zlc.season.compose.home\n\nimport android.os.Bundle\nimport androidx.compose.foundation.layout.Box\nimport androidx."
  },
  {
    "path": "samples/modules/compose/compose_notifications/.gitignore",
    "chars": 6,
    "preview": "/build"
  },
  {
    "path": "samples/modules/compose/compose_notifications/build.gradle.kts",
    "chars": 759,
    "preview": "import zlc.season.buildlogic.base.androidLibrary\nimport zlc.season.buildlogic.base.enableCompose\n\n@Suppress(\"DSL_SCOPE_V"
  },
  {
    "path": "samples/modules/compose/compose_notifications/consumer-rules.pro",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "samples/modules/compose/compose_notifications/proguard-rules.pro",
    "chars": 754,
    "preview": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguar"
  },
  {
    "path": "samples/modules/compose/compose_notifications/src/main/AndroidManifest.xml",
    "chars": 62,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest>\n\n</manifest>"
  },
  {
    "path": "samples/modules/compose/compose_notifications/src/main/java/zlc/season/compose/notifications/NotificationsScreen.kt",
    "chars": 1193,
    "preview": "package zlc.season.compose.notifications\n\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundati"
  },
  {
    "path": "samples/modules/compose/compose_notifications/src/main/java/zlc/season/compose/notifications/NotificationsViewModel.kt",
    "chars": 468,
    "preview": "package zlc.season.compose.notifications\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\ni"
  },
  {
    "path": "samples/modules/feature1/.gitignore",
    "chars": 6,
    "preview": "/build"
  },
  {
    "path": "samples/modules/feature1/build.gradle.kts",
    "chars": 623,
    "preview": "import zlc.season.buildlogic.base.androidLibrary\n\n@Suppress(\"DSL_SCOPE_VIOLATION\")\nplugins {\n    alias(libs.plugins.libr"
  },
  {
    "path": "samples/modules/feature1/consumer-rules.pro",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "samples/modules/feature1/proguard-rules.pro",
    "chars": 754,
    "preview": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguar"
  },
  {
    "path": "samples/modules/feature1/src/main/AndroidManifest.xml",
    "chars": 363,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <appli"
  },
  {
    "path": "samples/modules/feature1/src/main/java/zlc/season/feature1/AFragment.kt",
    "chars": 3534,
    "preview": "package zlc.season.feature1\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.View\nimport"
  },
  {
    "path": "samples/modules/feature1/src/main/java/zlc/season/feature1/BFragment.kt",
    "chars": 3535,
    "preview": "package zlc.season.feature1\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.View\nimport"
  },
  {
    "path": "samples/modules/feature1/src/main/java/zlc/season/feature1/CFragment.kt",
    "chars": 3539,
    "preview": "package zlc.season.feature1\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.View\nimport"
  },
  {
    "path": "samples/modules/feature1/src/main/java/zlc/season/feature1/TestAction.kt",
    "chars": 467,
    "preview": "package zlc.season.feature1\n\nimport android.content.Context\nimport android.os.Bundle\nimport android.widget.Toast\nimport "
  },
  {
    "path": "samples/modules/feature1/src/main/java/zlc/season/feature1/TestActivity.kt",
    "chars": 1183,
    "preview": "package zlc.season.feature1\n\nimport android.annotation.SuppressLint\nimport android.os.Bundle\nimport androidx.appcompat.a"
  },
  {
    "path": "samples/modules/feature1/src/main/java/zlc/season/feature1/TestBottomSheetDialogFragment.kt",
    "chars": 1173,
    "preview": "package zlc.season.feature1\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.View\nimport"
  },
  {
    "path": "samples/modules/feature1/src/main/java/zlc/season/feature1/TestDialogFragment.kt",
    "chars": 1250,
    "preview": "package zlc.season.feature1\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.View\nimport"
  },
  {
    "path": "samples/modules/feature1/src/main/java/zlc/season/feature1/TestFragment.kt",
    "chars": 1153,
    "preview": "package zlc.season.feature1\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.View\nimport"
  },
  {
    "path": "samples/modules/feature1/src/main/java/zlc/season/feature1/TestResultActivity.kt",
    "chars": 1522,
    "preview": "package zlc.season.feature1\n\nimport android.annotation.SuppressLint\nimport android.app.Activity\nimport android.content.I"
  },
  {
    "path": "samples/modules/feature1/src/main/res/layout/activity_test.xml",
    "chars": 824,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns"
  },
  {
    "path": "samples/modules/feature1/src/main/res/layout/activity_test_result.xml",
    "chars": 791,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns"
  },
  {
    "path": "samples/modules/feature1/src/main/res/layout/dialog_test.xml",
    "chars": 1253,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    andro"
  },
  {
    "path": "samples/modules/feature1/src/main/res/layout/fragment_common.xml",
    "chars": 2872,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    andr"
  },
  {
    "path": "samples/modules/feature1/src/main/res/layout/fragment_test.xml",
    "chars": 850,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    andro"
  },
  {
    "path": "samples/modules/feature1/src/main/res/values/colors.xml",
    "chars": 321,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"blue\">#2196F3</color>\n    <color name=\"green\">#00BCD"
  },
  {
    "path": "samples/modules/feature1/src/main/res/values/strings.xml",
    "chars": 23,
    "preview": "<resources></resources>"
  },
  {
    "path": "samples/modules/feature2/.gitignore",
    "chars": 6,
    "preview": "/build"
  },
  {
    "path": "samples/modules/feature2/build.gradle.kts",
    "chars": 747,
    "preview": "import zlc.season.buildlogic.base.androidLibrary\nimport zlc.season.buildlogic.base.enableCompose\n\n@Suppress(\"DSL_SCOPE_V"
  },
  {
    "path": "samples/modules/feature2/consumer-rules.pro",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "samples/modules/feature2/proguard-rules.pro",
    "chars": 754,
    "preview": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguar"
  },
  {
    "path": "samples/modules/feature2/src/main/AndroidManifest.xml",
    "chars": 62,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest>\n\n</manifest>"
  },
  {
    "path": "samples/modules/feature2/src/main/java/zlc/season/bar/Colors.kt",
    "chars": 188,
    "preview": "package zlc.season.bar\n\nimport androidx.compose.ui.graphics.Color\n\nobject Colors {\n    val YELLOW = Color(0xFFFF5722)\n  "
  },
  {
    "path": "samples/modules/feature2/src/main/java/zlc/season/bar/ComposeScreenA.kt",
    "chars": 4078,
    "preview": "package zlc.season.bar\n\nimport android.os.Bundle\nimport androidx.compose.foundation.layout.*\nimport androidx.compose.mat"
  },
  {
    "path": "samples/modules/feature2/src/main/java/zlc/season/bar/ComposeScreenB.kt",
    "chars": 4261,
    "preview": "package zlc.season.bar\n\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.Column\ni"
  },
  {
    "path": "samples/modules/feature2/src/main/java/zlc/season/bar/ComposeScreenC.kt",
    "chars": 4260,
    "preview": "package zlc.season.bar\n\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.Column\ni"
  },
  {
    "path": "samples/modules/feature2/src/main/java/zlc/season/bar/ScreenViewModels.kt",
    "chars": 1527,
    "preview": "package zlc.season.bar\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport kotlinx.coro"
  },
  {
    "path": "samples/modules/normal/dashboard/.gitignore",
    "chars": 6,
    "preview": "/build"
  },
  {
    "path": "samples/modules/normal/dashboard/build.gradle.kts",
    "chars": 623,
    "preview": "import zlc.season.buildlogic.base.androidLibrary\n\n@Suppress(\"DSL_SCOPE_VIOLATION\")\nplugins {\n    alias(libs.plugins.libr"
  },
  {
    "path": "samples/modules/normal/dashboard/consumer-rules.pro",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "samples/modules/normal/dashboard/proguard-rules.pro",
    "chars": 754,
    "preview": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguar"
  },
  {
    "path": "samples/modules/normal/dashboard/src/main/AndroidManifest.xml",
    "chars": 121,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n</manifest"
  },
  {
    "path": "samples/modules/normal/dashboard/src/main/java/zlc/season/dashboard/DashboardFragment.kt",
    "chars": 1284,
    "preview": "package zlc.season.dashboard\n\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.View\nimpor"
  }
]

// ... and 21 more files (download for full content)

About this extraction

This page contains the full source code of the ssseasonnn/Butterfly GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 221 files (299.7 KB), approximately 75.6k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!