```
├── .github/
├── CODEOWNERS
├── .gitignore
├── .ignoreRevsFile
├── 1.3.10_test_publish.sh
├── 1.4.20_test_publish.sh
├── 1.5.31_test_publish.sh
├── 1.6.21_test_publish.sh
├── 1.8.21_test_publish.sh
├── 1.9.22_test_publish.sh
├── 2.0.21_test_publish.sh
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── OpenKuiklyIOSRender.podspec
├── README-zh_CN.md
├── README.md
├── add_internal.sh
├── androidApp/
├── build.gradle.kts
├── src/
├── main/
├── AndroidManifest.xml
├── assets/
├── configColor.ini
├── fonts/
├── Satisfy-Regular.ttf
├── java/
├── com/
├── tencent/
├── kuikly/
├── android/
├── demo/
├── AppMainActivity.kt
├── ContextCodeHandler.kt
├── KRApplication.kt
├── KuiklyPageView.kt
├── KuiklyRenderActivity.kt
├── KuiklyRenderView.kt
├── MainActivity.kt
├── NativeMixKuiklyViewDemoActivity.kt
├── SkinIniFile.kt
├── adapter/
├── KRAPNGViewAdapter.kt
├── KRColorParserAdapter.kt
├── KRFontAdapter.kt
├── KRImageAdapter.kt
├── KRLogAdapter.kt
├── KRRouterAdapter.kt
├── KRTextPostProcessorAdapter.kt
├── KRThreadAdapter.kt
├── KRUncaughtExceptionHandlerAdapter.kt
├── PAGViewAdapter.kt
├── VideoViewAdapter.kt
├── module/
├── KRBridgeModule.kt
├── KRNotifyModule.kt
├── KRShareModule.kt
├── tdf/
├── KRTDFTestModule.kt
├── res/
├── anim/
├── slide_from_bottom.xml
├── slide_out_to_top.xml
├── drawable/
├── kuikly_icon.png
├── launch_background.xml
├── shap_btn_bg.xml
├── shape_ed_bg.xml
├── layout/
├── activity_app_main.xml
├── activity_hr.xml
├── activity_hr_native_item.xml
├── activity_hr_nd.xml
├── activity_main.xml
├── activity_native_mix_kuikly_view.xml
├── activity_nv_2.xml
├── values/
├── colors.xml
├── dimens.xml
├── strings.xml
├── styles.xml
├── xml/
├── network_security_config.xml
├── build.1.3.10.gradle.kts
├── build.1.4.20.gradle.kts
├── build.1.5.31.gradle.kts
├── build.1.6.21.gradle.kts
├── build.1.8.21.gradle.kts
├── build.1.9.22.gradle.kts
├── build.2.0.21.gradle.kts
├── build.gradle.kts
├── buildSrc/
├── build.gradle.kts
├── src/
├── main/
├── java/
├── KuiklyKotlinBuildVar.kt
├── core-annotations/
├── build.1.3.10.gradle
├── build.1.4.20.gradle
├── build.1.5.31.gradle.kts
├── build.1.6.21.gradle.kts
├── build.1.8.21.gradle.kts
├── build.1.9.22.gradle.kts
├── build.2.0.21.gradle.kts
├── build.gradle.kts
├── core-annotations.podspec
├── core_annotations.podspec
├── src/
├── androidMain/
├── AndroidManifest.xml
├── commonMain/
├── kotlin/
├── com/
├── tencent/
├── kuikly/
├── core/
├── annotations/
├── Page.kt
├── core-kapt/
├── .gitignore
├── build.gradle
├── src/
├── main/
├── java/
├── com/
├── tencent/
├── kuikly/
├── core/
├── kapt/
├── AndroidMultiEntryBuilder.kt
├── AndroidTargetEntryBuilder.kt
├── KuiklyCoreAbsEntryBuilder.kt
├── KuiklyCoreProcessor.kt
├── PageInfo.kt
├── resources/
├── META-INF/
├── services/
├── javax.annotation.processing.Processor
├── core-ksp/
├── build.1.5.31.gradle.kts
├── build.1.6.21.gradle.kts
├── build.1.8.21.gradle.kts
├── build.1.9.22.gradle.kts
├── build.2.0.21.gradle.kts
├── build.gradle.kts
├── core-ksp.podspec
├── src/
├── main/
├── kotlin/
├── com/
├── tencent/
├── kuikly/
├── core/
├── ksp/
├── Extensions.kt
├── KuiklyCoreProcessorProvider.kt
├── impl/
├── AndroidTargetEntryBuilder.kt
├── IOSTargetEntryBuilder.kt
├── KuiklyCoreAbsEntryBuilder.kt
├── PageInfo.kt
├── submodule/
├── AndroidMultiEntryBuilder.kt
├── IOSMultiTargetEntryBuilder.kt
```
## /.github/CODEOWNERS
```github/CODEOWNERS path="/.github/CODEOWNERS"
#####################################################
#
# List of approvers for KuiklyUI
#
#####################################################
#
# Learn about CODEOWNERS file format:
# https://help.github.com/en/articles/about-code-owners
#
# universal files
* @Cyunong @iPel @hidawei @elixxli
/README.md @hidawei
/buildSrc/ @elixxli
/androidApp/ @hidawei @elixxli
/core/ @iPel @hidawei
/core-annotations/ @elixxli
/core-kapt/ @elixxli
/core-ksp/ @elixxli
/core-render-android/ @iPel @hidawei
/core-render-ios/ @Cyunong
/demo/ @hidawei
/gradle/ @elixxli
/img/ @hidawei
/iosApp/ @Cyunong
/*.sh @elixxli @iPel
/*.gradle.kts @elixxli @iPel
/CODE_OF_CONDUCT.md @hidawei
/CONTRIBUTING.md @hidawei
/LICENSE @hidawei
/README* @hidawei
/OpenKuiklyIOSRender.podspec @Cyunong
/gradlew* @elixxli @iPel
/.gitignore @iPel @hidawei
```
## /.gitignore
```gitignore path="/.gitignore"
*.iml
.gradle
/local.properties
.idea
.DS_Store
/build
*/build
/captures
.externalNativeBuild
.cxx
local.properties
*.so
*.js
node_modules
Pods
Pods/*
Kotlin-js-store
## Various settings
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
**/xcuserdata/**
**/*.xcuserdatad/**
**/xcshareddata/
**/IDEWorkspaceChecks.plist
## Other
*.moved-aside
*.xccheckout
*.xcscmblueprint
*.xcbkptlist
*.pyc
proto/**/*.kt
/iosApp/iosApp.xcodeproj/xcshareddata/xcschemes/iosApp.xcscheme
```
## /.ignoreRevsFile
```ignoreRevsFile path="/.ignoreRevsFile"
# List of revisions that can be ignored with git-blame(1).
#
# See `blame.ignoreRevsFile` in git-config(1) to enable it by default, or
# use it with `--ignore-revs-file` manually with git-blame.
#
# To "install" it:
#
# git config --local blame.ignoreRevsFile .ignoreRevsFile
# format
4b33a6cacccd738eeb138dfc4312b698c1b1ace6
```
## /1.3.10_test_publish.sh
```sh path="/1.3.10_test_publish.sh"
# 1.记录原始url
ORIGIN_DISTRIBUTION_URL=$(grep "distributionUrl" gradle/wrapper/gradle-wrapper.properties | cut -d "=" -f 2)
echo "origin gradle url: $ORIGIN_DISTRIBUTION_URL"
# 2.切换gradle版本
NEW_DISTRIBUTION_URL="https\:\/\/services.gradle.org\/distributions\/gradle-5.4.1-bin.zip"
sed -i.bak "s/distributionUrl=.*$/distributionUrl=$NEW_DISTRIBUTION_URL/" gradle/wrapper/gradle-wrapper.properties
# 是否兼容 support
KUIKLY_ENABLE_ANDROID_SUPPORT_COMPATIBLE=0
current_dir=$PWD
core_render_android_dir=$current_dir/core-render-android/src/main/java
core_convert_util_file=$current_dir/core/src/commonMain/kotlin/com/tencent/kuikly/core/utils/ConvertUtil.kt
# 关闭androidx开关、将androidx包名替换成support包包名
if [ "$KUIKLY_ENABLE_ANDROID_SUPPORT_COMPATIBLE" -eq 1 ]; then
# 修改 gradle.properties,关闭 androidx
sed -i.bak -e "s/android.useAndroidX=true/android.useAndroidX=false/g" -e "s/android.enableJetifier=true/android.enableJetifier=false/g" gradle.properties
# 替换所有 androidx
echo $core_render_android_dir
for file in $(find $core_render_android_dir -type f -name "*.kt")
do
sed -i -depth -e 's/import androidx.recyclerview\./import android.support.v7\./g' -e 's/import androidx.dynamicanimation\./import android.support\./g' -e 's/import androidx\./import android.support\./g' "$file"
done
fi
# ConvertUtil的encodeToByteArray替换成toByteArray
echo $core_convert_util_file
sed -i.bak 's/md5L16\.encodeToByteArray()/md5L16\.toByteArray(Charsets.UTF_8)/g' $core_convert_util_file
# 构建
KUIKLY_AGP_VERSION="3.5.4" KUIKLY_KOTLIN_VERSION="1.3.10" ./gradlew -c settings.1.3.10.gradle.kts :core-annotations:publishToMavenLocal --stacktrace
KUIKLY_AGP_VERSION="3.5.4" KUIKLY_KOTLIN_VERSION="1.3.10" ./gradlew -c settings.1.3.10.gradle.kts :core-kapt:publishToMavenLocal --stacktrace
KUIKLY_AGP_VERSION="3.5.4" KUIKLY_KOTLIN_VERSION="1.3.10" ./gradlew -c settings.1.3.10.gradle.kts :core:publishToMavenLocal --stacktrace
KUIKLY_AGP_VERSION="3.5.4" KUIKLY_KOTLIN_VERSION="1.3.10" ./gradlew -c settings.1.3.10.gradle.kts :core-render-android:publishToMavenLocal --stacktrace
KUIKLY_AGP_VERSION="3.5.4" KUIKLY_KOTLIN_VERSION="1.3.10" KUIKLY_RENDER_SUFFIX="androidx" ./gradlew -c settings.1.3.10.gradle.kts :core-render-android:publishToMavenLocal --stacktrace
# 还原androidx
if [ "$KUIKLY_ENABLE_ANDROID_SUPPORT_COMPATIBLE" -eq 1 ]; then
# 修改 gradle.properties
mv gradle.properties.bak gradle.properties
# 恢复 androidx
for file in $(find $core_render_android_dir -type f -name "*.kt")
do
mv "$file-depth" "$file"
done
fi
# 还原其他文件
mv gradle/wrapper/gradle-wrapper.properties.bak gradle/wrapper/gradle-wrapper.properties
mv "$core_convert_util_file.bak" $core_convert_util_file
```
## /1.4.20_test_publish.sh
```sh path="/1.4.20_test_publish.sh"
KUIKLY_AGP_VERSION="4.2.1" KUIKLY_KOTLIN_VERSION="1.4.20" ./gradlew -c settings.1.4.20.gradle.kts :core-annotations:publishToMavenLocal --stacktrace
KUIKLY_AGP_VERSION="4.2.1" KUIKLY_KOTLIN_VERSION="1.4.20" ./gradlew -c settings.1.4.20.gradle.kts :core-kapt:publishToMavenLocal --stacktrace
KUIKLY_AGP_VERSION="4.2.1" KUIKLY_KOTLIN_VERSION="1.4.20" ./gradlew -c settings.1.4.20.gradle.kts :core:publishToMavenLocal --stacktrace
KUIKLY_AGP_VERSION="4.2.1" KUIKLY_KOTLIN_VERSION="1.4.20" ./gradlew -c settings.1.4.20.gradle.kts :core-render-android:publishToMavenLocal --stacktrace
```
## /1.5.31_test_publish.sh
```sh path="/1.5.31_test_publish.sh"
java --version
KUIKLY_KOTLIN_VERSION="1.5.31" ./gradlew -c settings.1.5.31.gradle.kts :core:publishToMavenLocal --stacktrace
KUIKLY_KOTLIN_VERSION="1.5.31" ./gradlew -c settings.1.5.31.gradle.kts :core-annotations:publishToMavenLocal --stacktrace
KUIKLY_KOTLIN_VERSION="1.5.31" ./gradlew -c settings.1.5.31.gradle.kts :core-ksp:publishToMavenLocal --stacktrace
KUIKLY_KOTLIN_VERSION="1.5.31" ./gradlew -c settings.1.5.31.gradle.kts :core-render-android:publishToMavenLocal --stacktrace
```
## /1.6.21_test_publish.sh
```sh path="/1.6.21_test_publish.sh"
KUIKLY_KOTLIN_VERSION="1.6.21" ./gradlew -c settings.1.6.21.gradle.kts :core:publishToMavenLocal --stacktrace
KUIKLY_KOTLIN_VERSION="1.6.21" ./gradlew -c settings.1.6.21.gradle.kts :core-annotations:publishToMavenLocal --stacktrace
KUIKLY_KOTLIN_VERSION="1.6.21" ./gradlew -c settings.1.6.21.gradle.kts :core-ksp:publishToMavenLocal --stacktrace
KUIKLY_KOTLIN_VERSION="1.6.21" ./gradlew -c settings.1.6.21.gradle.kts :core-render-android:publishToMavenLocal --stacktrace
```
## /1.8.21_test_publish.sh
```sh path="/1.8.21_test_publish.sh"
KUIKLY_KOTLIN_VERSION="1.8.21" ./gradlew -c settings.1.8.21.gradle.kts :core:publishToMavenLocal --stacktrace
KUIKLY_KOTLIN_VERSION="1.8.21" ./gradlew -c settings.1.8.21.gradle.kts :core-render-android:publishToMavenLocal --stacktrace
KUIKLY_KOTLIN_VERSION="1.8.21" ./gradlew -c settings.1.8.21.gradle.kts :core-annotations:publishToMavenLocal --stacktrace
KUIKLY_KOTLIN_VERSION="1.8.21" ./gradlew -c settings.1.8.21.gradle.kts :core-ksp:publishToMavenLocal --stacktrace
```
## /1.9.22_test_publish.sh
```sh path="/1.9.22_test_publish.sh"
# 1.记录原始url
ORIGIN_DISTRIBUTION_URL=$(grep "distributionUrl" gradle/wrapper/gradle-wrapper.properties | cut -d "=" -f 2)
echo "origin gradle url: $ORIGIN_DISTRIBUTION_URL"
# 2.切换gradle版本
NEW_DISTRIBUTION_URL="https\:\/\/services.gradle.org\/distributions\/gradle-7.5.1-bin.zip"
sed -i.bak "s/distributionUrl=.*$/distributionUrl=$NEW_DISTRIBUTION_URL/" gradle/wrapper/gradle-wrapper.properties
# 3. 修改 gradle.properties,关闭 androidx
sed -i.bak -e "s/kotlin.mpp.enableGranularSourceSetsMetadata=true//g" -e "s/kotlin.native.enableDependencyPropagation=false//g" -e '$ a\
#\
#kotlin.mpp.applyDefaultHierarchyTemplate=false' gradle.properties
# 4. 解决语法问题
ios_main_dir="core/src/iosMain/kotlin/com/tencent/kuikly"
ios_platform_impl="$ios_main_dir/core/module/PlatformImp.kt"
sed -i.bak '1i\
@file:OptIn(kotlinx.cinterop.ExperimentalForeignApi::class)
' "$ios_platform_impl"
ios_exception_tracker="$ios_main_dir/core/exception/ExceptionTracker.kt"
sed -i.bak -e '1i\
@file:OptIn(kotlin.experimental.ExperimentalNativeApi::class)
' -e "s/import kotlin.native.concurrent.AtomicReference/import kotlin.concurrent.AtomicReference/g" "$ios_exception_tracker"
# 5. 开始发布
KUIKLY_AGP_VERSION="7.4.2" KUIKLY_KOTLIN_VERSION="1.9.22" ./gradlew -c settings.1.9.22.gradle.kts :core-annotations:publishToMavenLocal --stacktrace
KUIKLY_AGP_VERSION="7.4.2" KUIKLY_KOTLIN_VERSION="1.9.22" ./gradlew -c settings.1.9.22.gradle.kts :core-ksp:publishToMavenLocal --stacktrace
KUIKLY_AGP_VERSION="7.4.2" KUIKLY_KOTLIN_VERSION="1.9.22" ./gradlew -c settings.1.9.22.gradle.kts :core:publishToMavenLocal --stacktrace
KUIKLY_AGP_VERSION="7.4.2" KUIKLY_KOTLIN_VERSION="1.9.22" ./gradlew -c settings.1.9.22.gradle.kts :core-render-android:publishToMavenLocal --stacktrace
# 6. 还原其他文件
mv gradle/wrapper/gradle-wrapper.properties.bak gradle/wrapper/gradle-wrapper.properties
mv gradle.properties.bak gradle.properties
mv "$ios_platform_impl.bak" "$ios_platform_impl"
mv "$ios_exception_tracker.bak" "$ios_exception_tracker"
```
## /2.0.21_test_publish.sh
```sh path="/2.0.21_test_publish.sh"
# 1.记录原始url
ORIGIN_DISTRIBUTION_URL=$(grep "distributionUrl" gradle/wrapper/gradle-wrapper.properties | cut -d "=" -f 2)
echo "origin gradle url: $ORIGIN_DISTRIBUTION_URL"
# 2.切换gradle版本
NEW_DISTRIBUTION_URL="https\:\/\/services.gradle.org\/distributions\/gradle-7.5.1-bin.zip"
sed -i.bak "s/distributionUrl=.*$/distributionUrl=$NEW_DISTRIBUTION_URL/" gradle/wrapper/gradle-wrapper.properties
# 3. 修改 gradle.properties,关闭 androidx
sed -i.bak -e "s/kotlin.mpp.enableGranularSourceSetsMetadata=true//g" -e "s/kotlin.native.enableDependencyPropagation=false//g" -e '$ a\
#\
#kotlin.mpp.applyDefaultHierarchyTemplate=false' gradle.properties
# 4. 解决语法问题
ios_main_dir="core/src/iosMain/kotlin/com/tencent/kuikly"
ios_platform_impl="$ios_main_dir/core/module/PlatformImp.kt"
sed -i.bak '1i\
@file:OptIn(kotlinx.cinterop.ExperimentalForeignApi::class)
' "$ios_platform_impl"
ios_exception_tracker="$ios_main_dir/core/exception/ExceptionTracker.kt"
sed -i.bak -e '1i\
@file:OptIn(kotlin.experimental.ExperimentalNativeApi::class)
' -e "s/import kotlin.native.concurrent.AtomicReference/import kotlin.concurrent.AtomicReference/g" "$ios_exception_tracker"
KUIKLY_AGP_VERSION="7.4.2" KUIKLY_KOTLIN_VERSION="2.0.21" ./gradlew -c settings.2.0.21.gradle.kts :core-annotations:publishToMavenLocal --stacktrace
KUIKLY_AGP_VERSION="7.4.2" KUIKLY_KOTLIN_VERSION="2.0.21" ./gradlew -c settings.2.0.21.gradle.kts :core:publishToMavenLocal --stacktrace
KUIKLY_AGP_VERSION="7.4.2" KUIKLY_KOTLIN_VERSION="2.0.21" ./gradlew -c settings.2.0.21.gradle.kts :core-ksp:publishToMavenLocal --stacktrace
KUIKLY_AGP_VERSION="7.4.2" KUIKLY_KOTLIN_VERSION="2.0.21" ./gradlew -c settings.2.0.21.gradle.kts :core-render-android:publishToMavenLocal --stacktrace
# 6. 还原其他文件
mv gradle/wrapper/gradle-wrapper.properties.bak gradle/wrapper/gradle-wrapper.properties
mv gradle.properties.bak gradle.properties
mv "$ios_platform_impl.bak" "$ios_platform_impl"
mv "$ios_exception_tracker.bak" "$ios_exception_tracker"
```
## /CODE_OF_CONDUCT.md
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, caste, color, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
[INSERT CONTACT METHOD].
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
[https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0].
Community Impact Guidelines were inspired by
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
For answers to common questions about this code of conduct, see the FAQ at
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available
at [https://www.contributor-covenant.org/translations][translations].
[homepage]: https://www.contributor-covenant.org
[v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html
[Mozilla CoC]: https://github.com/mozilla/diversity
[FAQ]: https://www.contributor-covenant.org/faq
[translations]: https://www.contributor-covenant.org/translations
## /CONTRIBUTING.md
# Contribution Guide
We highly appreciate your contributions to Kuikly, whether it's raising an issue, fixing a bug, or submitting a new feature. Before submitting your contribution, please take a moment to read the following guidelines:
## Issue Reporting Guidelines
The project collects [issues]() and requirements through Issues.
- Before raising an issue, please check if someone else has already proposed a similar feature or problem.
- When raising an issue, please describe the problem in as much detail as possible, including steps to reproduce, code snippets, screenshots, etc.
### Bug Report
#### Describe the bug
A clear and concise description of what the bug is.
#### To Reproduce
Steps to reproduce the behavior:
- Go to '...'
- Click on '....'
- Scroll down to '....'
- See error
#### Expected behavior
A clear and concise description of what you expected to happen.
#### Screenshots
If applicable, add screenshots to help explain your problem.
#### Smartphone (please complete the following information):
- Device: [e.g. iPhone6]
- OS and version: [e.g. iOS 13.3.1]
- Kuikly SDK version: [e.g. 2.0.0]
- Additional context Add any other context about the problem here.
### Feature Request
#### Is your feature request related to a problem? Please describe.
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
#### Describe the solution you'd like
A clear and concise description of what you want to happen.
#### Describe alternatives you've considered
A clear and concise description of any alternative solutions or features you've considered.
#### Additional context
Add any other context or screenshots about the feature request here.
## Pull Request Guidelines
### Branch Management
This project mainly includes the following three types of branches:
* main: A stable development branch that only accepts fully tested features or bug fixes. Releases are made directly from the main branch by tagging.
* feature: Named according to the feature/** rule, it is a feature development branch.
* bugfix: Named according to the bugfix/** rule, it is a daily bugfix branch.
## Commit Messgage
Please follow the [Angular Convention](https://docs.google.com/document/d/1QrDFcIiPjSLDn3EL15IJygNPiHORgU1_OOAqWjiDU5Y/edit#heading=h.greljkmo14y0) for commit messages:
- feat:New feature
- fix:Bug fix
- docs:Documentation changes
- style: Formatting changes (does not affect code execution)
- refactor:Code refactoring (neither a new feature nor a bug fix)
- test:Adding tests
- chore:Changes to the build process or auxiliary tools
## Merge Reuqest
Please initiate a merge request via [Merge Request](merge_requests/new). Before submitting an MR, please ensure:
- Checkout a topic branch from the relevant branch, e.g. main, and merge back against that branch.
- The code has been thoroughly tested and validated.
- The code complies with the project's coding standards.
- If it involves API changes, please update the API documentation.
- Link relevant issues or requirements.
## Code Standards
This project adheres to the coding style guides provided by Google: [Google's Style Guides](https://google.github.io/styleguide/)
## Version Numbering Convention
Version releases follow a three-part naming rule: `major.minor.fix`, e.g., 1.0.0
- major: Major version number, core baseline version.
- minor: Minor version number, used for releasing small feature modules.
- fix: Stage version number, used for bug fixes or partial code module optimizations.
For releasing alpha, beta, rc, etc., versions, append `-[alpha|beta|rc]` to the version number, e.g., 1.0.0-beta.
> Special Note: Kuikly will use a higher version of Kotlin during the development process to leverage official capabilities, while the Kotlin version in business projects may be lower. Therefore, it is necessary to support multiple versions for business selection. Thus, the version number of the core library needs to include the Kotlin version as a distinction, e.g., 1.0.0-1.7.0, where 1.7.0 is the minimum Kotlin version required by the business project.
## /LICENSE
``` path="/LICENSE"
Tencent is pleased to support the open source community by making KuiklyUI available.
Copyright (C) 2025 THL A29 Limited, a Tencent company. All rights reserved. The below software in this distribution may have been modified by THL A29 Limited ("Tencent Modifications"). All Tencent Modifications are Copyright (C) THL A29 Limited.
KuiklyUI is licensed under the License Terms of KuiklyUI except for the third-party components listed below, which is licensed under different terms. KuiklyUI does not impose any additional limitations beyond what is outlined in the respective licenses of these third-party components. Users must comply with all terms and conditions of original licenses of these third-party components and must ensure that the usage of the third party components adheres to all relevant laws and regulations.
Terms of the License Terms of KuiklyUI:
--------------------------------------------------------------------
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
- Without Tencent's prior written consent, you may not modify, extend, or create derivative works of the Software for the purpose of adding or enabling dynamic code loading/ live updating capability.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Other dependencies and licenses:
Open Source Software Licensed under the MIT License:
The below software in this distribution may have been modified by THL A29 Limited ("Tencent Modifications"). All Tencent Modifications are Copyright (C) 2025 THL A29 Limited.
--------------------------------------------------------------------
1. SDWebImage
Copyright (c) 2009-2020 Olivier Poitrey rs@dailymotion.com
2. WMPlayer
Copyright (c) 2016 文明
3. Masonry
Copyright (c) 2011-2012 Masonry Team - https://github.com/Masonry
4. FDFullscreenPopGesture
Copyright (c) 2015
5. SDWebImage/Core
Copyright (c) 2009-2017 Olivier Poitrey rs@dailymotion.com
6. yoga
Copyright (c) Facebook, Inc. and its affiliates.
7. com.github.bumptech.glide:gifdecoder
Copyright (c) 2013 Xcellent Creations, Inc.
Terms of the MIT License:
--------------------------------------------------------------------
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Open Source Software Licensed under the Apache License Version 2.0:
--------------------------------------------------------------------
1. com.squareup.okhttp3:okhttp
Copyright (C) 2017 Square, Inc.
2. com.google.android.material:material
Copyright (C) 2015 The Android Open Source Project
3. androidx.appcompat:appcompat
Copyright (C) 2012 The Android Open Source Project
4. androidx.core:core-ktx
Copyright (C) 2017 The Android Open Source Project
5. com.google.android.exoplayer
Copyright (c) The Android Open Source Project
6.com.github.penfeizhou.android.animation:apng
Copyright [2019] [Zhou Pengfei]
7. org.jetbrains.kotlin:kotlin-test
Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
8. o.mockk:mockk-common
Copyright [2017] [github.com/mockk]
9. org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable
Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
10. io.mockk:mockk
Copyright [2017] [github.com/mockk]
11. org.jetbrains.kotlin:kotlin-compiler-embeddable
Copyright 2010-2015 JetBrains s.r.o.
12. com.squareup:kotlinpoet
Copyright 2017 Square, Inc.
13. com.google.devtools.ksp:symbol-processing-api
Copyright 2000-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
14. androidx.recyclerview:recyclerview
Copyright 2018 The Android Open Source Project
15. androidx.test.espresso:espresso-core
Copyright (C)The Android Open Source Project
16. org.jetbrains.kotlin:kotlin-stdlib-jdk8
Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
17. org.jetbrains.kotlin:kotlin-klib-commonizer-embeddable
Copyright (c) kotlin-klib-commonizer-embeddable original author and authors
18. net.java.dev.jna:jna
Copyright (c) 2007 Timothy Wall, All Rights Reserved
19. com.github.bumptech.glide:disklrucache
Copyright 2012 Jake Wharton
Copyright 2011 The Android Open Source Project
20. org.jetbrains.kotlin:kotlin-stdlib
Copyright 2010-2015 JetBrains s.r.o.
21. org.jetbrains.kotlin:kotlin-stdlib-common
Copyright 2010-2016 JetBrains s.r.o.
22. org.jetbrains:annotations
Copyright 2000-2013 JetBrains s.r.o.
23. org.jetbrains.kotlin:kotlin-stdlib-jdk7
Copyright 2010-2017 JetBrains s.r.o.
24. com.squareup.okio:okio
Copyright (C) 2014 Square, Inc.
25. androidx.annotation:annotation
Copyright (C) 2014 The Android Open Source Project
26. androidx.core:core
Copyright (C) 2014 The Android Open Source Project
27. androidx.annotation:annotation-experimental
Copyright (c) The Android Open Source Project
28. androidx.lifecycle:lifecycle-runtime
Copyright (C) 2017 The Android Open Source Project
29. androidx.arch.core:core-runtime
Copyright (c) The Android Open Source Project
30. androidx.arch.core:core-common
Copyright (c) The Android Open Source Project
31. androidx.lifecycle:lifecycle-common
Copyright (C) 2017 The Android Open Source Project
32. ndroidx.versionedparcelable:versionedparcelable
Copyright 2018 The Android Open Source Project
33. androidx.collection:collection
Copyright 2018 The Android Open Source Project
34. androidx.concurrent:concurrent-futures
Copyright (c) The Android Open Source Project
35. com.google.guava:listenablefuture
Copyright (C) 2007 The Guava Authors
36. androidx.cursoradapter:cursoradapter
Copyright (c) The Android Open Source Project
37. androidx.activity:activity
Copyright 2019 The Android Open Source Project
38. androidx.lifecycle:lifecycle-viewmodel
Copyright (C) 2017 The Android Open Source Project
39. androidx.savedstate:savedstate
Copyright 2019 The Android Open Source Project
40. androidx.lifecycle:lifecycle-viewmodel-savedstate
Copyright 2018 The Android Open Source Project
41. ndroidx.lifecycle:lifecycle-livedata-core
Copyright (C) 2017 The Android Open Source Project
42. androidx.tracing:tracing
Copyright 2020 The Android Open Source Project
43. androidx.fragment:fragment
Copyright 2018 The Android Open Source Project
44. androidx.viewpager:viewpager
Copyright (c) The Android Open Source Project
45. androidx.customview:customview
Copyright 2018 The Android Open Source Project
46. androidx.loader:loader
Copyright (c) The Android Open Source Project
47. androidx.appcompat:appcompat-resources
Copyright (C) 2012 The Android Open Source Project
48. androidx.vectordrawable:vectordrawable
Copyright (c) The Android Open Source Project
49. androidx.vectordrawable:vectordrawable-animated
Copyright (c) The Android Open Source Project
50. androidx.interpolator:interpolator
Copyright (c) The Android Open Source Project
51. androidx.drawerlayout:drawerlayout
Copyright (c) The Android Open Source Project
52. androidx.cardview:cardview
Copyright (c) The Android Open Source Project
53. androidx.coordinatorlayout:coordinatorlayout
Copyright (c) The Android Open Source Project
54. androidx.constraintlayout:constraintlayout
Copyright (c) The Android Open Source Project
55. androidx.legacy:legacy-support-core-utils
Copyright (c) The Android Open Source Project
56. androidx.documentfile:documentfile
Copyright (c) The Android Open Source Project
57. androidx.localbroadcastmanager:localbroadcastmanager
Copyright (c) The Android Open Source Project
58. androidx.print:print
Copyright (c) The Android Open Source Project
59. androidx.transition:transition
Copyright (C) 2017 The Android Open Source Project
60. androidx.viewpager2:viewpager2
Copyright (c) The Android Open Source Project
61. com.squareup.picasso:picasso
Copyright 2013 Square, Inc.
62. androidx.exifinterface:exifinterface
Copyright 2018 The Android Open Source Project
63. com.google.android.exoplayer:exoplayer-common
Copyright (c) The Android Open Source Project
64. com.google.guava:failureaccess
Copyright (C) 2018 The Guava Authors
65. com.google.guava:listenablefuture
Copyright (c) com.google.guava:listenablefuture original author and authors
66. om.google.android.exoplayer:exoplayer-database
Copyright (c) The Android Open Source Project
67. com.google.android.exoplayer:exoplayer-datasource
Copyright (c) The Android Open Source Project
68. com.google.android.exoplayer:exoplayer-decoder
Copyright (c) The Android Open Source Project
69. com.google.android.exoplayer:exoplayer-extractor
Copyright (c) The Android Open Source Project
70. com.google.android.exoplayer:exoplayer-core
Copyright (c) The Android Open Source Project
71. com.google.android.exoplayer:exoplayer-dash
Copyright (c) The Android Open Source Project
72. com.google.android.exoplayer:exoplayer-hls
Copyright (c) The Android Open Source Project
73. com.google.android.exoplayer:exoplayer-rtsp
Copyright (c) The Android Open Source Project
74. com.google.android.exoplayer:exoplayer-smoothstreaming
Copyright (c) The Android Open Source Project
75. com.google.android.exoplayer:exoplayer-transformer
Copyright (c) The Android Open Source Project
76. com.google.android.exoplayer:exoplayer-ui
Copyright (c) The Android Open Source Project
77. androidx.media:media
Copyright 2018 The Android Open Source Project
78. com.github.penfeizhou.android.animation:frameanimation
Copyright [2019] [Zhou Pengfei]
79. io.mockk:mockk-dsl
Copyright (c) io.mockk:mockk-dsl original author and authors
80. org.jetbrains.kotlin:kotlin-reflect
Copyright 2010-2016JetBrains s.r.o.
81. o.mockk:mockk-agent-common
Copyright (C) 2017 The Android Open Source Project
82. io.mockk:mockk-agent-api
Copyright (C) io.mockk:mockk-agent-api original author and authors
83. org.objenesis:objenesis
Copyright 2006-2021 Joe Walnes, Henri Tremblay, Leonardo Mesquita
84. net.bytebuddy:byte-buddy
Copyright (c) net.bytebuddy:byte-buddy original author and authors
85. net.bytebuddy:byte-buddy-agent
Copyright (c) net.bytebuddy:byte-buddy-agent original author and authors
86. io.mockk:mockk-dsl-jvm
Copyright (c) io.mockk:mockk-dsl-jvm original author and authors
87. org.jetbrains.kotlin:kotlin-scripting-common
Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
88. org.jetbrains.kotlin:kotlin-scripting-jvm
Copyright (c) org.jetbrains.kotlin:kotlin-scripting-jvm original author and authors
89. org.jetbrains.kotlin:kotlin-script-runtime
Copyright (c) org.jetbrains.kotlin:kotlin-script-runtime original author and authors
90. org.jetbrains.kotlin:kotlin-daemon-embeddable
Copyright (c) org.jetbrains.kotlin:kotlin-daemon-embeddable original author and authors
91. androidx.dynamicanimation:dynamicanimation
Copyright (c) androidx.dynamicanimation:dynamicanimation original author and authors
92. androidx.test:runner
Copyright (c) androidx.test:runner original author and authors
93. androidx.test:monitor
Copyright (c) androidx.test:monitor original author and authors
94. androidx.test.services:storage
Copyright (c) androidx.test.services:storage original author and authors
95. com.squareup:javawriter
Copyright 2013 Square, Inc.
96. javax.inject:javax.inject
Copyright (C) 2009 The JSR-330 Expert Group
97. json
Copyright (C) 2010 The Android Open Source Project
Terms of the Apache License Version 2.0:
--------------------------------------------------------------------
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:
You must give any other recipients of the Work or Derivative Works a copy of this License; and
You must cause any modified files to carry prominent notices stating that You changed the files; and
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
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
Open Source Software Licensed under the Apache License Version 2.0 and Other Licenses of the Third-Party Components therein:
--------------------------------------------------------------------
1. com.tencent.hippy:hippy-common
Copyright (C) 2017-2019 THL A29 Limited, a Tencent company.
All rights reserved.
A copy of the Apache 2.0 is included in this file.
For the license of other third party components, please refer to the following URL:
https://github.com/Tencent/Hippy/blob/2.14.1/LICENSE
Open Source Software Licensed under the Apache License Version 2.0 and Other Licenses of the Third-Party Components therein:
--------------------------------------------------------------------
1. kotlin
Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
A copy of the Apache 2.0 is included in this file.
For the license of other third party components, please refer to the following URL:
https://github.com/JetBrains/kotlin/blob/v1.7.20/license/README.md
Open Source Software Licensed under the Apache License Version 2.0 and Other Licenses of the Third-Party Components therein:
--------------------------------------------------------------------
1. com.tencent.tav:libpag
Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved.
A copy of the Apache 2.0 is included in this file.
For the license of other third party components, please refer to the following URL:
https://github.com/Tencent/libpag/blob/v4.1.49/LICENSE.txt
Open Source Software Licensed under the BSD 2-Clause License and Other Licenses of the Third-Party Components therein:
--------------------------------------------------------------------
1. com.github.bumptech.glide:glide
Copyright 2014 Google, Inc. All rights reserved.
Terms of the BSD 2-Clause:
--------------------------------------------------------------------
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
For the license of other third party components, please refer to the following URL:
https://github.com/bumptech/glide/blob/v4.12.0/LICENSE
Open Source Software Licensed under the BSD 2-Clause License:
--------------------------------------------------------------------
1. com.github.bumptech.glide:compiler
Copyright 2014 Google, Inc. All rights reserved.
2. com.github.bumptech.glide:annotations
Copyright 2014 Google, Inc. All rights reserved.
A copy of the BSD 2-Clause is included in this file.
Open Source Software Licensed under the BSD 3-Clause License:
--------------------------------------------------------------------
1. org.hamcrest:hamcrest-core
Copyright (c) 2000-2006, www.hamcrest.org
All rights reserved.
2. org.hamcrest:hamcrest-library
Copyright (c) 2000-2006, www.hamcrest.org
All rights reserved.
3. org.hamcrest:hamcrest-integration
Copyright (c) 2000-2006, www.hamcrest.org
All rights reserved.
Terms of the BSD 3-Clause License:
--------------------------------------------------------------------
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Open Source Software in the Public Domain:
--------------------------------------------------------------------
1. org.json:json
```
## /OpenKuiklyIOSRender.podspec
```podspec path="/OpenKuiklyIOSRender.podspec"
Pod::Spec.new do |spec|
spec.name = "OpenKuiklyIOSRender"
spec.version = "2.0.0"
spec.summary = "Kuikly"
spec.description = <<-DESC
-Kuikly iOS平台渲染依赖库
DESC
spec.homepage = "https://github.com/Tencent-TDS/KuiklyUI"
spec.license = { :type => "KuiklyUI", :file => "LICENSE" }
spec.author = { "Kuikly" => "ruifanyuan@tencent.com" }
spec.ios.deployment_target = '9.0'
spec.source = { :git => "https://github.com/Tencent-TDS/KuiklyUI.git", :tag => "#{spec.version}" }
spec.dependency "OpenTDFCommon", "~> 1.0.0"
spec.pod_target_xcconfig = {
'VALID_ARCHS[sdk=iphonesimulator*]' => 'arm64 x86_64'
}
spec.user_target_xcconfig = { 'CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES' => 'YES' }
spec.requires_arc = true
spec.source_files = 'core-render-ios/**/*.{h,m,c,mm,s,cpp,cc}'
spec.libraries = "c++"
end
```
## /README-zh_CN.md
English | [简体中文](./README-zh_CN.md) | [Homepage](https://framework.tds.qq.com/)
## Introduction
`Kuikly` is a comprehensive cross-platform solution for UI and logic based on Kotlin multi-platform. It was launched by Tencent's company-level Oteam in the front-end field. It aims to provide a `high-performance, full-platform development framework with unified codebase, ultimate ease of use, and dynamic flexibility`. Currently supported platforms:
- [X] Android
- [X] iOS
- [ ] HarmonyOS(Open-source in May)
- [ ] Web (Open-source in Q2)
- [ ] Mini Programs (Open-source in Q2)
Since its launch, `Kuikly` has gained wide recognition from the business. It has been used by many products such as QQ, QQ Music, QQ Browser, Tencent News, Sogou Input Method, MyApp Hub(Tencent's app store), WeSing, Kugou Music, Kuwo Music, Tencent Self-selected Stock, ima.copilot, Weishi, etc.
## Key Features
- **Cross-platform:** Kotlin-based implementation ensuring consistent operation across multiple platforms - one codebase, five platforms
- **Native performance:** Generates platform-native binaries (.aar/.framework)
- **Native development experience:** Native UI rendering, native toolchain support, Kotlin as primary language
- **Lightweight:** Minimal SDK footprint (AOT mode: ~300KB for Android, ~1.2MB for iOS)
- **Dynamic capability:** Supports compilation into dynamic deliverables
- **Multiple paradigms:** Supports both declarative & reactive programming, with self-developed DSL and Compose DSL (under development).
## Project Structure
```shell
.
├── core # Cross-platform module implementing core capabilities like responsive UI, layout algorithms, Bridge communication, etc.
├── src
├── commanMain # Shared cross-platform code, defining cross-platform interfaces
├── androidMain # Android platform implementation (outputs aar)
├── jvmMain # Generic JVM platform code (no Android APIs, outputs jar)
├── iosMain # iOS platform implementation (outputs framework)
├── core-render-android # Android platform renderer module
├── core-render-ios # iOS platform renderer module
├── core-annotations # Annotations module, defining business annotations like @Page
├── core-ksp # Annotation processing module, generates Core entry files
├── buildSrc # Build scripts for compilation, packaging, and artifact splitting
├── demo # DSL example code
├── androidApp # Android host shell project
└── iosApp # iOS host shell project
```
## System Requirements
- iOS 12.0+
- Android 5.0+
- HarmonyOS Next 5.0.0(12)+
- Kotlin 1.3.10+
## Getting Started
- [Quick Start](https://kuikly.tds.qq.com/%E5%BF%AB%E9%80%9F%E5%BC%80%E5%A7%8B/hello-world.html)
- [Integration Guide](https://kuikly.tds.qq.com/%E5%BF%AB%E9%80%9F%E5%BC%80%E5%A7%8B/overview.html)
- [Component Features](https://kuikly.tds.qq.com/API/%E7%BB%84%E4%BB%B6/override.html)
## Building from Source
### Environment Setup
Refer to [Environment Configuration](https://kuikly.tds.qq.com/%E5%BF%AB%E9%80%9F%E5%BC%80%E5%A7%8B/env-setup.html)
### Running Android App
Ensure environment preparation is complete before building:
1. Open `KuiklyUI` root directory in `Android Studio` and sync project
2. Select androidApp configuration, then Run 'androidApp'
### Running iOS App
Ensure environment preparation is complete before building:
1. Navigate to `iosApp` directory
2. Execute `pod install --repo-update`
3. Open `KuiklyUI` root directory in Android Studio and sync project
4. Select iOSApp configuration, then Run 'iOSApp'
Alternatively, open KuiklyUI/iosApp in Xcode and Run
### Kotlin Version Support
The KuiklyUI directory contains Gradle configurations for various `Kotlin versions`:
Naming convention: `x.x.xx.gradle.kts` (default uses Kotlin 1.7.20)
Test publishing scripts for each version are available as `x.x.xx_test_publish.sh` for building local artifacts.
> Note: Kotlin 1.3.10/1.4.20 require JDK11
After successful build on any platform, you can modify Core, Render, and Demo to experience `Kuikly` development.
## Roadmap
[Roadmap (2025)](https://kuikly.tds.qq.com/%E5%8D%9A%E5%AE%A2/roadmap2025.html)
## Contribution Guidelines
We welcome all developers to submit issues or PRs for `Kuikly`. Please review our [Contribution Guide](CONTRIBUTING.md) before contributing.
## Code of Conduct
All project participants are expected to adhere to our [Code of Conduct](CODE_OF_CONDUCT.md). Participation constitutes agreement to these terms.
## FAQs
[`Kuikly` Q&A](https://kuikly.tds.qq.com/QA/kuikly-qa.html)
## Contributors
- Special thanks to the first batch of contributors tom(邱良雄), kam(林锦涛), and watson(金盎), who not only pioneered the incubation and exploration of the Kuikly cross-platform solutions in the frontend field, but also were the first to implement them in the QQ business.
- Thanks to the following core contributors for the continuous construction, maintenance, development and optimization of `Kuikly`:
tom kam watson rocky jonas ruifan pel layen bird zealot zhenhua vinney xuanxi arnon alexa allens eason
## Stay Connected
Scan the QR codes below to follow our latest updates or contact us for inquiries.
TDS WeChat Official Account
TDS Framework WeChat Official Account
Online Support
## /add_internal.sh
```sh path="/add_internal.sh"
#!/bin/bash
process_add_internal() {
echo "Processing $1"
# 读取文件内容
CONTENT=$(cat "$1")
# 使用正则表达式查找类定义,并在没有 internal、private 或 protected 的类、枚举、单例、抽象类和接口前添加 internal,同时避免在已经有 ::class 的地方添加,并保留文件末尾的换行符
NEW_CONTENT=$(echo "$CONTENT" | perl -pe 's/^(?:(?!(internal|private|protected|::))\b\s)*(sealed\s+)?(data\s+)?(enum\s+)?(abstract\s+)?(object\s+)?(class|interface)\s+(\w+)/internal $1$2$3$4$5$6$7 $8/g;
s/^(?:(?!(internal|private|protected|::))\b\s)*fun\s+/internal fun /g;
s/^(?:(?!(internal|private|protected|::))\b\s)*val\s+/internal val /g;
s/^(?:(?!(internal|private|protected|::))\b\s)*object\s+/internal object /g;
s/^(?:(?!(internal|private|protected|::))\b\s)*public fun\s+/internal fun /g;
s/^(?:(?!(internal|private|protected|::))\b\s)*public data class\s+/internal data class /g;
s/^(?:(?!(internal|private|protected|::))\b\s)*typealias\s+/internal typealias /g;
s/^(?:(?!(internal|private|protected|::))\b\s)*abstract open class\s+/internal abstract open class /g;
s/^(?:(?!(internal|private|protected|::))\b\s)*open class\s+/internal open class /g;
s/^(?:(?!(internal|private|protected|::))\b\s)*const val\s+/internal const val /g;
s/^(.*?)\bpublic\s+class\b(.*)$/$1internal class$2/g;
s/^(.*?)\bpublic\s+object\b(.*)$/$1internal object$2/g;
s/^(?:(?!(internal|private|protected|::))\b\s)*public interface\s+/internal interface /g;
s/^(.*?)\bpublic\s+public companion object\b(.*)$/$1internal companion object$2/g;
')
NEW_CONTENT=$(echo "$NEW_CONTENT" | perl -pe 's/\n?$/\n/;')
# 将修改后的内容写回文件
echo "$NEW_CONTENT" > "$FILE"
}
process_add_dir_internal() {
# 查找所有的 Kotlin 文件
KOTLIN_FILES=$(find "$1" -type f -name "*.kt")
for FILE in $KOTLIN_FILES; do
process_add_internal "$FILE"
done
}
# 修改此变量以指向你的模块目录
MODULE_DIR="./demo/src/commonMain"
process_add_dir_internal "$MODULE_DIR"
echo "All done!"
```
## /androidApp/build.gradle.kts
```kts path="/androidApp/build.gradle.kts"
plugins {
id("com.android.application")
kotlin("android")
}
android {
compileSdk = 32
defaultConfig {
applicationId = "com.tencent.kuikly.android.demo"
minSdk = 21
targetSdk = 32
versionCode = 1
versionName = "1.0"
}
buildTypes {
getByName("release") {
isMinifyEnabled = false
signingConfig = signingConfigs.getByName("debug")
}
}
sourceSets.getByName("main") {
jniLibs {
srcDir("libs")
}
}
packagingOptions {
doNotStrip("**/*.so")
}
}
dependencies {
implementation(fileTree("libs") {
include("*.jar")
})
implementation(project(":core"))
implementation(project(":demo"))
implementation(project(":core-render-android"))
implementation("com.squareup.okhttp3:okhttp:3.12.0")
implementation(Dependencies.material)
implementation(Dependencies.androidxAppcompat)
implementation(Dependencies.hippy)
implementation(Dependencies.androidXCoreKtx)
implementation("com.github.bumptech.glide:glide:4.12.0") // Glide主库,确保这里的版本是最新的
annotationProcessor("com.github.bumptech.glide:compiler:4.12.0") // Glide注解处理
implementation("com.tencent.tav:libpag:4.1.49-noffavc")
implementation("com.google.android.exoplayer:exoplayer:2.16.1")
implementation("com.github.penfeizhou.android.animation:apng:2.25.0")
}
```
## /androidApp/src/main/AndroidManifest.xml
```xml path="/androidApp/src/main/AndroidManifest.xml"
```
## /androidApp/src/main/assets/configColor.ini
```ini path="/androidApp/src/main/assets/configColor.ini"
[Color]
;跟肤-背景色
kuikly_skin_color_bg_default=#FFFFFFFF
kuikly_skin_color_bg_backplate=#FFF5F5F5
kuikly_skin_color_bg_skeleton=#FFF5F5F5
kuikly_skin_color_bg_mask_50=#80000000
kuikly_skin_color_bg_brokerage=#80FFF6F7
;跟肤-渐变背景色
kuikly_skin_color_bg_gradient01_normal=#FF46A0
kuikly_skin_color_bg_gradient02_normal=#FF3355
;跟肤-描边色
kuikly_skin_color_divider=#FFE6E6E6
kuikly_skin_color_btn_secondary_red_pressed_40=#FFE6E6E6
;跟肤-按钮色
kuikly_skin_color_btn_primary_red_normal=#FFFF3370
kuikly_skin_color_btn_primary_red_pressed=#FFE62E65
kuikly_skin_color_btn_primary_red_disable=#FFFFCCDB
kuikly_skin_color_btn_secondary_red_normal=#FFF5F5F5
kuikly_skin_color_btn_secondary_red_pressed=#FFE6E6E6
kuikly_skin_color_btn_secondary_red_disable=#FFF5F5F5
kuikly_skin_color_btn_primary_blue_normal=#FF0099FF
kuikly_skin_color_btn_primary_blue_pressed=#FF008AE5
kuikly_skin_color_btn_primary_blue_disable=#FFCCF0FF
;跟肤-渐变按钮
kuikly_skin_color_btn_gradient01_normal=#FFFF46A0
kuikly_skin_color_btn_gradient02_normal=#FFFF3355
kuikly_skin_color_btn_gradient01_pressed=#FFE63E8F
kuikly_skin_color_btn_gradient02_pressed=#FFE62E4D
kuikly_skin_color_btn_gradient01_disable=#FFFFCCE5
kuikly_skin_color_btn_gradient02_disable=#FFFFCCD5
;跟肤-文字色
kuikly_skin_color_text_primary=#FF000000
kuikly_skin_color_text_secondary=#FF999999
kuikly_skin_color_text_light=#FFC7C7C7
kuikly_skin_color_text_blue_brand=#FF0099FF
kuikly_skin_color_text_warning=#FFFF5967
kuikly_skin_color_text_hashtag=#FF32498F
kuikly_skin_color_text_red_brand=#FFFF3370
;跟肤-图标色
kuikly_skin_color_icon_primary=#FF000000
kuikly_skin_color_icon_secondary=#FF999999
kuikly_skin_color_icon_light=#FFC7C7C7
kuikly_skin_color_icon_blue_brand=#FF0099FF
kuikly_skin_color_icon_warning=#FFFF5967
kuikly_skin_color_icon_red_brand=#FFFF3370
;跟肤-标签色
kuikly_skin_color_tag_primary=#FFFF99B8
kuikly_skin_color_tag_secondary=#FFFFCCDB
kuikly_skin_color_tag_secondary_40=#66FFCCDB
kuikly_skin_color_tag_light=#FFFFF2F6
;跟肤-标签背景色
kuikly_skin_color_tag_normal=#FFF5F5F5
kuikly_skin_color_tag_selected=#FFF5F5F5
;跟肤-链接色
kuikly_skin_color_text_link=#FF2E77E5
;不跟肤-标签背景色
kuikly_color_tag_normal=#1AFFFFFF
kuikly_color_tag_selected=#33FFFFFF
kuikly_color_tag_light_100=#FFFFFFFF
kuikly_color_tag_light_70=#B3FFFFFF
kuikly_color_tag_light_15=#26FFFFFF
kuikly_color_tag_light_10=#1AFFFFFF
kuikly_color_tag_dark_80=#CC2F2F2F
kuikly_color_tag_dark_60=#992F2F2F
kuikly_color_tag_dark_50=#802F2F2F
kuikly_color_tag_dark_40=#662F2F2F
kuikly_color_tag_dark_30=#4D2F2F2F
kuikly_color_tips_dark=#FF252525
;不跟肤-背景色
kuikly_color_bg_default=#FFFFFFFF
kuikly_color_bg_backplate=#FFF5F5F5
kuikly_color_bg_skeleton=#FFF5F5F5
kuikly_color_bg_mask_50=#80000000
;不跟肤-描边色
kuikly_color_divider=#FFE6E6E6
;不跟肤-按钮色
kuikly_color_btn_primary_red_normal=#FFFF3370
kuikly_color_btn_primary_red_pressed=#FFE62E65
kuikly_color_btn_primary_red_disable=#FFFFCCDB
kuikly_color_btn_secondary_red_normal=#FFF5F5F5
kuikly_color_btn_secondary_red_pressed=#FFE6E6E6
kuikly_color_btn_secondary_red_disable=#FFF5F5F5
kuikly_color_btn_primary_blue_normal=#FF0099FF
kuikly_color_btn_primary_blue_pressed=#FF008AE5
kuikly_color_btn_primary_blue_disable=#FFCCF0FF
;不跟肤-渐变按钮
kuikly_color_btn_gradient01_normal=#FFFF46A0
kuikly_color_btn_gradient02_normal=#FFFF3355
kuikly_color_btn_gradient01_pressed=#FFE63E8F
kuikly_color_btn_gradient02_pressed=#FFE62E4D
kuikly_color_btn_gradient01_disable=#FFFFCCE5
kuikly_color_btn_gradient02_disable=#FFFFCCD5
;不跟肤-文字色
kuikly_color_text_primary=#FF000000
kuikly_color_text_secondary=#FF999999
kuikly_color_text_light=#FFC7C7C7
kuikly_color_text_blue_brand=#FF0099FF
kuikly_color_text_warning=#FFFF5967
kuikly_color_text_hashtag=#FF32498F
kuikly_color_text_red_brand=#FFFF3370
;不跟肤-图标色
kuikly_color_icon_primary=#FF000000
kuikly_color_icon_secondary=#FF999999
kuikly_color_icon_light=#FFC7C7C7
kuikly_color_icon_blue_brand=#FF0099FF
kuikly_color_icon_warning=#FFFF5967
kuikly_color_icon_red_brand=#FFFF3370
;不跟肤-标签色
kuikly_color_tag_primary=#FFFF99B8
kuikly_color_tag_secondary=#FFFFCCDB
kuikly_color_tag_light=#FFFFF2F6
;不跟肤-标签背景色
;不跟肤-链接色
kuikly_color_text_link=#FF2E77E5
```
## /androidApp/src/main/assets/fonts/Satisfy-Regular.ttf
Binary file available at https://raw.githubusercontent.com/Tencent-TDS/KuiklyUI/refs/heads/main/androidApp/src/main/assets/fonts/Satisfy-Regular.ttf
## /androidApp/src/main/java/com/tencent/kuikly/android/demo/AppMainActivity.kt
```kt path="/androidApp/src/main/java/com/tencent/kuikly/android/demo/AppMainActivity.kt"
/*
* Tencent is pleased to support the open source community by making KuiklyUI
* available.
* Copyright (C) 2025 THL A29 Limited, a Tencent company. All rights reserved.
* Licensed under the License of KuiklyUI;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* https://github.com/Tencent-TDS/KuiklyUI/blob/main/LICENSE
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.tencent.kuikly.android.demo
import android.app.Activity
import android.os.Bundle
import android.view.View
import android.widget.TextView
class AppMainActivity: Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_app_main)
val btn = findViewById(R.id.open_kuikly_page_btn)
val performanceBtn = findViewById(R.id.get_performance_data_btn)
btn.setOnClickListener {
val pageData = org.json.JSONObject()
KuiklyRenderActivity.start(MainActivity@this, "AppTabPage", pageData)
MainActivity@this.overridePendingTransition(R.anim.slide_from_bottom, 0)
}
performanceBtn.setOnClickListener {
val performanceData = MainActivity@this.getSharedPreferences("performance", MODE_PRIVATE).getString("PerformanceData", "")
findViewById(R.id.get_performance_data_tv).text = performanceData
}
}
}
```
## /androidApp/src/main/java/com/tencent/kuikly/android/demo/ContextCodeHandler.kt
```kt path="/androidApp/src/main/java/com/tencent/kuikly/android/demo/ContextCodeHandler.kt"
/*
* Tencent is pleased to support the open source community by making KuiklyUI
* available.
* Copyright (C) 2025 THL A29 Limited, a Tencent company. All rights reserved.
* Licensed under the License of KuiklyUI;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* https://github.com/Tencent-TDS/KuiklyUI/blob/main/LICENSE
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.tencent.kuikly.android.demo
import android.app.Activity
import android.util.Log
import android.view.ViewGroup
import com.tencent.kuikly.android.demo.module.KRBridgeModule
import com.tencent.kuikly.android.demo.module.KRNotifyModule
import com.tencent.kuikly.android.demo.module.KRShareModule
import com.tencent.kuikly.android.demo.module.tdf.KRTDFTestModule
import com.tencent.kuikly.core.render.android.IKuiklyRenderExport
import com.tencent.kuikly.core.render.android.adapter.KuiklyRenderLog
import com.tencent.kuikly.core.render.android.context.KuiklyRenderCoreExecuteModeBase
import com.tencent.kuikly.core.render.android.exception.ErrorReason
import com.tencent.kuikly.core.render.android.expand.KuiklyRenderViewBaseDelegatorDelegate
import com.tencent.kuikly.core.render.android.expand.KuiklyRenderViewBaseDelegator
import com.tencent.kuikly.core.render.android.performace.KRMonitorType
import com.tencent.kuikly.core.render.android.performace.KRPerformanceData
import com.tencent.kuikly.core.render.android.performace.launch.KRLaunchData
open class ContextCodeHandler(val pageName: String) {
private var beginTime : Long = 0
lateinit var kuiklyRenderViewDelegator: KuiklyRenderViewBaseDelegator
init {
}
open fun initContextHandler() : KuiklyRenderViewBaseDelegator{
// 2.1 实现KuiklyRenderViewBaseDelegatorDelegate接口
val delegate = object : KuiklyRenderViewBaseDelegatorDelegate {
// 2.1.1 处理Kuikly性能监控数据回调
override fun onGetPerformanceData(data: KRPerformanceData) {
this@ContextCodeHandler.onGetPerformanceData(data)
}
// 2.1.2 处理Kuikly 启动性能数据回调
override fun onGetLaunchData(data: KRLaunchData) {
this@ContextCodeHandler.onGetLaunchData(data)
}
// 2.1.3 注册自定义module
override fun registerExternalModule(kuiklyRenderExport: IKuiklyRenderExport) {
super.registerExternalModule(kuiklyRenderExport)
this@ContextCodeHandler.registerExternalModule(kuiklyRenderExport)
}
// 2.1.4 让宿主工程暴露自己的View
override fun registerExternalRenderView(kuiklyRenderExport: IKuiklyRenderExport) {
super.registerExternalRenderView(kuiklyRenderExport)
this@ContextCodeHandler.registerExternalRenderView(kuiklyRenderExport)
}
// 2.1.5 注册 TDF 通用 Module
override fun registerTDFModule(kuiklyRenderExport: IKuiklyRenderExport) {
super.registerTDFModule(kuiklyRenderExport)
this@ContextCodeHandler.registerTDFModule(kuiklyRenderExport)
}
// 2.1.6 给Kuikly框架设置页面打开模式,即KuiklyRenderCore的执行模式, 默认JVM模式
override fun coreExecuteModeX(): KuiklyRenderCoreExecuteModeBase {
return KuiklyRenderCoreExecuteModeBase.JVM
}
// 2.1.7 给Kuikly框架设置性能监控选项,默认只开启动监控
override fun performanceMonitorTypes(): List {
return this@ContextCodeHandler.performanceMonitorTypes()
}
// 2.1.8 [KuiklyRenderView]创建回调
override fun onKuiklyRenderViewCreated() {
super.onKuiklyRenderViewCreated()
this@ContextCodeHandler.onKuiklyRenderViewCreated()
}
// 2.1.9 [KuiklyRenderView]的子View已经创建回调
override fun onKuiklyRenderContentViewCreated() {
super.onKuiklyRenderContentViewCreated()
this@ContextCodeHandler.onKuiklyRenderContentViewCreated()
}
// 2.1.10 异常通知回调
override fun onUnhandledException(
throwable: Throwable,
errorReason: ErrorReason,
executeMode: KuiklyRenderCoreExecuteModeBase
) {
this@ContextCodeHandler.onUnhandledException(throwable, errorReason, executeMode.mode)
}
// 2.1.11 页面加载回调
override fun onPageLoadComplete(
isSucceed: Boolean,
errorReason: ErrorReason?,
executeMode: KuiklyRenderCoreExecuteModeBase
) {
this@ContextCodeHandler.onPageLoadComplete(isSucceed, errorReason, executeMode.mode)
}
}
// 2.2 创建KuiklyRenderViewBaseDelegator实例
kuiklyRenderViewDelegator = KuiklyRenderViewBaseDelegator(delegate)
return kuiklyRenderViewDelegator
}
open fun openPage(activity: Activity, hrContainerView: ViewGroup,
pageName: String, pageData: Map) {
// 4.1 通过框架Delegator,打开Kuikly页面
kuiklyRenderViewDelegator.onAttach(
hrContainerView,
"",
pageName,
pageData
)
}
fun onGetPerformanceData(data: KRPerformanceData) {
KuiklyRenderLog.d(TAG, "------------------------------------------ [PerformanceData] ------------------------------------------")
KuiklyRenderLog.d(TAG, "[PerformanceData] $data")
KuiklyRenderLog.d(TAG, "[PerformanceData] [Launch] ${data.launchData}")
KuiklyRenderLog.d(TAG, "[PerformanceData] [Frame] ${data.frameData}, fps: ${data.frameData?.getFps()}, kuiklyFps: ${data.frameData?.getKuiklyFps()}")
KuiklyRenderLog.d(TAG, "[PerformanceData] [Memory] maxPss: ${data.memoryData?.getMaxPss()}, deltaPss: ${data.memoryData?.getMaxPssIncrement()}, firstDeltaPss: ${data.memoryData?.getFirstPssIncrement()}")
KuiklyRenderLog.d(TAG, "[PerformanceData] [Memory] maxJavaHeap: ${data.memoryData?.getMaxJavaHeap()}, deltaJavaHeap: ${data.memoryData?.getMaxJavaHeapIncrement()}, fristDeltaJavaHeap: ${data.memoryData?.getFirstDeltaJavaHeap()}")
KuiklyRenderLog.d(TAG, "------------------------------------------ [PerformanceData] ------------------------------------------")
val performanceData = """
[PerformanceData] [Launch] ${data.launchData?.firstFramePaintCost}
[PerformanceData] [Frame] fps: ${data.frameData?.getFps()}, kuiklyFps: ${data.frameData?.getKuiklyFps()}
[PerformanceData] [Memory] firstPss: ${data.memoryData?.getFirstPssIncrement()}
[PerformanceData] [Memory] maxPss: ${data.memoryData?.getMaxPss()}
""".trimIndent()
// getSharedPreferences("performance", MODE_PRIVATE).edit().putString("PerformanceData", performanceData).commit()
}
fun onGetLaunchData(data: KRLaunchData) {
KuiklyRenderLog.d(TAG, "[onGetLaunchData] $data")
}
fun registerExternalModule(kuiklyRenderExport: IKuiklyRenderExport) {
with(kuiklyRenderExport) {
moduleExport(KRBridgeModule.MODULE_NAME) {
KRBridgeModule()
}
moduleExport(KRNotifyModule.MODULE_NAME) {
KRNotifyModule()
}
moduleExport(KRShareModule.MODULE_NAME) {
KRShareModule()
}
}
}
fun registerExternalRenderView(kuiklyRenderExport: IKuiklyRenderExport) {
with(kuiklyRenderExport) {
renderViewExport(KuiklyPageView.VIEW_NAME, {
KuiklyPageView(it)
})
}
}
fun registerTDFModule(kuiklyRenderExport: IKuiklyRenderExport) {
with(kuiklyRenderExport) {
registerTDFModule(KRTDFTestModule::class.java) {
KRTDFTestModule(it)
}
}
}
fun performanceMonitorTypes(): List {
return listOf(KRMonitorType.LAUNCH, KRMonitorType.FRAME, KRMonitorType.MEMORY)
}
fun turboDisplayKey(): String? {
return pageName
}
fun onKuiklyRenderViewCreated() {
beginTime = System.currentTimeMillis()
}
fun onKuiklyRenderContentViewCreated() {
val endTime = System.currentTimeMillis()
val elapsedTime = endTime - beginTime
KuiklyRenderLog.d("", "[KRLaunchMeta] onKuiklyRenderContentViewCreated ${System.currentTimeMillis()}")
Log.d(TAG, "pageCostTime:${elapsedTime}ms")
}
fun onUnhandledException(
throwable: Throwable,
errorReason: ErrorReason,
executeMode: Int
) {
KuiklyRenderLog.e(TAG, "onUnhandledException, errorReason: $errorReason, executeMode: ${executeMode}, ${throwable.stackTraceToString()}")
}
fun onPageLoadComplete(
isSucceed: Boolean,
errorReason: ErrorReason?,
executeMode: Int
) {
KuiklyRenderLog.e(TAG, "onPageLoadComplete isSucceed: $isSucceed, errorReason: $errorReason, executeMode: ${executeMode}")
}
companion object {
private const val TAG: String = "ContextCodeHandler"
}
}
```
## /androidApp/src/main/java/com/tencent/kuikly/android/demo/KRApplication.kt
```kt path="/androidApp/src/main/java/com/tencent/kuikly/android/demo/KRApplication.kt"
/*
* Tencent is pleased to support the open source community by making KuiklyUI
* available.
* Copyright (C) 2025 THL A29 Limited, a Tencent company. All rights reserved.
* Licensed under the License of KuiklyUI;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* https://github.com/Tencent-TDS/KuiklyUI/blob/main/LICENSE
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.tencent.kuikly.android.demo
import android.app.Application
/**
* Created by kam on 2022/10/20.
*/
class KRApplication : Application() {
init {
application = this
}
companion object {
lateinit var application: Application
}
}
```
## /androidApp/src/main/java/com/tencent/kuikly/android/demo/KuiklyPageView.kt
```kt path="/androidApp/src/main/java/com/tencent/kuikly/android/demo/KuiklyPageView.kt"
/*
* Tencent is pleased to support the open source community by making KuiklyUI
* available.
* Copyright (C) 2025 THL A29 Limited, a Tencent company. All rights reserved.
* Licensed under the License of KuiklyUI;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* https://github.com/Tencent-TDS/KuiklyUI/blob/main/LICENSE
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.tencent.kuikly.android.demo
import android.content.Context
import android.util.Size
import android.view.ViewGroup
import android.widget.FrameLayout
import com.tencent.kuikly.core.render.android.context.KuiklyRenderCoreExecuteModeBase
import com.tencent.kuikly.core.render.android.css.ktx.frame
import com.tencent.kuikly.core.render.android.css.ktx.toJSONObjectSafely
import com.tencent.kuikly.core.render.android.css.ktx.toMap
import com.tencent.kuikly.core.render.android.exception.ErrorReason
import com.tencent.kuikly.core.render.android.expand.KuiklyRenderViewBaseDelegatorDelegate
import com.tencent.kuikly.core.render.android.export.IKuiklyRenderViewExport
import com.tencent.kuikly.core.render.android.export.KuiklyRenderCallback
import org.json.JSONObject
/**
* Created by kam on 2023/9/22.
*/
class KuiklyPageView(context: Context) : FrameLayout(context), IKuiklyRenderViewExport,
KuiklyRenderViewBaseDelegatorDelegate {
private var kuiklyRenderView: KuiklyRenderView? = null
private var pageName = ""
private var pageData = "{}"
private var loadSuccessCallback: KuiklyRenderCallback? = null
private var loadFailureCallback: KuiklyRenderCallback? = null
private val lazyEvents by lazy(LazyThreadSafetyMode.NONE) { mutableListOf<() -> Unit>() }
override fun setProp(propKey: String, propValue: Any): Boolean {
return when (propKey) {
"loadSuccess" -> loadSuccessCallback(propValue)
"loadFailure" -> loadFailure(propValue)
"pageName" -> {
pageName = propValue as String
true
}
"pageData" -> {
pageData = propValue as String
true
}
else -> super.setProp(propKey, propValue)
}
}
override fun call(method: String, params: String?, callback: KuiklyRenderCallback?): Any? {
return when (method) {
"sendEvent" -> sendEventWithParams(params)
else -> super.call(method, params, callback)
}
}
override fun onPageLoadComplete(
isSucceed: Boolean,
errorReason: ErrorReason?,
executeMode: KuiklyRenderCoreExecuteModeBase,
) {
if (isSucceed && loadSuccessCallback != null) {
loadSuccessCallback?.invoke(mapOf())
}
if (!isSucceed && loadFailureCallback != null) {
loadSuccessCallback?.invoke(mapOf())
}
}
override fun setLayoutParams(params: ViewGroup.LayoutParams?) {
super.setLayoutParams(params)
params?.also {
kuiklyRenderView?.layoutParams = LayoutParams(it.width, it.height)
}
}
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom)
initKuiklyViewIfNeed()
}
override fun onDestroy() {
super.onDestroy()
kuiklyRenderView?.onDetach()
}
private fun initKuiklyViewIfNeed() {
if (kuiklyRenderView != null) {
return
}
val currentFrame = frame
if (currentFrame.right > 0 && currentFrame.bottom > 0 && pageName.isNotEmpty()) {
val hostPageData = mutableMapOf().apply {
putAll(getHostPageData())
}
hostPageData.putAll(pageData.toJSONObjectSafely().toMap())
kuiklyRenderView = KuiklyRenderView(context, this).apply {
this@KuiklyPageView.addView(this)
onAttach("", pageName, mapOf(), Size(currentFrame.right, currentFrame.bottom))
performAllLazyTasks()
}
}
}
private fun sendEventWithParams(params: String?) {
val json = params.toJSONObjectSafely()
val event = json.optString("event")
val data = json.optJSONObject("data") ?: JSONObject()
performTaskWhenKuiklyViewDidLoad {
kuiklyRenderView?.sendEvent(event, data.toMap())
}
}
private fun performAllLazyTasks() {
lazyEvents.forEach {
it()
}
lazyEvents.clear()
}
private fun loadSuccessCallback(propValue: Any): Boolean {
loadSuccessCallback = propValue as KuiklyRenderCallback
return true
}
private fun loadFailure(propValue: Any): Boolean {
loadFailureCallback = propValue as KuiklyRenderCallback
return true
}
private fun performTaskWhenKuiklyViewDidLoad(callback: () -> Unit) {
if (kuiklyRenderView != null) {
callback()
} else {
lazyEvents.add(callback)
}
}
private fun getHostPageData(): Map {
val act = activity
// if (act is PublicFragmentActivity) {
// return (act.fragment as? KuiklyFragment)?.pageData ?: mapOf()
// }
return mapOf()
}
companion object {
const val VIEW_NAME = "KuiklyPageView"
}
}
```
## /androidApp/src/main/java/com/tencent/kuikly/android/demo/KuiklyRenderActivity.kt
```kt path="/androidApp/src/main/java/com/tencent/kuikly/android/demo/KuiklyRenderActivity.kt"
/*
* Tencent is pleased to support the open source community by making KuiklyUI
* available.
* Copyright (C) 2025 THL A29 Limited, a Tencent company. All rights reserved.
* Licensed under the License of KuiklyUI;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* https://github.com/Tencent-TDS/KuiklyUI/blob/main/LICENSE
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.tencent.kuikly.android.demo
import android.content.Context
import android.content.Intent
import android.graphics.Color
import android.os.Bundle
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import androidx.appcompat.app.AppCompatActivity
import com.tencent.kuikly.android.demo.adapter.KRAPNGViewAdapter
import com.tencent.kuikly.android.demo.adapter.KRColorParserAdapter
import com.tencent.kuikly.android.demo.adapter.KRFontAdapter
import com.tencent.kuikly.android.demo.adapter.KRImageAdapter
import com.tencent.kuikly.android.demo.adapter.KRLogAdapter
import com.tencent.kuikly.android.demo.adapter.KRRouterAdapter
import com.tencent.kuikly.android.demo.adapter.KRTextPostProcessorAdapter
import com.tencent.kuikly.android.demo.adapter.KRThreadAdapter
import com.tencent.kuikly.android.demo.adapter.KRUncaughtExceptionHandlerAdapter
import com.tencent.kuikly.android.demo.adapter.PAGViewAdapter
import com.tencent.kuikly.android.demo.adapter.VideoViewAdapter
import com.tencent.kuikly.core.render.android.adapter.KuiklyRenderAdapterManager
import com.tencent.kuikly.core.render.android.css.ktx.toMap
import com.tencent.kuikly.core.render.android.expand.KuiklyRenderViewBaseDelegator
import org.json.JSONObject
/**
* Created by kam on 2022/7/27.
*/
class KuiklyRenderActivity : AppCompatActivity() {
private lateinit var hrContainerView: ViewGroup
private lateinit var loadingView: View
private lateinit var errorView: View
private lateinit var kuiklyRenderViewDelegator: KuiklyRenderViewBaseDelegator
protected val pageName: String
get() {
val pn = intent.getStringExtra(KEY_PAGE_NAME) ?: ""
return if (pn.isNotEmpty()) {
return pn
} else {
"router"
}
}
private lateinit var contextCodeHandler: ContextCodeHandler
init {
if (KuiklyRenderAdapterManager.krImageAdapter == null) {
KuiklyRenderAdapterManager.krImageAdapter = KRImageAdapter(this)
}
if (KuiklyRenderAdapterManager.krLogAdapter == null) {
KuiklyRenderAdapterManager.krLogAdapter = KRLogAdapter
}
if (KuiklyRenderAdapterManager.krUncaughtExceptionHandlerAdapter == null) {
KuiklyRenderAdapterManager.krUncaughtExceptionHandlerAdapter =
KRUncaughtExceptionHandlerAdapter
}
if (KuiklyRenderAdapterManager.krFontAdapter == null) {
KuiklyRenderAdapterManager.krFontAdapter = KRFontAdapter
}
if (KuiklyRenderAdapterManager.krColorParseAdapter == null) {
KuiklyRenderAdapterManager.krColorParseAdapter =
KRColorParserAdapter(KRApplication.application)
}
if (KuiklyRenderAdapterManager.krRouterAdapter == null) {
KuiklyRenderAdapterManager.krRouterAdapter = KRRouterAdapter()
}
if (KuiklyRenderAdapterManager.krThreadAdapter == null) {
KuiklyRenderAdapterManager.krThreadAdapter = KRThreadAdapter()
}
if (KuiklyRenderAdapterManager.krPagViewAdapter == null) {
KuiklyRenderAdapterManager.krPagViewAdapter = PAGViewAdapter()
}
if (KuiklyRenderAdapterManager.krAPNGViewAdapter == null) {
KuiklyRenderAdapterManager.krAPNGViewAdapter = KRAPNGViewAdapter()
}
if (KuiklyRenderAdapterManager.krVideoViewAdapter == null) {
KuiklyRenderAdapterManager.krVideoViewAdapter = VideoViewAdapter()
}
if (KuiklyRenderAdapterManager.krTextPostProcessorAdapter == null) {
KuiklyRenderAdapterManager.krTextPostProcessorAdapter = KRTextPostProcessorAdapter()
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
contextCodeHandler = ContextCodeHandler(pageName) // 1. 创建一个Kuikly页面打开的封装处理器
kuiklyRenderViewDelegator = contextCodeHandler.initContextHandler() // 2. 实例化Kuikly委托者类
setContentView(R.layout.activity_hr)
setupImmersiveMode()
hrContainerView = findViewById(R.id.hr_container) // 3. 获取用于承载Kuikly的容器View
loadingView = findViewById(R.id.hr_loading)
errorView = findViewById(R.id.hr_error)
// 4. 触发Kuikly View实例化
// hrContainerView:承载Kuikly的容器View
// contextCode: jvm模式下传递""
// pageName: 传递想要打开的Kuikly侧的Page名字
// pageData: 传递给Kuikly页面的参数
contextCodeHandler.openPage(this, hrContainerView, pageName, createPageData())
}
override fun onResume() { // 5.通知Kuikly页面触发onResume
super.onResume()
kuiklyRenderViewDelegator.onResume()
}
override fun onPause() { // 6. 通知Kuikly页面触发onStop
super.onPause()
kuiklyRenderViewDelegator.onPause()
}
override fun onDestroy() { // 7. 通知Kuikly页面触发onDestroy
super.onDestroy()
kuiklyRenderViewDelegator.onDetach()
}
private fun createPageData(): Map {
val param = argsToMap()
param["appId"] = 1
return param
}
private fun argsToMap(): MutableMap {
val jsonStr = intent.getStringExtra(KEY_PAGE_DATA) ?: return mutableMapOf()
return JSONObject(jsonStr).toMap()
}
private fun setupImmersiveMode() {
window?.apply {
addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
window?.statusBarColor = Color.TRANSPARENT
window?.decorView?.systemUiVisibility =
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
}
}
companion object {
private const val TAG = "KuiklyRenderActivity"
private const val KEY_PAGE_NAME = "pageName"
private const val KEY_PAGE_DATA = "pageData"
fun start(context: Context, pageName: String, pageData: JSONObject) {
val starter = Intent(context, KuiklyRenderActivity::class.java)
starter.putExtra(KEY_PAGE_NAME, pageName)
starter.putExtra(KEY_PAGE_DATA, pageData.toString())
context.startActivity(starter)
}
}
}
```
## /androidApp/src/main/java/com/tencent/kuikly/android/demo/KuiklyRenderView.kt
```kt path="/androidApp/src/main/java/com/tencent/kuikly/android/demo/KuiklyRenderView.kt"
/*
* Tencent is pleased to support the open source community by making KuiklyUI
* available.
* Copyright (C) 2025 THL A29 Limited, a Tencent company. All rights reserved.
* Licensed under the License of KuiklyUI;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* https://github.com/Tencent-TDS/KuiklyUI/blob/main/LICENSE
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.tencent.kuikly.android.demo
import android.content.Context
import com.tencent.kuikly.android.demo.module.KRBridgeModule
import com.tencent.kuikly.android.demo.module.KRNotifyModule
import com.tencent.kuikly.android.demo.module.KRShareModule
import com.tencent.kuikly.core.render.android.IKuiklyRenderExport
import com.tencent.kuikly.core.render.android.expand.KuiklyRenderViewBaseDelegatorDelegate
import com.tencent.kuikly.core.render.android.expand.KuiklyBaseView
class KuiklyRenderView(context: Context, delegate: KuiklyRenderViewBaseDelegatorDelegate? = null) : KuiklyBaseView(context, delegate) {
override fun registerExternalModule(kuiklyRenderExport: IKuiklyRenderExport) {
super.registerExternalModule(kuiklyRenderExport)
with(kuiklyRenderExport) {
moduleExport(KRBridgeModule.MODULE_NAME) {
KRBridgeModule()
}
moduleExport(KRNotifyModule.MODULE_NAME) {
KRNotifyModule()
}
moduleExport(KRShareModule.MODULE_NAME) {
KRShareModule()
}
}
}
}
```
## /androidApp/src/main/java/com/tencent/kuikly/android/demo/MainActivity.kt
```kt path="/androidApp/src/main/java/com/tencent/kuikly/android/demo/MainActivity.kt"
/*
* Tencent is pleased to support the open source community by making KuiklyUI
* available.
* Copyright (C) 2025 THL A29 Limited, a Tencent company. All rights reserved.
* Licensed under the License of KuiklyUI;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* https://github.com/Tencent-TDS/KuiklyUI/blob/main/LICENSE
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.tencent.kuikly.android.demo
import android.os.Bundle
import android.view.View
import android.widget.EditText
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity(), View.OnClickListener {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
findViewById(R.id.bt_hr).setOnClickListener(this)
findViewById(R.id.bt_hr_goods).setOnClickListener(this)
}
override fun onClick(view: View) {
val pagerName = findViewById(R.id.ed_pager_name).text.toString()
when (view.id) {
R.id.bt_hr -> {
}
else -> {
}
}
}
}
```
## /androidApp/src/main/java/com/tencent/kuikly/android/demo/NativeMixKuiklyViewDemoActivity.kt
```kt path="/androidApp/src/main/java/com/tencent/kuikly/android/demo/NativeMixKuiklyViewDemoActivity.kt"
/*
* Tencent is pleased to support the open source community by making KuiklyUI
* available.
* Copyright (C) 2025 THL A29 Limited, a Tencent company. All rights reserved.
* Licensed under the License of KuiklyUI;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* https://github.com/Tencent-TDS/KuiklyUI/blob/main/LICENSE
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.tencent.kuikly.android.demo
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import com.tencent.kuikly.core.render.android.css.ktx.toPxI
import com.tencent.kuikly.core.render.android.expand.KuiklyBaseView
/**
* Created by kam on 2023/9/5.
*/
class NativeMixKuiklyViewDemoActivity : AppCompatActivity() {
private var kuiklyView: KuiklyBaseView? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_native_mix_kuikly_view)
val rootView = findViewById(R.id.root_view)
val maskView = findViewById(R.id.bg_mask)
findViewById(R.id.show_kuikly_view).setOnClickListener {
kuiklyView = KuiklyBaseView(this).apply {
layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 500f.toPxI())
}
kuiklyView?.onAttach("", "ViewExamplePage", mapOf())
maskView?.visibility = View.VISIBLE
rootView.addView(kuiklyView)
}
maskView?.setOnClickListener {
kuiklyView?.onDetach()
rootView.removeView(kuiklyView)
maskView.visibility = View.GONE
}
}
override fun onPause() {
super.onPause()
kuiklyView?.onPause()
}
override fun onResume() {
super.onResume()
kuiklyView?.onResume()
}
override fun onDestroy() {
super.onDestroy()
kuiklyView?.onDetach()
}
companion object {
@JvmStatic
fun start(context: Context) {
val starter = Intent(context, NativeMixKuiklyViewDemoActivity::class.java)
context.startActivity(starter)
}
}
}
```
## /androidApp/src/main/java/com/tencent/kuikly/android/demo/SkinIniFile.kt
```kt path="/androidApp/src/main/java/com/tencent/kuikly/android/demo/SkinIniFile.kt"
/*
* Tencent is pleased to support the open source community by making KuiklyUI
* available.
* Copyright (C) 2025 THL A29 Limited, a Tencent company. All rights reserved.
* Licensed under the License of KuiklyUI;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* https://github.com/Tencent-TDS/KuiklyUI/blob/main/LICENSE
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.tencent.kuikly.android.demo
import android.content.Context
import android.util.Log
import com.tencent.kuikly.android.demo.adapter.execOnSubThread
import java.io.IOException
import java.util.regex.Pattern
typealias OnLoadFinishListener = SkinIniFile.(Boolean) -> Unit
/**
* ini 文件解析类,解析 .ini 文件的内容,将文件的结点读取到一个 [Section] Map 中,
* 外部可通过 [load] 方法加载文件,通过 [get] 方法获取指定的结点
*/
class SkinIniFile(private val context: Context) {
private val sections: MutableMap = mutableMapOf()
data class Section(val name: String, val values: MutableMap = mutableMapOf())
@Volatile
private var hasLoaded = false
/**
* 加载 [assetsPath] 对应的 ini 文件到内存,这是个 IO 操作, 内部切换到了子线程处理
* 加载完成后,会调用 [onLoadFinishListener] 回调; 如果已经加载过不会重复加载
*/
fun load(assetsPath: String, onLoadFinishListener: OnLoadFinishListener? = null) {
execOnSubThread {
loadInternal(assetsPath, onLoadFinishListener)
}
}
private fun loadInternal(assetsPath: String, onLoadFinishListener: OnLoadFinishListener? = null) {
if (hasLoaded) {
onLoadFinishListener?.invoke(this, true)
return
}
synchronized(this) {
if (hasLoaded) {
onLoadFinishListener?.invoke(this, true)
return
}
try {
context.assets.open(assetsPath).use { inputStream ->
inputStream.bufferedReader().use { bufferReader ->
var curSectionName = ""
bufferReader.forEachLine {
val cleanLineStr = it.trim { char -> char <= ' ' }
curSectionName = readEachLine(cleanLineStr, curSectionName)
}
}
}
hasLoaded = true
onLoadFinishListener?.invoke(this@SkinIniFile, true)
} catch (e: IOException) {
Log.e(TAG, "加载 assets 失败 ${e.message}")
hasLoaded = false
onLoadFinishListener?.invoke(this@SkinIniFile, false)
}
}
}
/**
* 同步获取 [sectionName] 对应的结点中的 [sectionKey] 对应的值, 如果没有找到,返回 [defaultValue]
* 注意: 在 [load] 方法未完成之前,这个方法也会返回 [defaultValue], 可以在 [load] 方法完成后,再调用此方法,见 [OnLoadFinishListener]
*/
fun get(sectionName: String, sectionKey: String, defaultValue: String? = null): String? {
val section = sections[sectionName] ?: return defaultValue
return section.values[sectionKey] ?: defaultValue
}
/**
* 同步获取 [sectionName] 对应的结点中的所有的 key 列表, 如果没有找到,返回空列表
*/
fun getAllSectionsKey(sectionName: String): List {
return sections[sectionName]?.values?.keys?.toList() ?: emptyList()
}
private fun readEachLine(cleanLineStr: String, sectionName: String): String {
var curSectionName = sectionName
if (sectionPattern.matcher(cleanLineStr).matches()) {
curSectionName = cleanLineStr.substring(1, cleanLineStr.length - 1)
val section = sections[curSectionName] ?: Section(curSectionName)
sections[section.name] = section
} else {
val keyValue = cleanLineStr.split("=", limit = 2)
if (keyValue.size == 2) {
val section = sections[curSectionName] ?: Section(curSectionName)
section.values[keyValue[0]] = keyValue[1]
sections[section.name] = section
}
}
return curSectionName
}
companion object {
private val sectionPattern = Pattern.compile("^\\[.*\\]$")
private const val TAG = "SkinIniFile"
}
}
```
## /androidApp/src/main/java/com/tencent/kuikly/android/demo/adapter/KRAPNGViewAdapter.kt
```kt path="/androidApp/src/main/java/com/tencent/kuikly/android/demo/adapter/KRAPNGViewAdapter.kt"
/*
* Tencent is pleased to support the open source community by making KuiklyUI
* available.
* Copyright (C) 2025 THL A29 Limited, a Tencent company. All rights reserved.
* Licensed under the License of KuiklyUI;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* https://github.com/Tencent-TDS/KuiklyUI/blob/main/LICENSE
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.tencent.kuikly.android.demo.adapter
import android.content.Context
import android.graphics.drawable.Drawable
import android.view.View
import android.widget.ImageView
import androidx.appcompat.widget.AppCompatImageView
import androidx.vectordrawable.graphics.drawable.Animatable2Compat.AnimationCallback
import com.github.penfeizhou.animation.apng.APNGDrawable
import com.github.penfeizhou.animation.loader.FileLoader
import com.tencent.kuikly.core.render.android.adapter.IAPNGView
import com.tencent.kuikly.core.render.android.adapter.IAPNGViewAnimationListener
import com.tencent.kuikly.core.render.android.adapter.IKRAPNGViewAdapter
// 依赖库 最轻量apng库 :implementation("com.github.penfeizhou.android.animation:apng:2.25.0")
class KRAPNGViewAdapter : IKRAPNGViewAdapter {
override fun createAPNGView(context: Context): IAPNGView {
return KRAPNGImpView(context)
}
}
class KRAPNGImpView(context: Context) : AppCompatImageView(context), IAPNGView {
private var apngDrawable : APNGDrawable? = null
private var currentLoopCount = 0
override fun setFilePath(filePath: String) {
apngDrawable = APNGDrawable(FileLoader(filePath))
apngDrawable?.setAutoPlay(false)
setImageDrawable(apngDrawable)
}
override fun asView(): View {
return this
}
override fun setRepeatCount(count: Int) {
apngDrawable?.setLoopLimit(count)
}
override fun playAnimation() {
apngDrawable?.start()
}
override fun stopAnimation() {
apngDrawable?.stop()
}
override fun addAnimationListener(listener: IAPNGViewAnimationListener) {
apngDrawable?.registerAnimationCallback(object : AnimationCallback() {
override fun onAnimationEnd(drawable: Drawable?) {
super.onAnimationEnd(drawable)
listener.onAnimationEnd(this@KRAPNGImpView)
}
})
}
override fun removeAnimationListener(listener: IAPNGViewAnimationListener) {
apngDrawable?.clearAnimationCallbacks()
}
}
```
## /androidApp/src/main/java/com/tencent/kuikly/android/demo/adapter/KRColorParserAdapter.kt
```kt path="/androidApp/src/main/java/com/tencent/kuikly/android/demo/adapter/KRColorParserAdapter.kt"
/*
* Tencent is pleased to support the open source community by making KuiklyUI
* available.
* Copyright (C) 2025 THL A29 Limited, a Tencent company. All rights reserved.
* Licensed under the License of KuiklyUI;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* https://github.com/Tencent-TDS/KuiklyUI/blob/main/LICENSE
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.tencent.kuikly.android.demo.adapter
import android.content.Context
import android.graphics.Color
import com.tencent.kuikly.core.render.android.adapter.IKRColorParserAdapter
import com.tencent.kuikly.android.demo.SkinIniFile
class KRColorParserAdapter(context: Context) : IKRColorParserAdapter {
companion object {
private const val COLOR_UNIQUE_ID = "_color_unique_id_"
private const val COLOR_KUIKLY_TOKEN_PREFIX = "kuikly"
private const val COLOR_SECTION = "Color"
}
private val colorIniFile = SkinIniFile(context).apply {
load("configColor.ini")
}
override fun toColor(colorStr: String): Int? {
if (!colorStr.contains(COLOR_UNIQUE_ID) && !colorStr.contains(COLOR_KUIKLY_TOKEN_PREFIX)) {
return colorStr.toLongOrNull()?.toInt()
}
var token = colorStr
if (colorStr.contains(COLOR_UNIQUE_ID)) {
val index = colorStr.indexOf(COLOR_UNIQUE_ID)
token = colorStr.substring(0, index)
}
val colorHex = colorIniFile.get(
sectionName = COLOR_SECTION,
sectionKey = token,
defaultValue = null
)
?: throw IllegalArgumentException("找不到对应的颜色 token=$token,请检查 demo 中 configColor.ini 文件中是否存在改颜色,若不存在,手动更新一下")
return Color.parseColor(colorHex)
}
}
```
## /androidApp/src/main/java/com/tencent/kuikly/android/demo/adapter/KRFontAdapter.kt
```kt path="/androidApp/src/main/java/com/tencent/kuikly/android/demo/adapter/KRFontAdapter.kt"
/*
* Tencent is pleased to support the open source community by making KuiklyUI
* available.
* Copyright (C) 2025 THL A29 Limited, a Tencent company. All rights reserved.
* Licensed under the License of KuiklyUI;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* https://github.com/Tencent-TDS/KuiklyUI/blob/main/LICENSE
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.tencent.kuikly.android.demo.adapter
import android.graphics.Typeface
import com.tencent.kuikly.core.render.android.adapter.IKRFontAdapter
import com.tencent.kuikly.android.demo.KRApplication
/**
* Created by kam on 2022/10/20.
*/
object KRFontAdapter : IKRFontAdapter {
override fun getTypeface(fontFamily: String, result: (Typeface?) -> Unit) {
if (fontFamily.isEmpty()) {
result(null)
} else {
var tfe: Typeface? = null
when (fontFamily) {
"Satisfy-Regular" -> {
tfe = Typeface.createFromAsset(KRApplication.application.assets, "fonts/$fontFamily.ttf")
}
}
result(tfe)
}
}
}
```
## /androidApp/src/main/java/com/tencent/kuikly/android/demo/adapter/KRImageAdapter.kt
```kt path="/androidApp/src/main/java/com/tencent/kuikly/android/demo/adapter/KRImageAdapter.kt"
/*
* Tencent is pleased to support the open source community by making KuiklyUI
* available.
* Copyright (C) 2025 THL A29 Limited, a Tencent company. All rights reserved.
* Licensed under the License of KuiklyUI;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* https://github.com/Tencent-TDS/KuiklyUI/blob/main/LICENSE
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.tencent.kuikly.android.demo.adapter
import android.content.Context
import android.content.res.Resources
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.util.Base64
import android.util.Log
import android.widget.ImageView
import com.bumptech.glide.Glide
import com.bumptech.glide.RequestBuilder
import com.bumptech.glide.request.target.CustomTarget
import com.bumptech.glide.request.transition.Transition
import com.tencent.kuikly.android.demo.KRApplication
import com.tencent.kuikly.core.render.android.adapter.HRImageLoadOption
import com.tencent.kuikly.core.render.android.adapter.IKRImageAdapter
import kotlin.math.roundToInt
/**
* Created by kam on 2022/8/15.
*/
class KRImageAdapter(val context: Context) : IKRImageAdapter {
override fun fetchDrawable(
imageLoadOption: HRImageLoadOption,
callback: (drawable: Drawable?) -> Unit,
) {
if (imageLoadOption.isBase64()) {
loadFromBase64(imageLoadOption, callback)
} else if (imageLoadOption.isWebUrl() || imageLoadOption.isAssets() || imageLoadOption.isFile()) {
// http/assets/file 图片使用 glide 加载
requestImage(imageLoadOption, callback)
}
}
private fun requestImage(
imageLoadOption: HRImageLoadOption,
callback: (drawable: Drawable?) -> Unit,
) {
val src = if (imageLoadOption.isAssets()) {
val assetPath = imageLoadOption.src.substring(HRImageLoadOption.SCHEME_ASSETS.length)
"file:///android_asset/$assetPath"
} else {
imageLoadOption.src
}
val requestBuilder = if (src.endsWith(".gif")) {
Glide.with(KRApplication.application)
.asGif()
.load(src) as RequestBuilder
} else {
Glide.with(KRApplication.application)
.asDrawable()
.load(src)
}
if (imageLoadOption.needResize) {
requestBuilder.override(imageLoadOption.requestWidth, imageLoadOption.requestHeight)
when (imageLoadOption.scaleType) {
ImageView.ScaleType.CENTER_CROP -> requestBuilder.centerCrop()
ImageView.ScaleType.FIT_CENTER -> requestBuilder.fitCenter()
else -> {}
}
}
requestBuilder
.into(object : CustomTarget() {
override fun onLoadCleared(placeholder: Drawable?) {
callback.invoke(null)
}
override fun onLoadFailed(errorDrawable: Drawable?) {
super.onLoadFailed(errorDrawable)
callback.invoke(null)
}
override fun onResourceReady(
resource: Drawable,
transition: Transition?,
) {
callback.invoke(resource)
}
})
}
private fun loadFromBase64(
imageLoadOption: HRImageLoadOption,
callback: (drawable: Drawable?) -> Unit,
) {
execOnSubThread {
val options = BitmapFactory.Options()
options.inJustDecodeBounds = true
val bytes = Base64.decode(imageLoadOption.src.split(",")[1], Base64.DEFAULT)
BitmapFactory.decodeByteArray(bytes, 0, bytes.size, options)
try {
options.inPreferredConfig = Bitmap.Config.ARGB_8888
options.inJustDecodeBounds = false
try {
options.inSampleSize = calculateInSampleSize(options,
imageLoadOption.requestWidth,
imageLoadOption.requestHeight)
} catch (e: ArithmeticException) { // 偶现报除以0,可能是inSampleSize超过int的范围溢出了。这里catch兜底使用原始inSampleSize
Log.d("ECHRImageAdapter", "loadFromBase64: $e")
}
val bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size, options)
callback.invoke(BitmapDrawable(Resources.getSystem(), bitmap))
} catch (e: OutOfMemoryError) {
Log.d("ECHRImageAdapter", "oom happen: $e")
}
}
}
private fun calculateInSampleSize(
options: BitmapFactory.Options,
reqWidth: Int,
reqHeight: Int,
): Int {
return if (reqWidth != 0 && reqHeight != 0 && reqWidth != -1 && reqHeight != -1) {
var height = options.outHeight
var width = options.outWidth
var inSampleSize: Int
inSampleSize = 1
while (height > reqHeight && width > reqWidth) {
val heightRatio = (height.toFloat() / reqHeight.toFloat()).roundToInt()
val widthRatio = (width.toFloat() / reqWidth.toFloat()).roundToInt()
val ratio = if (heightRatio > widthRatio) heightRatio else widthRatio
if (ratio < 2) {
break
}
width = width shr 1
height = height shr 1
inSampleSize = inSampleSize shl 1
}
inSampleSize
} else {
1
}
}
}
```
## /androidApp/src/main/java/com/tencent/kuikly/android/demo/adapter/KRLogAdapter.kt
```kt path="/androidApp/src/main/java/com/tencent/kuikly/android/demo/adapter/KRLogAdapter.kt"
/*
* Tencent is pleased to support the open source community by making KuiklyUI
* available.
* Copyright (C) 2025 THL A29 Limited, a Tencent company. All rights reserved.
* Licensed under the License of KuiklyUI;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* https://github.com/Tencent-TDS/KuiklyUI/blob/main/LICENSE
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.tencent.kuikly.android.demo.adapter
import android.util.Log
import com.tencent.kuikly.core.render.android.adapter.IKRLogAdapter
/**
* Created by kam on 2023/3/27.
*/
object KRLogAdapter : IKRLogAdapter {
override val asyncLogEnable: Boolean
get() = true
override fun i(tag: String, msg: String) {
Log.i(tag, msg)
}
override fun d(tag: String, msg: String) {
Log.d(tag, msg)
}
override fun e(tag: String, msg: String) {
Log.e(tag, msg)
}
}
```
## /androidApp/src/main/java/com/tencent/kuikly/android/demo/adapter/KRRouterAdapter.kt
```kt path="/androidApp/src/main/java/com/tencent/kuikly/android/demo/adapter/KRRouterAdapter.kt"
/*
* Tencent is pleased to support the open source community by making KuiklyUI
* available.
* Copyright (C) 2025 THL A29 Limited, a Tencent company. All rights reserved.
* Licensed under the License of KuiklyUI;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* https://github.com/Tencent-TDS/KuiklyUI/blob/main/LICENSE
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.tencent.kuikly.android.demo.adapter
import android.app.Activity
import android.content.Context
import android.os.Bundle
import com.tencent.kuikly.android.demo.KuiklyRenderActivity
import com.tencent.kuikly.android.demo.NativeMixKuiklyViewDemoActivity
import com.tencent.kuikly.core.render.android.adapter.IKRRouterAdapter
import org.json.JSONObject
/**
* Created by kam on 2023/4/19.
*/
class KRRouterAdapter : IKRRouterAdapter {
override fun openPage(
context: Context,
pageName: String,
pageData: JSONObject,
) {
if (pageName == "NativeMixKuikly") {
NativeMixKuiklyViewDemoActivity.start(context)
} else {
KuiklyRenderActivity.start(context, pageName, pageData)
}
}
override fun closePage(context: Context) {
(context as? Activity)?.finish()
}
}
```
## /androidApp/src/main/java/com/tencent/kuikly/android/demo/adapter/KRTextPostProcessorAdapter.kt
```kt path="/androidApp/src/main/java/com/tencent/kuikly/android/demo/adapter/KRTextPostProcessorAdapter.kt"
/*
* Tencent is pleased to support the open source community by making KuiklyUI
* available.
* Copyright (C) 2025 THL A29 Limited, a Tencent company. All rights reserved.
* Licensed under the License of KuiklyUI;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* https://github.com/Tencent-TDS/KuiklyUI/blob/main/LICENSE
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.tencent.kuikly.android.demo.adapter
import android.util.Log
import com.tencent.kuikly.core.render.android.adapter.IKRTextPostProcessorAdapter
import com.tencent.kuikly.core.render.android.adapter.TextPostProcessorInput
import com.tencent.kuikly.core.render.android.adapter.TextPostProcessorOutput
import com.tencent.kuikly.core.render.android.css.ktx.toPxF
/**
* Created by kam on 2023/10/25.
*/
class KRTextPostProcessorAdapter : IKRTextPostProcessorAdapter {
override fun onTextPostProcess(inputParams: TextPostProcessorInput): TextPostProcessorOutput {
val fontSize = inputParams.textProps.fontSize.toPxF()
Log.d("CR7", "fontSize: $fontSize")
return TextPostProcessorOutput(inputParams.sourceText)
}
}
```
## /androidApp/src/main/java/com/tencent/kuikly/android/demo/adapter/KRThreadAdapter.kt
```kt path="/androidApp/src/main/java/com/tencent/kuikly/android/demo/adapter/KRThreadAdapter.kt"
/*
* Tencent is pleased to support the open source community by making KuiklyUI
* available.
* Copyright (C) 2025 THL A29 Limited, a Tencent company. All rights reserved.
* Licensed under the License of KuiklyUI;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* https://github.com/Tencent-TDS/KuiklyUI/blob/main/LICENSE
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.tencent.kuikly.android.demo.adapter
import com.tencent.kuikly.core.render.android.adapter.IKRThreadAdapter
import java.util.concurrent.Executors
/**
* Created by kam on 2023/4/21.
*/
class KRThreadAdapter : IKRThreadAdapter {
override fun executeOnSubThread(task: () -> Unit) {
execOnSubThread(task)
}
}
private val subThreadPoolExecutor by lazy {
Executors.newFixedThreadPool(2)
}
fun execOnSubThread(runnable: () -> Unit) {
subThreadPoolExecutor.execute(runnable)
}
```
## /androidApp/src/main/java/com/tencent/kuikly/android/demo/adapter/KRUncaughtExceptionHandlerAdapter.kt
```kt path="/androidApp/src/main/java/com/tencent/kuikly/android/demo/adapter/KRUncaughtExceptionHandlerAdapter.kt"
/*
* Tencent is pleased to support the open source community by making KuiklyUI
* available.
* Copyright (C) 2025 THL A29 Limited, a Tencent company. All rights reserved.
* Licensed under the License of KuiklyUI;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* https://github.com/Tencent-TDS/KuiklyUI/blob/main/LICENSE
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.tencent.kuikly.android.demo.adapter
import android.util.Log
import com.tencent.kuikly.core.render.android.adapter.IKRUncaughtExceptionHandlerAdapter
import com.tencent.kuikly.android.demo.BuildConfig
/**
* Created by kam on 2022/12/1.
*/
object KRUncaughtExceptionHandlerAdapter : IKRUncaughtExceptionHandlerAdapter {
private const val TAG = "KRExceptionHandler"
override fun uncaughtException(throwable: Throwable) {
if (BuildConfig.DEBUG) {
throw throwable
} else {
Log.e(TAG, "KR error: ${throwable.stackTraceToString()}")
}
}
}
```
## /androidApp/src/main/java/com/tencent/kuikly/android/demo/adapter/PAGViewAdapter.kt
```kt path="/androidApp/src/main/java/com/tencent/kuikly/android/demo/adapter/PAGViewAdapter.kt"
/*
* Tencent is pleased to support the open source community by making KuiklyUI
* available.
* Copyright (C) 2025 THL A29 Limited, a Tencent company. All rights reserved.
* Licensed under the License of KuiklyUI;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* https://github.com/Tencent-TDS/KuiklyUI/blob/main/LICENSE
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.tencent.kuikly.android.demo.adapter
import android.content.Context
import android.content.res.AssetManager
import android.view.View
import com.tencent.kuikly.core.render.android.adapter.IKRPAGViewAdapter
import com.tencent.kuikly.core.render.android.adapter.IPAGView
import com.tencent.kuikly.core.render.android.adapter.IPAGViewListener
import com.tencent.kuikly.core.render.android.expand.component.pag.KRPAGView
import org.libpag.PAGTextLayer
import org.libpag.PAGImageLayer
import org.libpag.PAGImage
import org.libpag.PAGView
/**
* Created by kam on 2023/5/17.
*/
class PAGViewAdapter : IKRPAGViewAdapter {
init {
try {
System.loadLibrary("pag")
} catch (e: Throwable) {
}
}
override fun createPAGView(context: Context): IPAGView {
return KRPagView(context)
}
}
class KRPagView(context: Context) : PAGView(context), IPAGView {
private var listeners = mutableListOf()
override fun asView(): View {
return this
}
override fun setFilePath(filePath: String) {
path = filePath
}
override fun setPAGViewRepeatCount(count: Int) {
setRepeatCount(count)
}
override fun playPAGView() {
play()
}
override fun stopPAGView() {
stop()
}
override fun replaceTextLayerContent(layerName: String, text: String) {
if (composition == null) return
val layers = composition.getLayersByName(layerName) ?: return
layers.filterIsInstance().forEach {
it.setText(text)
}
}
override fun replaceImageLayerContent(layerName: String, filePath: String) {
if (composition == null) return
val layers = composition.getLayersByName(layerName) ?: return
layers.filterIsInstance().forEach {
if (filePath.startsWith("assets://")) {
val assetManager = this.context.assets
val image = PAGImage.FromAssets(assetManager, filePath.substring(9))
it.setImage(image)
} else {
val image = PAGImage.FromPath(filePath)
it.setImage(image)
}
}
}
override fun addPAGViewListener(listener: IPAGViewListener) {
addListener(KRPagViewListener(listener).apply { listeners.add(this) })
}
override fun removePAGViewListener(listener: IPAGViewListener) {
for (pagViewListener in listeners) {
if (pagViewListener.listener == listener) {
removeListener(pagViewListener)
break
}
}
}
}
class KRPagViewListener(val listener: IPAGViewListener) : PAGView.PAGViewListener {
override fun onAnimationStart(view: PAGView) {
listener.onAnimationStart(view)
}
override fun onAnimationEnd(view: PAGView) {
listener.onAnimationEnd(view)
}
override fun onAnimationCancel(view: PAGView) {
listener.onAnimationCancel(view)
}
override fun onAnimationRepeat(view: PAGView) {
listener.onAnimationRepeat(view)
}
override fun onAnimationUpdate(view: PAGView) {
}
}
```
## /androidApp/src/main/java/com/tencent/kuikly/android/demo/adapter/VideoViewAdapter.kt
```kt path="/androidApp/src/main/java/com/tencent/kuikly/android/demo/adapter/VideoViewAdapter.kt"
/*
* Tencent is pleased to support the open source community by making KuiklyUI
* available.
* Copyright (C) 2025 THL A29 Limited, a Tencent company. All rights reserved.
* Licensed under the License of KuiklyUI;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* https://github.com/Tencent-TDS/KuiklyUI/blob/main/LICENSE
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.tencent.kuikly.android.demo.adapter
import android.content.Context
import android.net.Uri
import com.google.android.exoplayer2.ExoPlayer
import com.google.android.exoplayer2.MediaItem
import com.google.android.exoplayer2.PlaybackException
import com.google.android.exoplayer2.PlaybackParameters
import com.google.android.exoplayer2.Player.Listener
import com.google.android.exoplayer2.Player.STATE_BUFFERING
import com.google.android.exoplayer2.Player.STATE_ENDED
import com.google.android.exoplayer2.Player.STATE_IDLE
import com.google.android.exoplayer2.Player.STATE_READY
import com.google.android.exoplayer2.source.DefaultMediaSourceFactory
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector
import com.google.android.exoplayer2.ui.PlayerView
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory
import com.tencent.kuikly.core.render.android.adapter.IKRVideoView
import com.tencent.kuikly.core.render.android.adapter.IKRVideoViewAdapter
import com.tencent.kuikly.core.render.android.adapter.IKRVideoViewListener
import com.tencent.kuikly.core.render.android.expand.component.KRVideoPlayState
import com.tencent.kuikly.core.render.android.expand.component.KRVideoViewContentMode
/**
* Created by kam on 2023/6/17.
*/
class VideoViewAdapter : IKRVideoViewAdapter {
override fun createVideoView(context: Context, src: String, listener: IKRVideoViewListener): IKRVideoView {
return KuiklyVideoView(context, src, listener)
}
}
class KuiklyVideoView(context: Context, private val src: String, private val listener: IKRVideoViewListener) : PlayerView(context), IKRVideoView {
private val exoPlayer = ExoPlayer.Builder(context)
.setTrackSelector(DefaultTrackSelector())
.build()
init {
useController = false
val item = MediaItem.fromUri(Uri.parse(src))
exoPlayer.addMediaItem(item)
exoPlayer.prepare()
player = exoPlayer
exoPlayer.addListener(object : Listener {
override fun onPlaybackStateChanged(playbackState: Int) {
super.onPlaybackStateChanged(playbackState)
when(playbackState) {
STATE_ENDED -> {
listener.videoPlayStateDidChangedWithState(KRVideoPlayState.KRVideoPlayStatePlayEnd, mapOf())
}
STATE_BUFFERING -> {
listener.videoPlayStateDidChangedWithState(KRVideoPlayState.KRVideoPlayStateCaching, mapOf())
}
STATE_READY -> {
if (exoPlayer.playWhenReady) {
listener.videoPlayStateDidChangedWithState(KRVideoPlayState.KRVideoPlayStatePlaying, mapOf())
} else {
listener.videoPlayStateDidChangedWithState(KRVideoPlayState.KRVideoPlayStatePaused, mapOf())
}
}
}
}
override fun onPlayerError(error: PlaybackException) {
super.onPlayerError(error)
listener.videoPlayStateDidChangedWithState(KRVideoPlayState.KRVideoPlayStateFaild, mapOf())
}
override fun onRenderedFirstFrame() {
super.onRenderedFirstFrame()
listener.videoFirstFrameDidDisplay()
}
})
}
override fun preplay() {
}
override fun play() {
exoPlayer.play()
}
override fun pause() {
exoPlayer.pause()
}
override fun stop() {
exoPlayer.stop()
}
override fun setVideoContentMode(videoViewContentMode: KRVideoViewContentMode) {
}
override fun setMuted(muted: Boolean) {
exoPlayer.isDeviceMuted = muted
}
override fun setRate(rate: Float) {
exoPlayer.playbackParameters = PlaybackParameters(rate)
}
override fun seekToTime(seekToTimeMs: Long) {
exoPlayer.seekTo(seekToTimeMs)
}
override fun setProp(propKey: String, propValue: Any): Boolean {
// 处理自定义属性, 处理返回true,不处理返回false
return false
}
override fun call(method: String, params: String?) {
// 处理自定义方法调用
}
}
```
## /androidApp/src/main/java/com/tencent/kuikly/android/demo/module/KRBridgeModule.kt
```kt path="/androidApp/src/main/java/com/tencent/kuikly/android/demo/module/KRBridgeModule.kt"
/*
* Tencent is pleased to support the open source community by making KuiklyUI
* available.
* Copyright (C) 2025 THL A29 Limited, a Tencent company. All rights reserved.
* Licensed under the License of KuiklyUI;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* https://github.com/Tencent-TDS/KuiklyUI/blob/main/LICENSE
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.tencent.kuikly.android.demo.module
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.graphics.drawable.Drawable
import android.util.Log
import android.widget.Toast
import com.bumptech.glide.Glide
import com.bumptech.glide.request.target.CustomTarget
import com.bumptech.glide.request.transition.Transition
import com.tencent.kuikly.android.demo.KRApplication
import com.tencent.kuikly.core.render.android.adapter.KuiklyRenderLog
import com.tencent.kuikly.core.render.android.export.KuiklyRenderBaseModule
import com.tencent.kuikly.core.render.android.export.KuiklyRenderCallback
import org.json.JSONArray
import org.json.JSONObject
import java.io.BufferedReader
import java.io.File
import java.io.IOException
import java.io.InputStreamReader
import java.math.BigInteger
import java.security.MessageDigest
import java.text.SimpleDateFormat
import java.util.Date
/**
* Created by kam on 2022/8/11.
*/
class KRBridgeModule : KuiklyRenderBaseModule() {
private fun testArray(params: Array, callback: KuiklyRenderCallback?) : Any {
callback?.invoke(params)
return params
}
override fun call(method: String, params: Any?, callback: KuiklyRenderCallback?): Any? {
if (method == "testArray") {
return testArray(params as Array, callback)
}
return super.call(method, params, callback)
}
override fun call(method: String, params: String?, callback: KuiklyRenderCallback?): Any? {
return when (method) {
"showAlert" -> {
showAlert(params, callback)
}
"closePage" -> {
closePage(params)
}
"openPage" -> {
openPage(params)
}
"copyToPasteboard" -> {
copyToPasteboard(params)
}
"toast" -> {
toast(params)
}
"log" -> {
log(params)
}
"reportDT" -> {
reportDT(params)
}
"reportRealtime" -> {
reportRealtime(params)
}
"localServeTime" -> {
localServeTime(params, callback)
}
"currentTimestamp" -> {
currentTimestamp(params)
}
"dateFormatter" -> {
dateFormatter(params)
}
"getLocalImagePath" -> {
getLocalImagePath(params, callback)
}
"readAssetFile" -> {
readAssetPath(params, callback)
}
else -> callback?.invoke(mapOf(
"code" to -1,
"message" to "方法不存在"
))
}
}
private fun reportRealtime(params: String?) {
}
private fun reportDT(params: String?) {
}
private fun log(params: String?) {
if (params == null) {
return
}
val paramJSON = JSONObject(params)
Log.i("KuiklyRender", paramJSON.optString("content"))
}
private fun toast(params: String?) {
if (params == null) {
return
}
val paramJSON = JSONObject(params)
Toast.makeText(KRApplication.application,
paramJSON.optString("content"),
Toast.LENGTH_SHORT).show()
}
private fun copyToPasteboard(params: String?) {
if (params == null) {
return
}
val paramJSON = JSONObject(params)
(context?.getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager)?.also {
it.setPrimaryClip(ClipData.newPlainText(MODULE_NAME, paramJSON.optString("content")))
}
}
private fun openPage(params: String?) {
if (params == null) {
return
}
val ctx = context ?: return
val paramJSON = JSONObject(params)
val url = paramJSON.optString("url")
Log.i(MODULE_NAME, "open page url: $url")
}
private fun closePage(params: String?) {
activity?.finish()
}
private fun showAlert(params: String?, callback: KuiklyRenderCallback?) {
if (params == null) {
return
}
val paramJSON = JSONObject(params)
val titleText = paramJSON.optString("title")
val message = paramJSON.optString("message")
val buttons = paramJSON.optJSONArray("buttons") ?: JSONArray()
}
private fun localServeTime(params: String?, callback: KuiklyRenderCallback?) {
val time = (System.currentTimeMillis() / 1000.0)
callback?.invoke(mapOf(
"time" to time
))
}
private fun currentTimestamp(params: String?): String {
return (System.currentTimeMillis()).toString()
}
private fun dateFormatter(params: String?): String {
val paramJSONObject = JSONObject(params ?: "{}")
val data = Date(paramJSONObject.optLong("timeStamp"))
val format = SimpleDateFormat(paramJSONObject.optString("format"))
return format.format(data)
}
fun getMD5Hash(input: String): String {
val md = MessageDigest.getInstance("MD5")
val messageDigest = md.digest(input.toByteArray())
val no = BigInteger(1, messageDigest)
var hashText = no.toString(16)
// 补全前导零,以确保哈希值的长度为32位
while (hashText.length < 32) {
hashText = "0$hashText"
}
return hashText
}
private fun getLocalImagePath(params: String?, callback: KuiklyRenderCallback?) {
val paramJSONObject = JSONObject(params ?: "{}")
val imageUrl = paramJSONObject.optString("imageUrl")
Glide.with(context!!)
.downloadOnly()
.load(imageUrl)
.into(object : CustomTarget() {
override fun onResourceReady(
resource: File,
transition: Transition?
) {
val localFilePath = resource.absolutePath
callback?.invoke(mapOf(
"localPath" to localFilePath
))
}
override fun onLoadCleared(placeholder: Drawable?) {
callback?.invoke(mapOf())
}
override fun onLoadFailed(placeholder: Drawable?) {
callback?.invoke(mapOf())
}
})
}
private fun readAssetPath(params: String?, callback: KuiklyRenderCallback?) {
val startTimeMills1 = System.currentTimeMillis()
KuiklyRenderLog.d("WBDemo", "native readAssetPath startMills: $startTimeMills1")
Thread{
try {
val startTimeMills2 = System.currentTimeMillis()
val paramJSONObject = JSONObject(params ?: "{}")
val assetPath = paramJSONObject.optString("assetPath")
val inputStream = context?.assets?.open(assetPath)
val reader = BufferedReader(InputStreamReader(inputStream))
val stringBuilder = StringBuilder()
var line: String?
while (reader.readLine().also { line = it } != null) {
stringBuilder.append(line)
}
reader.close()
val content = stringBuilder.toString()
val cost1 = System.currentTimeMillis() - startTimeMills1
val cost2 = System.currentTimeMillis() - startTimeMills2
KuiklyRenderLog.d("WBDemo", "readAssetPath cost1: $cost1, cost2: $cost2")
callback?.invoke(mapOf(
"result" to content
))
} catch (e: IOException) {
e.printStackTrace()
callback?.invoke(mapOf(
"error" to e.message
))
}
}.start()
}
companion object {
const val MODULE_NAME = "HRBridgeModule"
}
}
private fun JSONObject.toMap(): Map {
val map = mutableMapOf()
val keys = keys()
while (keys.hasNext()) {
val key = keys.next()
when (val v = opt(key)) {
is JSONObject -> {
map[key] = v.toMap()
}
else -> {
v?.also {
map[key] = it
}
}
}
}
return map
}
```
## /androidApp/src/main/java/com/tencent/kuikly/android/demo/module/KRNotifyModule.kt
```kt path="/androidApp/src/main/java/com/tencent/kuikly/android/demo/module/KRNotifyModule.kt"
/*
* Tencent is pleased to support the open source community by making KuiklyUI
* available.
* Copyright (C) 2025 THL A29 Limited, a Tencent company. All rights reserved.
* Licensed under the License of KuiklyUI;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* https://github.com/Tencent-TDS/KuiklyUI/blob/main/LICENSE
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.tencent.kuikly.android.demo.module
import com.tencent.kuikly.core.render.android.export.KuiklyRenderBaseModule
/**
* Created by kam on 2022/10/29.
*/
class KRNotifyModule : KuiklyRenderBaseModule() {
companion object {
const val MODULE_NAME = "HRNotifyModule"
}
}
```
## /androidApp/src/main/java/com/tencent/kuikly/android/demo/module/KRShareModule.kt
```kt path="/androidApp/src/main/java/com/tencent/kuikly/android/demo/module/KRShareModule.kt"
/*
* Tencent is pleased to support the open source community by making KuiklyUI
* available.
* Copyright (C) 2025 THL A29 Limited, a Tencent company. All rights reserved.
* Licensed under the License of KuiklyUI;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* https://github.com/Tencent-TDS/KuiklyUI/blob/main/LICENSE
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.tencent.kuikly.android.demo.module
import com.tencent.kuikly.core.render.android.export.KuiklyRenderBaseModule
class KRShareModule : KuiklyRenderBaseModule() {
companion object {
const val MODULE_NAME = "HRShareModule"
}
}
```
## /androidApp/src/main/java/com/tencent/kuikly/android/demo/module/tdf/KRTDFTestModule.kt
```kt path="/androidApp/src/main/java/com/tencent/kuikly/android/demo/module/tdf/KRTDFTestModule.kt"
/*
* Tencent is pleased to support the open source community by making KuiklyUI
* available.
* Copyright (C) 2025 THL A29 Limited, a Tencent company. All rights reserved.
* Licensed under the License of KuiklyUI;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* https://github.com/Tencent-TDS/KuiklyUI/blob/main/LICENSE
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.tencent.kuikly.android.demo.module.tdf
import com.tencent.kuikly.core.render.android.adapter.KuiklyRenderLog
import com.tencent.tdf.annotation.TDFMethod
import com.tencent.tdf.annotation.TDFModule
import com.tencent.tdf.module.TDFBaseModule
import com.tencent.tdf.module.TDFModuleContext
import com.tencent.tdf.module.TDFModulePromise
import java.lang.RuntimeException
@TDFModule(name = "TDFTestModule")
class KRTDFTestModule(context: TDFModuleContext?) : TDFBaseModule(context) {
@TDFMethod
fun syncCall(
a1: String?,
a2: Int?,
a3: Double?,
a4: Boolean?,
a5: Float?,
a6: List?,
a7: Map?
) {
KuiklyRenderLog.d(MODULE_NAME, "syncCall: a1: $a1, a2: $a2, a3: $a3, a4: $a4, a5: $a5, a6: $a6, a7:$a7")
}
@TDFMethod
fun syncCallWithReturnValue(
a1: String?,
a2: Int?,
a3: Double?,
a4: Boolean?,
a5: Float?,
a6: List?,
a7: Map?
) : List{
return listOf(1, 2, 3 ,"", listOf(4, 5, 6), mapOf("a" to 1, "b" to 2))
}
@TDFMethod
fun asyncCall(isSuccess: Boolean, promise: TDFModulePromise) {
if (isSuccess) {
promise.resolve(mapOf("a" to 1, "b" to 2, "c" to listOf(4, 5, 6)))
} else {
promise.reject(RuntimeException("asyncCall error!!!"))
}
}
companion object {
const val MODULE_NAME = "HRBridgeModule"
}
}
```
## /androidApp/src/main/res/anim/slide_from_bottom.xml
```xml path="/androidApp/src/main/res/anim/slide_from_bottom.xml"
```
## /androidApp/src/main/res/anim/slide_out_to_top.xml
```xml path="/androidApp/src/main/res/anim/slide_out_to_top.xml"
```
## /androidApp/src/main/res/drawable/kuikly_icon.png
Binary file available at https://raw.githubusercontent.com/Tencent-TDS/KuiklyUI/refs/heads/main/androidApp/src/main/res/drawable/kuikly_icon.png
## /androidApp/src/main/res/drawable/launch_background.xml
```xml path="/androidApp/src/main/res/drawable/launch_background.xml"
```
## /androidApp/src/main/res/drawable/shap_btn_bg.xml
```xml path="/androidApp/src/main/res/drawable/shap_btn_bg.xml"
```
## /androidApp/src/main/res/drawable/shape_ed_bg.xml
```xml path="/androidApp/src/main/res/drawable/shape_ed_bg.xml"
```
## /androidApp/src/main/res/layout/activity_app_main.xml
```xml path="/androidApp/src/main/res/layout/activity_app_main.xml"
```
## /androidApp/src/main/res/layout/activity_hr.xml
```xml path="/androidApp/src/main/res/layout/activity_hr.xml"
```
## /androidApp/src/main/res/layout/activity_hr_native_item.xml
```xml path="/androidApp/src/main/res/layout/activity_hr_native_item.xml"
```
## /androidApp/src/main/res/layout/activity_hr_nd.xml
```xml path="/androidApp/src/main/res/layout/activity_hr_nd.xml"
```
## /androidApp/src/main/res/layout/activity_main.xml
```xml path="/androidApp/src/main/res/layout/activity_main.xml"
```
## /androidApp/src/main/res/layout/activity_native_mix_kuikly_view.xml
```xml path="/androidApp/src/main/res/layout/activity_native_mix_kuikly_view.xml"
```
## /androidApp/src/main/res/layout/activity_nv_2.xml
```xml path="/androidApp/src/main/res/layout/activity_nv_2.xml"
```
## /androidApp/src/main/res/values/colors.xml
```xml path="/androidApp/src/main/res/values/colors.xml"
#6200EE#3700B3#03DAC5
```
## /androidApp/src/main/res/values/dimens.xml
```xml path="/androidApp/src/main/res/values/dimens.xml"
16dp16dp
```
## /androidApp/src/main/res/values/strings.xml
```xml path="/androidApp/src/main/res/values/strings.xml"
KuiklyUIDemo
```
## /androidApp/src/main/res/values/styles.xml
```xml path="/androidApp/src/main/res/values/styles.xml"
```
## /androidApp/src/main/res/xml/network_security_config.xml
```xml path="/androidApp/src/main/res/xml/network_security_config.xml"
```
## /build.1.3.10.gradle.kts
```kts path="/build.1.3.10.gradle.kts"
buildscript {
repositories {
gradlePluginPortal()
google()
mavenCentral()
mavenLocal()
}
dependencies {
classpath(BuildPlugin.kotlin)
classpath(BuildPlugin.android)
}
}
allprojects {
repositories {
google()
mavenCentral()
}
}
```
## /build.1.4.20.gradle.kts
```kts path="/build.1.4.20.gradle.kts"
buildscript {
repositories {
gradlePluginPortal()
google()
mavenCentral()
}
dependencies {
classpath(BuildPlugin.kotlin)
classpath(BuildPlugin.android)
}
}
allprojects {
repositories {
google()
mavenCentral()
maven { url = uri("https://jitpack.io") }
}
}
```
## /build.1.5.31.gradle.kts
```kts path="/build.1.5.31.gradle.kts"
buildscript {
repositories {
gradlePluginPortal()
google()
mavenCentral()
mavenLocal()
}
dependencies {
classpath(BuildPlugin.kotlin)
classpath(BuildPlugin.android)
}
}
allprojects {
repositories {
google()
mavenCentral()
}
}
```
## /build.1.6.21.gradle.kts
```kts path="/build.1.6.21.gradle.kts"
buildscript {
repositories {
gradlePluginPortal()
google()
mavenCentral()
mavenLocal()
}
dependencies {
classpath(BuildPlugin.kotlin)
classpath(BuildPlugin.android)
}
}
allprojects {
repositories {
google()
mavenCentral()
}
}
```
## /build.1.8.21.gradle.kts
```kts path="/build.1.8.21.gradle.kts"
buildscript {
repositories {
gradlePluginPortal()
google()
mavenCentral()
mavenLocal()
}
dependencies {
classpath(BuildPlugin.kotlin)
classpath(BuildPlugin.android)
}
}
allprojects {
repositories {
google()
mavenCentral()
mavenLocal()
}
}
```
## /build.1.9.22.gradle.kts
```kts path="/build.1.9.22.gradle.kts"
plugins {
kotlin("multiplatform") version "1.9.22" apply false
id("com.android.application") version "7.4.2" apply false
id("com.android.library") version "7.4.2" apply false
}
buildscript {
repositories {
gradlePluginPortal()
google()
mavenCentral()
mavenLocal()
}
}
allprojects {
repositories {
google()
mavenCentral()
mavenLocal()
}
}
```
## /build.2.0.21.gradle.kts
```kts path="/build.2.0.21.gradle.kts"
plugins {
kotlin("multiplatform") version "2.0.21" apply false
id("com.android.application") version "7.4.2" apply false
id("com.android.library") version "7.4.2" apply false
}
buildscript {
repositories {
gradlePluginPortal()
google()
mavenCentral()
mavenLocal()
}
}
allprojects {
repositories {
google()
mavenCentral()
mavenLocal()
}
}
```
## /build.gradle.kts
```kts path="/build.gradle.kts"
plugins {
id("com.google.devtools.ksp") version(Version.getKSPVersion())
}
buildscript {
repositories {
gradlePluginPortal()
google()
mavenCentral()
mavenLocal()
}
dependencies {
classpath(BuildPlugin.kotlin)
classpath(BuildPlugin.android)
}
}
allprojects {
repositories {
google()
mavenCentral()
mavenLocal()
}
}
```
## /buildSrc/build.gradle.kts
```kts path="/buildSrc/build.gradle.kts"
import org.gradle.kotlin.dsl.`kotlin-dsl`
plugins {
`kotlin-dsl`
}
dependencies {
implementation("org.json:json:20231013")
}
repositories {
mavenCentral()
}
```
## /buildSrc/src/main/java/KuiklyKotlinBuildVar.kt
```kt path="/buildSrc/src/main/java/KuiklyKotlinBuildVar.kt"
/*
* Tencent is pleased to support the open source community by making KuiklyUI
* available.
* Copyright (C) 2025 THL A29 Limited, a Tencent company. All rights reserved.
* Licensed under the License of KuiklyUI;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* https://github.com/Tencent-TDS/KuiklyUI/blob/main/LICENSE
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import org.gradle.api.Project
import java.io.File
import java.io.FileInputStream
import java.util.Properties
/**
* Created by kam on 2022/6/28.
*/
object Dependencies {
val kotlinpoet by lazy {
"com.squareup:kotlinpoet:${Version.KOTLINPOET_VERSION}"
}
val kspApi by lazy {
"com.google.devtools.ksp:symbol-processing-api:${Version.getKSPVersion()}"
}
val androidxAppcompat by lazy {
"androidx.appcompat:appcompat:${Version.ANDROIDX_APPCOMPAT_VERSION}"
}
val material by lazy {
"com.google.android.material:material:${Version.MATERIAL_VERSION}"
}
val hippy by lazy {
"com.tencent.hippy:hippy-common:2.14.1"
}
val androidXCoreKtx by lazy {
"androidx.core:core-ktx:1.7.0"
}
val tdfCommon by lazy {
"com.tencent.tdf:tdf-common:1.0.1"
}
val kspSymbolProcessingGradlePlugin by lazy {
"com.google.devtools.ksp:symbol-processing-gradle-plugin:${Version.getKSPVersion()}"
}
}
object BuildPlugin {
val kotlin by lazy {
"org.jetbrains.kotlin:kotlin-gradle-plugin:${Version.getKotlinVersion()}"
}
val android by lazy {
"com.android.tools.build:gradle:${Version.getAGPVersion()}"
}
}
object Publishing {
const val kuiklyGroup = "com.tencent.kuikly"
}
object Output {
const val name = "nativevue2"
const val KEY_PACK_LOCAL_AAR_BUNDLE = "packLocalAarBundle"
}
object MavenConfig {
const val GROUP = "com.tencent.kuikly-open"
const val REPO_URL = "https://oss.sonatype.org/service/local/staging/deploy/maven2"
const val SNAPSHOT_REPO_URL = ""
const val RENDER_ANDROID_ARTIFACT_ID = "core-render-android"
private const val KEY_USER_NAME = "username"
private const val KEY_USER_PASSWORD = "password"
fun getUsername(project: Project) : String {
var username = ""
if (project.hasProperty(KEY_USER_NAME)) {
return project.property(KEY_USER_NAME) as String
}
if (username.isEmpty()) {
val propertiesFile = File(project.rootDir, "local.properties")
if (!propertiesFile.exists()) {
return ""
}
val prop = Properties().apply {
load(FileInputStream(propertiesFile))
}
username = prop.getProperty(KEY_USER_NAME) ?: ""
}
println("kuikly username is $username")
return username
}
fun getPassword(project: Project) : String {
var password = ""
if (project.hasProperty(KEY_USER_PASSWORD)) {
return project.property(KEY_USER_PASSWORD) as String
}
if (password.isEmpty()) {
val propertiesFile = File(project.rootDir, "local.properties")
if (!propertiesFile.exists()) {
return ""
}
val prop = Properties().apply {
load(FileInputStream(propertiesFile))
}
password = prop.getProperty(KEY_USER_PASSWORD) ?: ""
}
return password
}
fun getRepoUrl(version: String) : String {
if (version.endsWith("-SNAPSHOT")) {
return SNAPSHOT_REPO_URL
}
return REPO_URL
}
}
object Version {
const val KOTLINPOET_VERSION = "1.10.2"
const val ANDROIDX_APPCOMPAT_VERSION = "1.3.1"
const val MATERIAL_VERSION = "1.4.0"
const val SNAPSHOT_SUFFIX = "-SNAPSHOT"
const val DEFAULT_KUIKLY_VERSION = "1.1.0-beta5"
private const val DEFAULT_KOTLIN_VERSION = "1.7.20"
private const val DEFAULT_AGP_VERSION = "7.1.3"
private const val KEY_KUIKLY_VERSION = "KUIKLY_VERSION"
private const val KEY_KOTLIN_VERSION = "KUIKLY_KOTLIN_VERSION"
private const val KEY_AGP_VERSION = "KUIKLY_AGP_VERSION"
private const val KEY_CI_BUILD_NUM = "KUIKLY_CI_BUILD_NUM"
private const val KEY_RENDER_SUFFIX = "KUIKLY_RENDER_SUFFIX"
/**
* 获取 Kuikly 版本号
*/
fun getKuiklyVersion() : String {
var kuiklyVersion = System.getenv(KEY_KUIKLY_VERSION)
if (kuiklyVersion != null && kuiklyVersion.isNotEmpty()) {
return kuiklyVersion
}
kuiklyVersion = DEFAULT_KUIKLY_VERSION
// 流水席构建,版本号增加 snapshot
val buildNum = System.getenv(KEY_CI_BUILD_NUM)
if (buildNum != null && buildNum.isNotEmpty()) {
kuiklyVersion += ".$buildNum"
}
return "$kuiklyVersion$SNAPSHOT_SUFFIX"
}
/**
* 获取 kotlin 版本号
*/
fun getKotlinVersion() : String {
var kotlinVersion = System.getenv(KEY_KOTLIN_VERSION)
if (kotlinVersion == null || kotlinVersion.isEmpty()) {
kotlinVersion = DEFAULT_KOTLIN_VERSION
}
return kotlinVersion
}
/**
* 获取 AGP 版本号
*/
fun getAGPVersion() : String {
var agpVersion = System.getenv(KEY_AGP_VERSION)
if (agpVersion == null || agpVersion.isEmpty()) {
agpVersion = DEFAULT_AGP_VERSION
}
return agpVersion
}
/**
* 获取 render 后缀
*/
fun getRenderSuffix() : String {
var renderSuffix = System.getenv(KEY_RENDER_SUFFIX)
if (renderSuffix == null || renderSuffix.isEmpty()) {
renderSuffix = ""
}
return renderSuffix
}
/**
* 获取 core 版本号,版本号规则:${shortVersion}-${kotlinVersion}
* 适用于 core、core-ksp、core-annotation 三个库
*/
fun getCoreVersion() : String{
val kuiklyVersion = getKuiklyVersion()
var coreVersion = "${getKuiklyVersion()}-${getKotlinVersion()}"
if (kuiklyVersion.endsWith(SNAPSHOT_SUFFIX)) {
// -SNAPSHOT 前插入 kotlin 版本号
coreVersion = StringBuilder(kuiklyVersion).insert(
kuiklyVersion.indexOf(SNAPSHOT_SUFFIX), "-${getKotlinVersion()}").toString()
}
return coreVersion
}
/**
* 获取 render 版本号
*/
fun getRenderVersion() : String {
val renderVersion = getCoreVersion()
// 用于区分 androidx、support 版本
val renderSuffix = getRenderSuffix()
if (renderSuffix.isNotEmpty()) {
return "${renderVersion}-${renderSuffix}"
}
return renderVersion
}
fun getKSPVersion() : String {
return when (getKotlinVersion()) {
"1.6.21" -> "1.6.21-1.0.6"
"1.7.20" -> "1.7.20-1.0.7"
"1.8.21" -> "1.8.21-1.0.11"
"1.9.22" -> "1.9.22-1.0.16"
"2.0.21" -> "2.0.21-1.0.27"
else -> "${getKotlinVersion()}-1.0.7" // 默认版本
}
}
}
```
## /core-annotations/build.1.3.10.gradle
```gradle path="/core-annotations/build.1.3.10.gradle"
plugins {
id 'com.android.library'
id 'org.jetbrains.kotlin.android'
id("maven-publish")
}
group = MavenConfig.GROUP
version = Version.INSTANCE.getCoreVersion()
afterEvaluate {
publishing {
repositories {
String username = MavenConfig.INSTANCE.getUsername(project)
String password = MavenConfig.INSTANCE.getPassword(project)
if (!username.isEmpty() && !password.isEmpty()) {
maven {
credentials {
setUsername(username)
setPassword(password)
}
url = uri(MavenConfig.INSTANCE.getRepoUrl(version as String))
}
} else {
mavenLocal()
}
}
publications {
release(MavenPublication) {
groupId MavenConfig.GROUP
artifactId "core-annotations"
version Version.INSTANCE.getCoreVersion()
artifact(tasks.getByName("bundleReleaseAar"))
// artifact("$buildDir/outputs/aar/core-annotations-compat-release.aar")
}
// tasks.getByName("publish") {
// dependsOn("bundleReleaseAar")
//
// }
}
}
// tasks.named("publishReleasePublicationToMavenRepository").configure {
// dependsOn("bundleReleaseAar")
// }
}
android {
compileSdkVersion(30)
defaultConfig {
minSdkVersion(21)
targetSdkVersion(30)
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles "consumer-rules.pro"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7
}
// kotlinOptions {
// jvmTarget = '1.8'
// }
sourceSets {
main.java.srcDirs(
project.rootDir.absolutePath + "/core-annotations/src/commonMain/kotlin",
)
main.manifest.srcFile("src/androidMain/AndroidManifest.xml")
}
}
```
## /core-annotations/build.1.4.20.gradle
```gradle path="/core-annotations/build.1.4.20.gradle"
plugins {
id 'com.android.library'
id 'org.jetbrains.kotlin.android'
id("maven-publish")
}
group = MavenConfig.GROUP
version = Version.INSTANCE.getCoreVersion()
afterEvaluate {
publishing {
repositories {
String username = MavenConfig.INSTANCE.getUsername(project)
String password = MavenConfig.INSTANCE.getPassword(project)
if (!username.isEmpty() && !password.isEmpty()) {
maven {
credentials {
setUsername(username)
setPassword(password)
}
url = uri(MavenConfig.INSTANCE.getRepoUrl(version as String))
}
} else {
mavenLocal()
}
}
publications {
release(MavenPublication) {
groupId MavenConfig.GROUP
artifactId "core-annotations"
version Version.INSTANCE.getCoreVersion()
artifact(tasks.getByName("bundleReleaseAar"))
// artifact("$buildDir/outputs/aar/core-annotations-compat-release.aar")
}
// tasks.getByName("publish") {
// dependsOn("bundleReleaseAar")
//
// }
}
}
// tasks.named("publishReleasePublicationToMavenRepository").configure {
// dependsOn("bundleReleaseAar")
// }
}
android {
compileSdkVersion(30)
defaultConfig {
minSdkVersion(21)
targetSdkVersion(30)
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles "consumer-rules.pro"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7
}
// kotlinOptions {
// jvmTarget = '1.8'
// }
sourceSets {
main.java.srcDirs(
project.rootDir.absolutePath + "/core-annotations/src/commonMain/kotlin",
)
main.manifest.srcFile("src/androidMain/AndroidManifest.xml")
}
}
```
## /core-annotations/build.1.5.31.gradle.kts
```kts path="/core-annotations/build.1.5.31.gradle.kts"
plugins {
kotlin("multiplatform")
// kotlin("native.cocoapods")
id("com.android.library")
id("maven-publish")
}
group = MavenConfig.GROUP
version = Version.getCoreVersion()
publishing {
repositories {
val username = MavenConfig.getUsername(project)
val password = MavenConfig.getPassword(project)
if (username.isNotEmpty() && password.isNotEmpty()) {
maven {
credentials {
setUsername(username)
setPassword(password)
}
url = uri(MavenConfig.getRepoUrl(version as String))
}
} else {
mavenLocal()
}
}
}
kotlin {
jvm()
android()
sourceSets {
val commonMain by getting
}
}
android {
compileSdk = 32
sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
defaultConfig {
minSdk = 21
targetSdk = 32
}
}
```
## /core-annotations/build.1.6.21.gradle.kts
```kts path="/core-annotations/build.1.6.21.gradle.kts"
plugins {
kotlin("multiplatform")
kotlin("native.cocoapods")
id("com.android.library")
id("maven-publish")
}
group = MavenConfig.GROUP
version = Version.getCoreVersion()
publishing {
repositories {
val username = MavenConfig.getUsername(project)
val password = MavenConfig.getPassword(project)
if (username.isNotEmpty() && password.isNotEmpty()) {
maven {
credentials {
setUsername(username)
setPassword(password)
}
url = uri(MavenConfig.getRepoUrl(version as String))
}
} else {
mavenLocal()
}
}
}
kotlin {
jvm()
android()
iosArm64()
iosX64()
iosSimulatorArm64()
// cocoapods {
// summary = "Some description for the Shared Module"
// homepage = "Link to the Shared Module homepage"
// ios.deploymentTarget = "14.1"
//// framework {
//// baseName = "core-annotations"
//// }
// }
sourceSets {
val commonMain by getting
}
}
android {
compileSdk = 32
sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
defaultConfig {
minSdk = 21
targetSdk = 32
}
}
```
## /core-annotations/build.1.8.21.gradle.kts
```kts path="/core-annotations/build.1.8.21.gradle.kts"
plugins {
kotlin("multiplatform")
kotlin("native.cocoapods")
id("com.android.library")
id("maven-publish")
}
group = MavenConfig.GROUP
version = Version.getCoreVersion()
publishing {
repositories {
val username = MavenConfig.getUsername(project)
val password = MavenConfig.getPassword(project)
if (username.isNotEmpty() && password.isNotEmpty()) {
maven {
credentials {
setUsername(username)
setPassword(password)
}
url = uri(MavenConfig.getRepoUrl(version as String))
}
} else {
mavenLocal()
}
}
}
kotlin {
jvm()
android()
iosArm64()
iosX64()
iosSimulatorArm64()
// cocoapods {
// summary = "Some description for the Shared Module"
// homepage = "Link to the Shared Module homepage"
// ios.deploymentTarget = "14.1"
//// framework {
//// baseName = "core-annotations"
//// }
// }
sourceSets {
val commonMain by getting
}
}
android {
compileSdk = 32
sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
defaultConfig {
minSdk = 21
targetSdk = 32
}
}
```
## /core-annotations/build.1.9.22.gradle.kts
```kts path="/core-annotations/build.1.9.22.gradle.kts"
plugins {
kotlin("multiplatform")
kotlin("native.cocoapods")
id("com.android.library")
id("maven-publish")
}
group = MavenConfig.GROUP
version = Version.getCoreVersion()
publishing {
repositories {
val username = MavenConfig.getUsername(project)
val password = MavenConfig.getPassword(project)
if (username.isNotEmpty() && password.isNotEmpty()) {
maven {
credentials {
setUsername(username)
setPassword(password)
}
url = uri(MavenConfig.getRepoUrl(version as String))
}
} else {
mavenLocal()
}
}
}
kotlin {
jvm()
android()
iosArm64()
iosX64()
iosSimulatorArm64()
// cocoapods {
// summary = "Some description for the Shared Module"
// homepage = "Link to the Shared Module homepage"
// ios.deploymentTarget = "14.1"
//// framework {
//// baseName = "core-annotations"
//// }
// }
sourceSets {
val commonMain by getting
}
}
android {
compileSdk = 32
sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
defaultConfig {
minSdk = 21
targetSdk = 32
}
}
```
## /core-annotations/build.2.0.21.gradle.kts
```kts path="/core-annotations/build.2.0.21.gradle.kts"
plugins {
kotlin("multiplatform")
kotlin("native.cocoapods")
id("com.android.library")
id("maven-publish")
}
group = MavenConfig.GROUP
version = Version.getCoreVersion()
publishing {
repositories {
val username = MavenConfig.getUsername(project)
val password = MavenConfig.getPassword(project)
if (username.isNotEmpty() && password.isNotEmpty()) {
maven {
credentials {
setUsername(username)
setPassword(password)
}
url = uri(MavenConfig.getRepoUrl(version as String))
}
} else {
mavenLocal()
}
}
}
kotlin {
jvm()
android()
iosArm64()
iosX64()
iosSimulatorArm64()
// cocoapods {
// summary = "Some description for the Shared Module"
// homepage = "Link to the Shared Module homepage"
// ios.deploymentTarget = "14.1"
//// framework {
//// baseName = "core-annotations"
//// }
// }
sourceSets {
val commonMain by getting
}
}
android {
compileSdk = 32
sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
defaultConfig {
minSdk = 21
targetSdk = 32
}
}
```
## /core-annotations/build.gradle.kts
```kts path="/core-annotations/build.gradle.kts"
plugins {
kotlin("multiplatform")
kotlin("native.cocoapods")
id("com.android.library")
id("maven-publish")
}
group = MavenConfig.GROUP
version = Version.getCoreVersion()
publishing {
repositories {
val username = MavenConfig.getUsername(project)
val password = MavenConfig.getPassword(project)
if (username.isNotEmpty() && password.isNotEmpty()) {
maven {
credentials {
setUsername(username)
setPassword(password)
}
url = uri(MavenConfig.getRepoUrl(version as String))
}
} else {
mavenLocal()
}
}
}
kotlin {
jvm()
android()
iosArm64()
iosX64()
iosSimulatorArm64()
// cocoapods {
// summary = "Some description for the Shared Module"
// homepage = "Link to the Shared Module homepage"
// ios.deploymentTarget = "14.1"
//// framework {
//// baseName = "core-annotations"
//// }
// }
sourceSets {
val commonMain by getting
}
}
android {
compileSdk = 32
sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
defaultConfig {
minSdk = 21
targetSdk = 32
}
}
```
## /core-annotations/core-annotations.podspec
```podspec path="/core-annotations/core-annotations.podspec"
Pod::Spec.new do |spec|
spec.name = 'core-annotations'
spec.version = '1.0'
spec.homepage = 'Link to the Shared Module homepage'
spec.source = { :git => "Not Published", :tag => "Cocoapods/#{spec.name}/#{spec.version}" }
spec.authors = ''
spec.license = ''
spec.summary = 'Some description for the Shared Module'
spec.vendored_frameworks = "build/cocoapods/framework/core-annotations.framework"
spec.libraries = "c++"
spec.module_name = "#{spec.name}_umbrella"
spec.ios.deployment_target = '14.1'
spec.pod_target_xcconfig = {
'KOTLIN_PROJECT_PATH' => ':core-annotations',
'PRODUCT_MODULE_NAME' => 'core-annotations',
}
spec.script_phases = [
{
:name => 'Build core-annotations',
:execution_position => :before_compile,
:shell_path => '/bin/sh',
:script => <<-SCRIPT
if [ "YES" = "$COCOAPODS_SKIP_KOTLIN_BUILD" ]; then
echo "Skipping Gradle build task invocation due to COCOAPODS_SKIP_KOTLIN_BUILD environment variable set to \"YES\""
exit 0
fi
set -ev
REPO_ROOT="$PODS_TARGET_SRCROOT"
"$REPO_ROOT/../gradlew" -p "$REPO_ROOT" $KOTLIN_PROJECT_PATH:syncFramework \
-Pkotlin.native.cocoapods.platform=$PLATFORM_NAME \
-Pkotlin.native.cocoapods.archs="$ARCHS" \
-Pkotlin.native.cocoapods.configuration=$CONFIGURATION
SCRIPT
}
]
end
```
## /core-annotations/core_annotations.podspec
```podspec path="/core-annotations/core_annotations.podspec"
Pod::Spec.new do |spec|
spec.name = 'core_annotations'
spec.version = '1.1.0-beta5-1.7.20-SNAPSHOT'
spec.homepage = ''
spec.source = { :http=> ''}
spec.authors = ''
spec.license = ''
spec.summary = ''
spec.vendored_frameworks = 'build/cocoapods/framework/core_annotations.framework'
spec.libraries = 'c++'
spec.pod_target_xcconfig = {
'KOTLIN_PROJECT_PATH' => ':core-annotations',
'PRODUCT_MODULE_NAME' => 'core_annotations',
}
spec.script_phases = [
{
:name => 'Build core_annotations',
:execution_position => :before_compile,
:shell_path => '/bin/sh',
:script => <<-SCRIPT
if [ "YES" = "$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED" ]; then
echo "Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \"YES\""
exit 0
fi
set -ev
REPO_ROOT="$PODS_TARGET_SRCROOT"
"$REPO_ROOT/../gradlew" -p "$REPO_ROOT" $KOTLIN_PROJECT_PATH:syncFramework \
-Pkotlin.native.cocoapods.platform=$PLATFORM_NAME \
-Pkotlin.native.cocoapods.archs="$ARCHS" \
-Pkotlin.native.cocoapods.configuration="$CONFIGURATION"
SCRIPT
}
]
end
```
## /core-annotations/src/androidMain/AndroidManifest.xml
```xml path="/core-annotations/src/androidMain/AndroidManifest.xml"
```
## /core-annotations/src/commonMain/kotlin/com/tencent/kuikly/core/annotations/Page.kt
```kt path="/core-annotations/src/commonMain/kotlin/com/tencent/kuikly/core/annotations/Page.kt"
/*
* Tencent is pleased to support the open source community by making KuiklyUI
* available.
* Copyright (C) 2025 THL A29 Limited, a Tencent company. All rights reserved.
* Licensed under the License of KuiklyUI;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* https://github.com/Tencent-TDS/KuiklyUI/blob/main/LICENSE
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.tencent.kuikly.core.annotations
/**
* Kuikly页面注解
* @property name 页面名字
* @property supportInLocal 是否内置打包。true: 将所有true的页面打包成一个产物,然后可内置到宿主安装包,false: 不内置打包
* @property moduleId 页面属于哪个模块,可用于按模块维度将页面打包
re */
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.SOURCE)
annotation class Page(val name: String = "", val supportInLocal: Boolean = false, val moduleId: String = "")
```
## /core-kapt/.gitignore
```gitignore path="/core-kapt/.gitignore"
/build
```
## /core-kapt/build.gradle
```gradle path="/core-kapt/build.gradle"
plugins {
id 'java-library'
id 'org.jetbrains.kotlin.jvm'
id 'kotlin'
id 'kotlin-kapt'
id 'maven-publish'
}
group = MavenConfig.GROUP
version = Version.INSTANCE.getCoreVersion()
afterEvaluate {
publishing {
repositories {
String username = MavenConfig.INSTANCE.getUsername(project)
String password = MavenConfig.INSTANCE.getPassword(project)
if (!username.isEmpty() && !password.isEmpty()) {
maven {
credentials {
setUsername(username)
setPassword(password)
}
url = uri(MavenConfig.INSTANCE.getRepoUrl(version as String))
}
} else {
mavenLocal()
}
}
publications {
release(MavenPublication) {
groupId MavenConfig.GROUP
artifactId "core-kapt"
version Version.INSTANCE.getCoreVersion()
from components.getByName("java")
// artifact(tasks.getByName("bundleReleaseAar"))
// artifact("$buildDir/outputs/aar/core-render-android-compoat-release.aar")
}
}
}
// tasks.named("publishReleasePublicationToMavenRepository").configure {
// dependsOn("bundleReleaseAar")
// }
}
java {
sourceCompatibility = JavaVersion.VERSION_1_7
targetCompatibility = JavaVersion.VERSION_1_7
//
// kotlinOptions {
// jvmTarget = '1.8'
// }
sourceSets {
main.java.srcDirs(
projectDir.parentFile.absolutePath + "/core-annotations/src/commonMain/kotlin"
)
}
}
dependencies {
implementation "com.squareup:kotlinpoet:1.6.0"
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.11")
// implementation(project("compat:core-annotations-compat"))
}
```
## /core-kapt/src/main/java/com/tencent/kuikly/core/kapt/AndroidMultiEntryBuilder.kt
```kt path="/core-kapt/src/main/java/com/tencent/kuikly/core/kapt/AndroidMultiEntryBuilder.kt"
/*
* Tencent is pleased to support the open source community by making KuiklyUI
* available.
* Copyright (C) 2025 THL A29 Limited, a Tencent company. All rights reserved.
* Licensed under the License of KuiklyUI;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* https://github.com/Tencent-TDS/KuiklyUI/blob/main/LICENSE
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.tencent.kuikly.core.kapt
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.FileSpec
import com.squareup.kotlinpoet.FunSpec
import com.squareup.kotlinpoet.KModifier
import com.squareup.kotlinpoet.TypeSpec
class AndroidMultiEntryBuilder(private val subModules: String) : AndroidTargetEntryBuilder() {
override fun build(builder: FileSpec.Builder, pagesAnnotations: List) {
if (subModules.isEmpty()) {
if (pagesAnnotations.isEmpty()) {
return
}
builder.addType(
TypeSpec.objectBuilder(entryFileName() + "_" + pagesAnnotations.first().moduleId)
.addFunction(createSubModuleRegisterPagesFuncSpec(pagesAnnotations))
.build()
)
} else {
builder.addType(
TypeSpec.classBuilder(entryFileName())
.addSuperinterface(ClassName("com.tencent.kuikly.core", "IKuiklyCoreEntry"))
.addProperty(createHadRegisterNativeBridgeProperty())
.addProperty(createDelegateProperty())
.addFunction(createCallKtMethodFuncSpec())
.addFunction(createPagesFuncSpec())
.build()
)
}
}
private fun createSubModuleRegisterPagesFuncSpec(
pagesAnnotations: List
): FunSpec {
return FunSpec.builder("triggerRegisterPages")
.addRegisterPageRouteStatement(pagesAnnotations)
.build()
}
private fun createPagesFuncSpec(): FunSpec {
return FunSpec.builder("triggerRegisterPages")
.addModifiers(KModifier.OVERRIDE)
.addMultiStatement()
.build()
}
private fun FunSpec.Builder.addMultiStatement(): FunSpec.Builder {
subModules.split("&").forEach {
addStatement(entryFileName() + "_" + it + ".triggerRegisterPages()")
}
return this
}
override fun entryFileName(): String = "KuiklyCoreEntry"
override fun packageName(): String = "com.tencent.kuikly.core.android"
}
```
## /core-kapt/src/main/java/com/tencent/kuikly/core/kapt/AndroidTargetEntryBuilder.kt
```kt path="/core-kapt/src/main/java/com/tencent/kuikly/core/kapt/AndroidTargetEntryBuilder.kt"
/*
* Tencent is pleased to support the open source community by making KuiklyUI
* available.
* Copyright (C) 2025 THL A29 Limited, a Tencent company. All rights reserved.
* Licensed under the License of KuiklyUI;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* https://github.com/Tencent-TDS/KuiklyUI/blob/main/LICENSE
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.tencent.kuikly.core.kapt
import com.squareup.kotlinpoet.*
/**
* Created by kam on 2022/6/26.
*/
open class AndroidTargetEntryBuilder : KuiklyCoreAbsEntryBuilder() {
override fun build(
builder: FileSpec.Builder,
pagesAnnotations: List
) {
builder.addType(
TypeSpec.classBuilder(entryFileName())
.addSuperinterface(ClassName("com.tencent.kuikly.core", "IKuiklyCoreEntry"))
.addProperty(createHadRegisterNativeBridgeProperty())
.addProperty(createDelegateProperty())
.addFunction(createCallKtMethodFuncSpec())
.addFunction(createTriggerRegisterPagesFuncSpec(pagesAnnotations))
.build()
)
}
fun createDelegateProperty(): PropertySpec {
return PropertySpec.builder(
"delegate",
ClassName("com.tencent.kuikly.core.IKuiklyCoreEntry", INTERFACE_NAME_DELEGATE).copy(true)
)
.addModifiers(KModifier.OVERRIDE)
.mutable()
.initializer("null")
.build()
}
fun createHadRegisterNativeBridgeProperty(): PropertySpec {
return PropertySpec.builder(
"hadRegisterNativeBridge",
Boolean::class.asTypeName()
)
.addModifiers(KModifier.PRIVATE)
.mutable()
.initializer("false")
.build()
}
fun createCallKtMethodFuncSpec(): FunSpec {
return FunSpec.builder(FUNC_NAME_CALL_KT_METHOD)
.addParameters(createKtMethodParameters())
.addModifiers(KModifier.OVERRIDE)
.addStatement("if (!hadRegisterNativeBridge) {\n")
.addStatement("triggerRegisterPages()")
.addStatement(" hadRegisterNativeBridge = true\n" +
" val nativeBridge = NativeBridge()\n" +
" nativeBridge.delegate = object : NativeBridge.NativeBridgeDelegate {\n" +
" override fun callNative(\n" +
" methodId: Int,\n" +
" arg0: Any?,\n" +
" arg1: Any?,\n" +
" arg2: Any?,\n" +
" arg3: Any?,\n" +
" arg4: Any?,\n" +
" arg5: Any?\n" +
" ): Any? {\n" +
" return delegate?.callNative(methodId, arg0, arg1, arg2, arg3, arg4,\n" +
" arg5)\n" +
" }\n" +
" }\n" +
" BridgeManager.registerNativeBridge(arg0 as String, nativeBridge)\n" +
" }")
// .addRegisterPageRouteStatement(pagesAnnotations)
.addStatement("BridgeManager.callKotlinMethod(methodId, arg0, arg1, arg2, arg3, arg4, arg5)")
.build()
}
private fun createTriggerRegisterPagesFuncSpec(
pagesAnnotations: List
): FunSpec {
return FunSpec.builder("triggerRegisterPages")
.addModifiers(KModifier.OVERRIDE)
.addRegisterPageRouteStatement(pagesAnnotations)
.build()
}
private fun createDelegateTypeSpec(): TypeSpec {
return TypeSpec.interfaceBuilder(INTERFACE_NAME_DELEGATE)
.addFunction(
FunSpec.builder(FUNC_NAME_CALL_NATIVE)
.addParameters(
createKtMethodParameters()
)
.addModifiers(KModifier.ABSTRACT)
.returns(Any::class.asTypeName().copy(nullable = true))
.build()
)
.build()
}
private fun createCompanionObjectBuilder(pagesAnnotations: List): TypeSpec {
return TypeSpec.companionObjectBuilder()
.addFunction(
FunSpec.builder("registerAllPages")
.addStatement(
"if (!BridgeManager.isDidInit()) {\n" +
"BridgeManager.init()\n"
)
.addRegisterPageRouteStatement(pagesAnnotations)
.addStatement("}\n")
.build()
)
.build()
}
override fun entryFileName(): String = "KuiklyCoreEntry"
override fun packageName(): String = "com.tencent.kuikly.core.android"
companion object {
const val FUNC_NAME_CALL_NATIVE = "callNative"
private const val INTERFACE_NAME_DELEGATE = "Delegate"
}
}
```
## /core-kapt/src/main/java/com/tencent/kuikly/core/kapt/KuiklyCoreAbsEntryBuilder.kt
```kt path="/core-kapt/src/main/java/com/tencent/kuikly/core/kapt/KuiklyCoreAbsEntryBuilder.kt"
/*
* Tencent is pleased to support the open source community by making KuiklyUI
* available.
* Copyright (C) 2025 THL A29 Limited, a Tencent company. All rights reserved.
* Licensed under the License of KuiklyUI;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* https://github.com/Tencent-TDS/KuiklyUI/blob/main/LICENSE
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.tencent.kuikly.core.kapt
import com.squareup.kotlinpoet.*
/**
* Created by kam on 2022/6/25.
*/
abstract class KuiklyCoreAbsEntryBuilder {
fun build(
pagesAnnotations: List
): List {
val fileSpecs = mutableListOf()
val fileSpecBuilder = FileSpec.builder(packageName(), entryFileName())
for (comment in getCommonComments(pagesAnnotations)) {
fileSpecBuilder.addComment(comment)
}
fileSpecBuilder
.addComment("\nthis file is generating by ksp\n")
.addComment("please do not modified it!!!")
.addImport("com.tencent.kuikly.core.manager", "BridgeManager")
.addImport(PACKAGE_NAME_NVI, "NativeBridge")
build(fileSpecBuilder, pagesAnnotations)
fileSpecs.add(fileSpecBuilder.build())
return fileSpecs
}
abstract fun build(
builder: FileSpec.Builder,
pagesAnnotations: List
)
abstract fun entryFileName(): String
abstract fun packageName(): String
protected open fun getCommonComments(pagesAnnotations: List): List {
return emptyList()
}
protected fun createKtMethodParameters(): List {
return listOf(
ParameterSpec.builder("methodId", Int::class.asTypeName()).build(),
createNullableAnyParameterSpec("arg0"),
createNullableAnyParameterSpec("arg1"),
createNullableAnyParameterSpec("arg2"),
createNullableAnyParameterSpec("arg3"),
createNullableAnyParameterSpec("arg4"),
createNullableAnyParameterSpec("arg5")
)
}
protected fun createNullableAnyParameterSpec(name: String): ParameterSpec =
ParameterSpec.builder(name, Any::class.asTypeName().copy(nullable = true)).build()
fun FunSpec.Builder.addRegisterPageRouteStatement(pagesAnnotations: List): FunSpec.Builder {
pagesAnnotations.forEach { info ->
addStatement(createRegisterRouter(info.pageName, info.pageFullName))
}
return this
}
private fun createRegisterRouter(pageName: String, pageQualifiedName: String): String {
return "BridgeManager.registerPageRouter(\"$pageName\") {\n" +
"$pageQualifiedName()\n" +
"}"
}
companion object {
const val PACKAGE_NAME_NVI = "com.tencent.kuikly.core.nvi"
const val FUNC_NAME_CALL_KT_METHOD = "callKotlinMethod"
}
}
```
## /core-kapt/src/main/java/com/tencent/kuikly/core/kapt/KuiklyCoreProcessor.kt
```kt path="/core-kapt/src/main/java/com/tencent/kuikly/core/kapt/KuiklyCoreProcessor.kt"
/*
* Tencent is pleased to support the open source community by making KuiklyUI
* available.
* Copyright (C) 2025 THL A29 Limited, a Tencent company. All rights reserved.
* Licensed under the License of KuiklyUI;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* https://github.com/Tencent-TDS/KuiklyUI/blob/main/LICENSE
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.tencent.kuikly.core.kapt
import com.tencent.kuikly.core.annotations.Page
import javax.annotation.processing.AbstractProcessor
import javax.annotation.processing.Filer
import javax.annotation.processing.ProcessingEnvironment
import javax.annotation.processing.RoundEnvironment
import javax.lang.model.SourceVersion
import javax.lang.model.element.TypeElement
/**
* Created by kam on 2022/11/9.
*/
class KuiklyCoreProcessor : AbstractProcessor() {
private lateinit var mFiler: Filer
private lateinit var options: Map
override fun init(processingEnv: ProcessingEnvironment) {
super.init(processingEnv)
mFiler = processingEnv.filer
options = processingEnv.options
}
override fun process(
set: MutableSet,
roundEnvironment: RoundEnvironment
): Boolean {
if (set.isEmpty()) {
return true
}
val pageName = (options["pageName"] as? String) ?: ""
val packBundleByModuleId = (options["packBundleByModuleId"] as? String?) ?: ""
val packLocalAar = (options["packLocalAar"] as? String) ?: ""
println("kapt: pgName: $pageName")
println("kapt: packLocalAar$packLocalAar")
println("kapt: packBundleByModuleId: $packBundleByModuleId")
val pageAnnotations = roundEnvironment.getElementsAnnotatedWith(Page::class.java)
val pageInfoList = mutableListOf()
pageAnnotations.forEach {
val type = it as TypeElement
val pageInfo = type.toPageInfo()
if (packLocalAar.isNotEmpty()) {
val pair = parsePackLocalAarParams(packLocalAar)
val forcePackAarList = pair.first
val notPackAarList = pair.second
if (forcePackAarList.contains(pageInfo.pageName)) {
pageInfoList.add(pageInfo)
} else if (pageInfo.packLocal && !notPackAarList.contains(pageInfo.pageName)) {
pageInfoList.add(pageInfo)
}
} else if (packBundleByModuleId.isNotEmpty()) {
val moduleSet = packBundleByModuleId.split("&").toSet()
if (moduleSet.contains(pageInfo.moduleId)) {
pageInfoList.add(pageInfo)
}
} else if (pageName.isNotEmpty()) {
if (pageName == pageInfo.pageName) {
pageInfoList.add(pageInfo)
}
} else {
pageInfoList.add(type.toPageInfo())
}
}
getAndroidEntry().build(pageInfoList).forEach {
it.writeTo(mFiler)
}
return true
}
private fun getAndroidEntry(): KuiklyCoreAbsEntryBuilder {
val enableMultiModule = (options["enableMultiModule"] as? Boolean) ?: false
val subModules = options["subModules"] as? String ?: ""
return if (enableMultiModule) AndroidMultiEntryBuilder(subModules) else AndroidTargetEntryBuilder()
}
override fun getSupportedAnnotationTypes(): MutableSet {
return mutableSetOf(
Page::class.java.canonicalName
)
}
private fun TypeElement.toPageInfo(): PageInfo {
var name = getAnnotation(Page::class.java).name
if (name.isEmpty()) {
name = simpleName.toString()
}
return PageInfo(
name, qualifiedName.toString(), getAnnotation(Page::class.java).supportInLocal,
getAnnotation(Page::class.java).moduleId
)
}
private fun parsePackLocalAarParams(packLocalAar: String): Pair, List> {
val packLocalAarSpilt = packLocalAar.split(",")
return Pair(packLocalAarSpilt[0].split("|").toList(), packLocalAarSpilt[1].split("|").toList())
}
}
```
## /core-kapt/src/main/java/com/tencent/kuikly/core/kapt/PageInfo.kt
```kt path="/core-kapt/src/main/java/com/tencent/kuikly/core/kapt/PageInfo.kt"
/*
* Tencent is pleased to support the open source community by making KuiklyUI
* available.
* Copyright (C) 2025 THL A29 Limited, a Tencent company. All rights reserved.
* Licensed under the License of KuiklyUI;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* https://github.com/Tencent-TDS/KuiklyUI/blob/main/LICENSE
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.tencent.kuikly.core.kapt
/**
* Created by kam on 2022/11/9.
*/
data class PageInfo(
val pageName: String,
val pageFullName: String,
val packLocal: Boolean = false,
val moduleId: String = ""
)
```
## /core-kapt/src/main/resources/META-INF/services/javax.annotation.processing.Processor
```Processor path="/core-kapt/src/main/resources/META-INF/services/javax.annotation.processing.Processor"
com.tencent.kuikly.core.kapt.KuiklyCoreProcessor
```
## /core-ksp/build.1.5.31.gradle.kts
```kts path="/core-ksp/build.1.5.31.gradle.kts"
plugins {
kotlin("multiplatform")
id("maven-publish")
}
group = MavenConfig.GROUP
version = Version.getCoreVersion()
publishing {
repositories {
val username = MavenConfig.getUsername(project)
val password = MavenConfig.getPassword(project)
if (username.isNotEmpty() && password.isNotEmpty()) {
maven {
credentials {
setUsername(username)
setPassword(password)
}
url = uri(MavenConfig.getRepoUrl(version as String))
}
} else {
mavenLocal()
}
}
}
kotlin {
jvm()
sourceSets {
val jvmMain by getting {
dependencies {
implementation(Dependencies.kotlinpoet)
implementation("com.google.devtools.ksp:symbol-processing-api:1.5.31-1.0.0")
implementation(project(":core-annotations"))
}
kotlin.srcDir("src/main/kotlin")
kotlin.srcDir("src/main/kotlin/impl")
resources.srcDir("src/main/resources")
}
}
}
```
## /core-ksp/build.1.6.21.gradle.kts
```kts path="/core-ksp/build.1.6.21.gradle.kts"
plugins {
kotlin("multiplatform")
id("maven-publish")
}
group = MavenConfig.GROUP
version = Version.getCoreVersion()
publishing {
repositories {
val username = MavenConfig.getUsername(project)
val password = MavenConfig.getPassword(project)
if (username.isNotEmpty() && password.isNotEmpty()) {
maven {
credentials {
setUsername(username)
setPassword(password)
}
url = uri(MavenConfig.getRepoUrl(version as String))
}
} else {
mavenLocal()
}
}
}
kotlin {
jvm()
sourceSets {
val jvmMain by getting {
dependencies {
implementation(Dependencies.kotlinpoet)
implementation("com.google.devtools.ksp:symbol-processing-api:1.6.21-1.0.6")
implementation(project(":core-annotations"))
}
kotlin.srcDir("src/main/kotlin")
kotlin.srcDir("src/main/kotlin/impl")
resources.srcDir("src/main/resources")
}
}
}
```
## /core-ksp/build.1.8.21.gradle.kts
```kts path="/core-ksp/build.1.8.21.gradle.kts"
plugins {
kotlin("multiplatform")
id("maven-publish")
}
group = MavenConfig.GROUP
version = Version.getCoreVersion()
publishing {
repositories {
val username = MavenConfig.getUsername(project)
val password = MavenConfig.getPassword(project)
if (username.isNotEmpty() && password.isNotEmpty()) {
maven {
credentials {
setUsername(username)
setPassword(password)
}
url = uri(MavenConfig.getRepoUrl(version as String))
}
} else {
mavenLocal()
}
}
}
kotlin {
jvm()
sourceSets {
val jvmMain by getting {
dependencies {
implementation(Dependencies.kotlinpoet)
implementation("com.google.devtools.ksp:symbol-processing-api:1.8.21-1.0.11")
implementation(project(":core-annotations"))
}
kotlin.srcDir("src/main/kotlin")
kotlin.srcDir("src/main/kotlin/impl")
resources.srcDir("src/main/resources")
}
}
}
```
## /core-ksp/build.1.9.22.gradle.kts
```kts path="/core-ksp/build.1.9.22.gradle.kts"
plugins {
kotlin("multiplatform")
id("maven-publish")
}
group = MavenConfig.GROUP
version = Version.getCoreVersion()
publishing {
repositories {
val username = MavenConfig.getUsername(project)
val password = MavenConfig.getPassword(project)
if (username.isNotEmpty() && password.isNotEmpty()) {
maven {
credentials {
setUsername(username)
setPassword(password)
}
url = uri(MavenConfig.getRepoUrl(version as String))
}
} else {
mavenLocal()
}
}
}
kotlin {
jvm()
sourceSets {
val jvmMain by getting {
dependencies {
implementation(Dependencies.kotlinpoet)
implementation("com.google.devtools.ksp:symbol-processing-api:1.9.22-1.0.16")
implementation(project(":core-annotations"))
}
kotlin.srcDir("src/main/kotlin")
kotlin.srcDir("src/main/kotlin/impl")
resources.srcDir("src/main/resources")
}
}
}
```
## /core-ksp/build.2.0.21.gradle.kts
```kts path="/core-ksp/build.2.0.21.gradle.kts"
plugins {
kotlin("multiplatform")
id("maven-publish")
}
group = MavenConfig.GROUP
version = Version.getCoreVersion()
publishing {
repositories {
val username = MavenConfig.getUsername(project)
val password = MavenConfig.getPassword(project)
if (username.isNotEmpty() && password.isNotEmpty()) {
maven {
credentials {
setUsername(username)
setPassword(password)
}
url = uri(MavenConfig.getRepoUrl(version as String))
}
} else {
mavenLocal()
}
}
}
kotlin {
jvm()
sourceSets {
val jvmMain by getting {
dependencies {
implementation(Dependencies.kotlinpoet)
implementation("com.google.devtools.ksp:symbol-processing-api:2.0.21-1.0.27")
implementation(project(":core-annotations"))
}
kotlin.srcDir("src/main/kotlin")
kotlin.srcDir("src/main/kotlin/impl")
resources.srcDir("src/main/resources")
}
}
}
```
## /core-ksp/build.gradle.kts
```kts path="/core-ksp/build.gradle.kts"
plugins {
kotlin("multiplatform")
id("maven-publish")
}
group = MavenConfig.GROUP
version = Version.getCoreVersion()
publishing {
repositories {
val username = MavenConfig.getUsername(project)
val password = MavenConfig.getPassword(project)
if (username.isNotEmpty() && password.isNotEmpty()) {
maven {
credentials {
setUsername(username)
setPassword(password)
}
url = uri(MavenConfig.getRepoUrl(version as String))
}
} else {
mavenLocal()
}
}
}
kotlin {
jvm()
sourceSets {
val jvmMain by getting {
dependencies {
implementation(Dependencies.kotlinpoet)
implementation(Dependencies.kspApi)
implementation(project(":core-annotations"))
}
kotlin.srcDir("src/main/kotlin")
kotlin.srcDir("src/main/kotlin/impl")
resources.srcDir("src/main/resources")
}
}
}
```
## /core-ksp/core-ksp.podspec
```podspec path="/core-ksp/core-ksp.podspec"
Pod::Spec.new do |spec|
spec.name = 'core-ksp'
spec.version = '1.0'
spec.homepage = 'Link to the Shared Module homepage'
spec.source = { :git => "Not Published", :tag => "Cocoapods/#{spec.name}/#{spec.version}" }
spec.authors = ''
spec.license = ''
spec.summary = 'Some description for the Shared Module'
spec.vendored_frameworks = "build/cocoapods/framework/core-ksp.framework"
spec.libraries = "c++"
spec.module_name = "#{spec.name}_umbrella"
spec.ios.deployment_target = '14.1'
spec.pod_target_xcconfig = {
'KOTLIN_PROJECT_PATH' => ':core-ksp',
'PRODUCT_MODULE_NAME' => 'core-ksp',
}
spec.script_phases = [
{
:name => 'Build core-ksp',
:execution_position => :before_compile,
:shell_path => '/bin/sh',
:script => <<-SCRIPT
if [ "YES" = "$COCOAPODS_SKIP_KOTLIN_BUILD" ]; then
echo "Skipping Gradle build task invocation due to COCOAPODS_SKIP_KOTLIN_BUILD environment variable set to \"YES\""
exit 0
fi
set -ev
REPO_ROOT="$PODS_TARGET_SRCROOT"
"$REPO_ROOT/../gradlew" -p "$REPO_ROOT" $KOTLIN_PROJECT_PATH:syncFramework \
-Pkotlin.native.cocoapods.platform=$PLATFORM_NAME \
-Pkotlin.native.cocoapods.archs="$ARCHS" \
-Pkotlin.native.cocoapods.configuration=$CONFIGURATION
SCRIPT
}
]
end
```
## /core-ksp/src/main/kotlin/com/tencent/kuikly/core/ksp/Extensions.kt
```kt path="/core-ksp/src/main/kotlin/com/tencent/kuikly/core/ksp/Extensions.kt"
/*
* Tencent is pleased to support the open source community by making KuiklyUI
* available.
* Copyright (C) 2025 THL A29 Limited, a Tencent company. All rights reserved.
* Licensed under the License of KuiklyUI;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* https://github.com/Tencent-TDS/KuiklyUI/blob/main/LICENSE
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.tencent.kuikly.core.ksp
import com.google.devtools.ksp.symbol.KSClassDeclaration
import impl.PageInfo
/**
* Created by kam on 2022/6/25.
*/
fun String.iosFamily(): Boolean {
return contains("ios")
}
fun String.androidJVMFamily(): Boolean {
return contains("android")
}
private fun KSClassDeclaration.pageAnnotateValue(): String {
var name = ""
annotations.toList()[0].arguments.forEach {
if (it.name?.asString() == "name") {
name = (it.value as? String) ?: ""
}
}
if (name.isEmpty()) {
name = simpleName.asString()
}
return name
}
private fun KSClassDeclaration.supportInLocalAnnotateValue(): Boolean {
val pageAnnotation = annotations.toList()[0]
pageAnnotation.arguments.forEach {
if (it.name?.asString() == "supportInLocal") {
return (it.value as? Boolean) ?: false
}
}
return false
}
@Synchronized
private fun KSClassDeclaration.moduleIdAnnotateValue(): String {
annotations.toList()[0].arguments.forEach {
if (it.name?.asString() == "moduleId") {
return (it.value as? String) ?: ""
}
}
return ""
}
fun KSClassDeclaration.toPageInfo(): PageInfo {
return PageInfo(
pageAnnotateValue(),
qualifiedName!!.asString(),
supportInLocalAnnotateValue(),
moduleIdAnnotateValue()
)
}
fun String.sourceSetBelow(startDirectoryName: String): String =
substringAfter("/$startDirectoryName/").substringBefore("/kotlin/").substringAfterLast('/')
```
## /core-ksp/src/main/kotlin/com/tencent/kuikly/core/ksp/KuiklyCoreProcessorProvider.kt
```kt path="/core-ksp/src/main/kotlin/com/tencent/kuikly/core/ksp/KuiklyCoreProcessorProvider.kt"
/*
* Tencent is pleased to support the open source community by making KuiklyUI
* available.
* Copyright (C) 2025 THL A29 Limited, a Tencent company. All rights reserved.
* Licensed under the License of KuiklyUI;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* https://github.com/Tencent-TDS/KuiklyUI/blob/main/LICENSE
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.tencent.kuikly.core.ksp
import com.google.devtools.ksp.processing.*
import com.google.devtools.ksp.symbol.KSAnnotated
import com.google.devtools.ksp.symbol.KSClassDeclaration
import com.squareup.kotlinpoet.FileSpec
import com.tencent.kuikly.core.annotations.Page
import impl.AndroidTargetEntryBuilder
import impl.KuiklyCoreAbsEntryBuilder
import impl.IOSTargetEntryBuilder
import impl.PageInfo
import impl.submodule.AndroidMultiEntryBuilder
import impl.submodule.IOSMultiTargetEntryBuilder
/**
* Created by kam on 2022/6/19.
*/
class KuiklyCoreProcessorProvider : SymbolProcessorProvider {
override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor {
return CoreProcessor(environment.codeGenerator, environment.logger, environment.options)
}
}
class CoreProcessor(
private val codeGenerator: CodeGenerator,
private val logger: KSPLogger,
private val option: Map,
) :
SymbolProcessor {
private var isInitialInvocation = true
override fun process(resolver: Resolver): List {
val newFiles = resolver.getNewFiles()
if (!isInitialInvocation || newFiles.firstOrNull() == null) {
// * A subsequent invocation is for processing generated files. We do not need to process these.
// * If there are no new files to process, we avoid generating an output file, as this would break
// incremental compilation.
// TODO: This could be omitted if file generation were not required to discover the output source set.
return emptyList()
}
isInitialInvocation = false
codeGenerator.createNewFile(
dependencies = Dependencies(aggregating = true),
packageName = "",
fileName = "KuiklyCoreEntry",
extensionName = "kt"
).use { output ->
// TODO: This hack to discover the output source set should be replaced with a better solution.
getEntryBuilder().also {
buildEntryFile(resolver, it).forEach { fileSpec ->
output.write(fileSpec.toString().toByteArray())
}
}
}
return emptyList()
}
private fun buildEntryFile(
resolver: Resolver,
absEntryBuilder: KuiklyCoreAbsEntryBuilder,
): List {
val pageName = option["pageName"] ?: ""
val packLocalAarBundle = option["packLocalAarBundle"] ?: ""
val packBundleByModuleId = option["packBundleByModuleId"] ?: ""
val pageClassDeclarations = mutableListOf()
val moduleSet = packBundleByModuleId.split("&").toSet()
resolver.getSymbolsWithAnnotation(Page::class.qualifiedName!!)
.filterIsInstance()
.forEach { classDeclaration ->
val pageInfo = classDeclaration.toPageInfo()
if (packLocalAarBundle == "1") {
if (pageInfo.packLocal) { // 只打包支持内置的page
pageClassDeclarations.add(pageInfo)
}
} else if (pageName.isNotEmpty()) { // 全部page打成一个包
if (pageName == pageInfo.pageName) {
pageClassDeclarations.add(pageInfo)
}
} else if (packBundleByModuleId.isNotEmpty()) { // 按照moduleId打包bundle
if (moduleSet.contains(pageInfo.moduleId)) {
pageClassDeclarations.add(pageInfo)
}
} else {
pageClassDeclarations.add(pageInfo)
}
}
return absEntryBuilder.build(pageClassDeclarations)
}
private fun getEntryBuilder(): KuiklyCoreAbsEntryBuilder{
val enableMultiModule = option["enableMultiModule"]?.toBoolean() ?: false
val isMainModule = option["isMainModule"]?.toBoolean() ?: false
val subModules = option["subModules"] ?: ""
val moduleId = option["moduleId"] ?: ""
val outputSourceSet =
codeGenerator.generatedFile.first().toString().sourceSetBelow("ksp")
return when {
outputSourceSet.androidJVMFamily() -> {
if (enableMultiModule) {
AndroidMultiEntryBuilder(isMainModule, subModules, moduleId)
} else {
AndroidTargetEntryBuilder()
}
}
outputSourceSet.iosFamily() -> {
if (enableMultiModule) {
IOSMultiTargetEntryBuilder(isMainModule, subModules, moduleId)
} else {
IOSTargetEntryBuilder()
}
}
else -> {
AndroidTargetEntryBuilder()
}
}
}
}
```
## /core-ksp/src/main/kotlin/impl/AndroidTargetEntryBuilder.kt
```kt path="/core-ksp/src/main/kotlin/impl/AndroidTargetEntryBuilder.kt"
/*
* Tencent is pleased to support the open source community by making KuiklyUI
* available.
* Copyright (C) 2025 THL A29 Limited, a Tencent company. All rights reserved.
* Licensed under the License of KuiklyUI;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* https://github.com/Tencent-TDS/KuiklyUI/blob/main/LICENSE
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package impl
import com.squareup.kotlinpoet.*
/**
* Created by kam on 2022/6/26.
*/
open class AndroidTargetEntryBuilder : KuiklyCoreAbsEntryBuilder() {
override fun build(
builder: FileSpec.Builder,
pagesAnnotations: List,
) {
builder.addType(
TypeSpec.classBuilder(entryFileName())
.addSuperinterface(ClassName("com.tencent.kuikly.core", "IKuiklyCoreEntry"))
.addProperty(createHadRegisterNativeBridgeProperty())
.addProperty(createDelegateProperty())
.addFunction(createCallKtMethodFuncSpec())
.addFunction(createTriggerRegisterPagesFuncSpec(pagesAnnotations))
.build()
)
}
internal fun createDelegateProperty(): PropertySpec {
return PropertySpec.builder(
"delegate",
ClassName("com.tencent.kuikly.core.IKuiklyCoreEntry", INTERFACE_NAME_DELEGATE).copy(true)
)
.addModifiers(KModifier.OVERRIDE)
.mutable()
.initializer("null")
.build()
}
internal fun createHadRegisterNativeBridgeProperty(): PropertySpec {
return PropertySpec.builder(
"hadRegisterNativeBridge",
Boolean::class.asTypeName()
)
.addModifiers(KModifier.PRIVATE)
.mutable()
.initializer("false")
.build()
}
internal fun createCallKtMethodFuncSpec(): FunSpec {
return FunSpec.builder(FUNC_NAME_CALL_KT_METHOD)
.addParameters(createKtMethodParameters())
.addModifiers(KModifier.OVERRIDE)
.addStatement("if (!hadRegisterNativeBridge) {\n")
.addStatement("triggerRegisterPages()\n")
.addStatement(" hadRegisterNativeBridge = true\n" +
" val nativeBridge = NativeBridge()\n" +
" nativeBridge.delegate = object : NativeBridge.NativeBridgeDelegate {\n" +
" override fun callNative(\n" +
" methodId: Int,\n" +
" arg0: Any?,\n" +
" arg1: Any?,\n" +
" arg2: Any?,\n" +
" arg3: Any?,\n" +
" arg4: Any?,\n" +
" arg5: Any?\n" +
" ): Any? {\n" +
" return delegate?.callNative(methodId, arg0, arg1, arg2, arg3, arg4,\n" +
" arg5)\n" +
" }\n" +
" }\n" +
" BridgeManager.registerNativeBridge(arg0 as String, nativeBridge)\n" +
" }")
// .addRegisterPageRouteStatement(pagesAnnotations)
.addStatement("BridgeManager.callKotlinMethod(methodId, arg0, arg1, arg2, arg3, arg4, arg5)")
.build()
}
private fun createTriggerRegisterPagesFuncSpec(
pagesAnnotations: List,
) : FunSpec {
return FunSpec.builder("triggerRegisterPages")
.addModifiers(KModifier.OVERRIDE)
.addRegisterPageRouteStatement(pagesAnnotations)
.build()
}
private fun createDelegateTypeSpec(): TypeSpec {
return TypeSpec.interfaceBuilder(INTERFACE_NAME_DELEGATE)
.addFunction(
FunSpec.builder(FUNC_NAME_CALL_NATIVE)
.addParameters(
createKtMethodParameters()
)
.addModifiers(KModifier.ABSTRACT)
.returns(Any::class.asTypeName().copy(nullable = true))
.build()
)
.build()
}
private fun createCompanionObjectBuilder(pagesAnnotations: List): TypeSpec {
return TypeSpec.companionObjectBuilder()
.addFunction(
FunSpec.builder("registerAllPages")
.addStatement(
"if (!BridgeManager.isDidInit()) {\n" +
"BridgeManager.init()\n"
)
.addRegisterPageRouteStatement(pagesAnnotations)
.addStatement("}\n")
.build()
)
.build()
}
override fun entryFileName(): String = "KuiklyCoreEntry"
override fun packageName(): String {
return "com.tencent.kuikly.core.android"
}
companion object {
const val FUNC_NAME_CALL_NATIVE = "callNative"
private const val INTERFACE_NAME_DELEGATE = "Delegate"
}
}
```
## /core-ksp/src/main/kotlin/impl/IOSTargetEntryBuilder.kt
```kt path="/core-ksp/src/main/kotlin/impl/IOSTargetEntryBuilder.kt"
/*
* Tencent is pleased to support the open source community by making KuiklyUI
* available.
* Copyright (C) 2025 THL A29 Limited, a Tencent company. All rights reserved.
* Licensed under the License of KuiklyUI;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* https://github.com/Tencent-TDS/KuiklyUI/blob/main/LICENSE
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package impl
import com.squareup.kotlinpoet.*
/**
* Created by kam on 2022/6/25.
*/
open class IOSTargetEntryBuilder : KuiklyCoreAbsEntryBuilder() {
override fun build(builder: FileSpec.Builder, pagesAnnotations: List) {
builder.addType(
TypeSpec.classBuilder(entryFileName())
.addProperty(createDelegateProperty())
.addProperty(createHadRegisterNativeBridgeProperty())
.addFunction(createCallKtMethodFuncSpec(pagesAnnotations))
.addType(createDelegateTypeSpec())
.addType(createCompanionObject(pagesAnnotations))
.build()
)
}
override fun entryFileName(): String {
return "KuiklyCoreEntry"
}
override fun packageName(): String {
return ""
}
fun createDelegateProperty(): PropertySpec {
return PropertySpec.builder(
VAR_NAME_DELEGATE,
ClassName("", HR_CORE_ENTRY_DELEGATE).copy(true)
)
.mutable()
.initializer("null")
.build()
}
fun createHadRegisterNativeBridgeProperty(): PropertySpec {
return PropertySpec.builder(
"hadRegisterNativeBridge",
Boolean::class.asTypeName()
)
.addModifiers(KModifier.PRIVATE)
.mutable()
.initializer("false")
.build()
}
fun createCallKtMethodFuncSpec(
pagesAnnotations: List,
): FunSpec {
return FunSpec.builder(FUNC_NAME_CALL_KT_METHOD)
.addParameters(createKtMethodParameters())
.addStatement("try {")
.addStatement(
"if (!BridgeManager.isDidInit()) {\n" +
"BridgeManager.init()\n"
)
.addStatement("$METHOD_NAME_TRIGGER_REGISTER_PAGES()")
.addStatement("}\n")
.addStatement(" if (!hadRegisterNativeBridge) {\n" +
" hadRegisterNativeBridge = true\n" +
" val nativeBridge = NativeBridge()\n" +
" nativeBridge.iosNativeBridgeDelegate = object : NativeBridge.IOSNativeBridgeDelegate {\n" +
" override fun callNative(\n" +
" methodId: Int,\n" +
" arg0: Any?,\n" +
" arg1: Any?,\n" +
" arg2: Any?,\n" +
" arg3: Any?,\n" +
" arg4: Any?,\n" +
" arg5: Any?\n" +
" ): Any? {\n" +
" return hrCoreDelegate?.callNative(methodId, arg0, arg1, arg2, arg3, arg4, arg5)\n" +
" }\n" +
" }\n" +
" BridgeManager.registerNativeBridge(arg0 as String, nativeBridge)\n" +
" }")
.addStatement("BridgeManager.callKotlinMethod(methodId, arg0, arg1, arg2, arg3, arg4, arg5)")
.addStatement("}")
.addStatement("catch(t: Throwable) {")
.addStatement("BridgeManager.callExceptionMethod(t.stackTraceToString())")
.addStatement("}")
.build()
}
fun createDelegateTypeSpec(): TypeSpec {
return TypeSpec.interfaceBuilder(HR_CORE_ENTRY_DELEGATE)
.addFunction(
FunSpec.builder(AndroidTargetEntryBuilder.FUNC_NAME_CALL_NATIVE)
.addParameters(
createKtMethodParameters()
)
.addModifiers(KModifier.ABSTRACT)
.returns(Any::class.asTypeName().copy(nullable = true))
.build()
)
.build()
}
private fun createCompanionObject(pagesAnnotations: List): TypeSpec {
return TypeSpec.companionObjectBuilder()
.addProperty(
PropertySpec.builder(PROP_NAME_HAD_REGISTER_PAGES, Boolean::class.asTypeName())
.mutable(true)
.addModifiers(KModifier.PRIVATE)
.initializer("false")
.build()
)
.addFunction(
FunSpec.builder(METHOD_NAME_PAGE_EXIST)
.addParameter(ParameterSpec.builder(PARAM_NAME_PAGE_NAME, String::class.asTypeName()).build())
.addStatement("$METHOD_NAME_TRIGGER_REGISTER_PAGES()")
.addStatement("return BridgeManager.$METHOD_NAME_PAGE_EXIST($PARAM_NAME_PAGE_NAME)")
.returns(Boolean::class.asTypeName())
.build()
)
.addFunction(
FunSpec.builder(METHOD_NAME_TRIGGER_REGISTER_PAGES)
.addModifiers(KModifier.PRIVATE)
.addStatement("if(!$PROP_NAME_HAD_REGISTER_PAGES) {")
.addRegisterPageRouteStatement(pagesAnnotations)
.addStatement("$PROP_NAME_HAD_REGISTER_PAGES=true")
.addStatement("}")
.build()
)
.build()
}
companion object {
private const val HR_CORE_ENTRY_DELEGATE = "Delegate"
private const val VAR_NAME_DELEGATE = "hrCoreDelegate"
internal const val METHOD_NAME_TRIGGER_REGISTER_PAGES = "triggerRegisterPages"
internal const val METHOD_NAME_PAGE_EXIST = "isPageExist"
internal const val PROP_NAME_HAD_REGISTER_PAGES = "hadRegisterPages"
internal const val PARAM_NAME_PAGE_NAME = "pageName"
}
}
```
## /core-ksp/src/main/kotlin/impl/KuiklyCoreAbsEntryBuilder.kt
```kt path="/core-ksp/src/main/kotlin/impl/KuiklyCoreAbsEntryBuilder.kt"
/*
* Tencent is pleased to support the open source community by making KuiklyUI
* available.
* Copyright (C) 2025 THL A29 Limited, a Tencent company. All rights reserved.
* Licensed under the License of KuiklyUI;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* https://github.com/Tencent-TDS/KuiklyUI/blob/main/LICENSE
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package impl
import com.squareup.kotlinpoet.*
/**
* Created by kam on 2022/6/25.
*/
abstract class KuiklyCoreAbsEntryBuilder {
fun build(
pagesAnnotations: List,
): List {
val fileSpecs = mutableListOf()
val fileSpecBuilder = FileSpec.builder(packageName(), entryFileName())
for (comment in getCommonComments(pagesAnnotations)) {
fileSpecBuilder.addComment(comment)
}
fileSpecBuilder
.addComment("\nthis file is generating by ksp\n")
.addComment("please do not modified it!!!")
.addImport("com.tencent.kuikly.core.manager", "BridgeManager")
.addImport(PACKAGE_NAME_NVI, "NativeBridge")
build(fileSpecBuilder, pagesAnnotations)
fileSpecs.add(fileSpecBuilder.build())
return fileSpecs
}
abstract fun build(
builder: FileSpec.Builder,
pagesAnnotations: List
)
abstract fun entryFileName(): String
abstract fun packageName(): String
protected open fun getCommonComments(pagesAnnotations: List): List {
return emptyList()
}
protected fun createKtMethodParameters(): List {
return listOf(
ParameterSpec.builder("methodId", Int::class.asTypeName()).build(),
createNullableAnyParameterSpec("arg0"),
createNullableAnyParameterSpec("arg1"),
createNullableAnyParameterSpec("arg2"),
createNullableAnyParameterSpec("arg3"),
createNullableAnyParameterSpec("arg4"),
createNullableAnyParameterSpec("arg5")
)
}
protected fun createNullableAnyParameterSpec(name: String): ParameterSpec {
return ParameterSpec.builder(name, Any::class.asTypeName().copy(nullable = true)).build()
}
fun FunSpec.Builder.addRegisterPageRouteStatement(pagesAnnotations: List): FunSpec.Builder {
pagesAnnotations.forEach { info ->
addStatement(createRegisterRouter(info.pageName, info.pageFullName))
}
return this
}
private fun createRegisterRouter(pageName: String, pageQualifiedName: String): String {
return "BridgeManager.registerPageRouter(\"$pageName\") {\n" +
"$pageQualifiedName()\n" +
"}"
}
companion object {
const val PACKAGE_NAME_NVI = "com.tencent.kuikly.core.nvi"
const val FUNC_NAME_CALL_KT_METHOD = "callKotlinMethod"
}
}
```
## /core-ksp/src/main/kotlin/impl/PageInfo.kt
```kt path="/core-ksp/src/main/kotlin/impl/PageInfo.kt"
/*
* Tencent is pleased to support the open source community by making KuiklyUI
* available.
* Copyright (C) 2025 THL A29 Limited, a Tencent company. All rights reserved.
* Licensed under the License of KuiklyUI;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* https://github.com/Tencent-TDS/KuiklyUI/blob/main/LICENSE
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package impl
/**
* Created by kam on 2022/11/9.
*/
data class PageInfo(
val pageName: String,
val pageFullName: String,
val packLocal: Boolean = false,
val moduleId: String = ""
)
```
## /core-ksp/src/main/kotlin/impl/submodule/AndroidMultiEntryBuilder.kt
```kt path="/core-ksp/src/main/kotlin/impl/submodule/AndroidMultiEntryBuilder.kt"
/*
* Tencent is pleased to support the open source community by making KuiklyUI
* available.
* Copyright (C) 2025 THL A29 Limited, a Tencent company. All rights reserved.
* Licensed under the License of KuiklyUI;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* https://github.com/Tencent-TDS/KuiklyUI/blob/main/LICENSE
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package impl.submodule
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.FileSpec
import com.squareup.kotlinpoet.FunSpec
import com.squareup.kotlinpoet.KModifier
import com.squareup.kotlinpoet.TypeSpec
import impl.AndroidTargetEntryBuilder
import impl.PageInfo
class AndroidMultiEntryBuilder(private val isMainModule: Boolean, private val subModules: String, private val moduleId: String) : AndroidTargetEntryBuilder() {
override fun build(builder: FileSpec.Builder, pagesAnnotations: List) {
if (!isMainModule) {
builder.addType(
TypeSpec.objectBuilder(entryFileName() + "_" + moduleId)
.addFunction(createSubModuleRegisterPagesFuncSpec(pagesAnnotations))
.build()
)
} else {
builder.addType(
TypeSpec.classBuilder(entryFileName())
.addSuperinterface(ClassName("com.tencent.kuikly.core", "IKuiklyCoreEntry"))
.addProperty(createHadRegisterNativeBridgeProperty())
.addProperty(createDelegateProperty())
.addFunction(createCallKtMethodFuncSpec())
.addFunction(createPagesFuncSpec(pagesAnnotations))
.build()
)
}
}
private fun createSubModuleRegisterPagesFuncSpec(
pagesAnnotations: List,
) : FunSpec {
return FunSpec.builder("triggerRegisterPages")
.addRegisterPageRouteStatement(pagesAnnotations)
.addSubModuleStatement()
.build()
}
private fun createPagesFuncSpec(pagesAnnotations: List): FunSpec {
return FunSpec.builder("triggerRegisterPages")
.addModifiers(KModifier.OVERRIDE)
.addRegisterPageRouteStatement(pagesAnnotations)
.addSubModuleStatement()
.build()
}
private fun FunSpec.Builder.addSubModuleStatement(): FunSpec.Builder {
subModules.split("&").forEach {
val name = it.trim()
if(name.isNotEmpty()) {
addStatement(entryFileName() + "_" + name + ".triggerRegisterPages()")
}
}
return this
}
}
```
The content has been capped at 50000 tokens, and files over NaN bytes have been omitted. The user could consider applying other filters to refine the result. The better and more specific the context, the better the LLM can follow instructions. If the context seems verbose, the user can refine the filter using uithub. Thank you for using https://uithub.com - Perfect LLM context for any GitHub repo.