``` ├── .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

Kuikly Logo

[English](./README.md) | 简体中文 | [官网](https://framework.tds.qq.com/) ## 项目介绍 `Kuikly` 是基于Kotlin Multiplatform的UI与逻辑全面跨端综合解决方案,由腾讯大前端领域Oteam(公司级)推出,旨在提供一套`一码多端、极致易用、动态灵活的全平台高性能开发框架`。目前已支持平台: - [X] Android - [X] iOS - [ ] 鸿蒙(5月开源) - [ ] Web(Q2开源) - [ ] 小程序(Q2开源) `Kuikly` 推出后受到业务广泛认可,已应用于 QQ、QQ 音乐、QQ 浏览器、腾讯新闻、搜狗输入法、应用宝、全民K歌、酷狗音乐、酷我音乐、自选股、ima.copilot、微视等多款产品。 ## 特点 - 跨平台:基于 Kotlin 跨平台实现多平台一致运行,一码五端 - 原生性能:运行平台原生编译产物(.aar/.framework) - 原生开发体验:原生 UI 渲染、原生开发工具链、Kotlin 原生开发语言 - 轻量:SDK 增量小(AOT模式下,Android:约 300 KB,iOS:约 1.2 MB) - 动态化:支持编译成动态化产物 - 多开发范式:声明式&响应式开发范式,支持自研 DSL 和 Compose DSL(开发中) ## 项目结构 ```shell . ├── core # 跨平台模块,实现各个平台响应式 UI、布局算法、Bridge 通信等核心能力 ├── src ├── commanMain # 跨平台共享代码、定义跨平台接口 ├── androidMain # Android 平台实现代码 (aar) ├── jvmMain # 泛 JVM 平台代码(不涉及 Android API)(jar) ├── iosMain # iOS 平台实现代码(framework) ├── core-render-android # android 平台的渲染器模块 ├── core-render-ios # iOS 平台的渲染器模块 ├── core-annotations # 注解模块,定义业务注解 @Page ├── core-ksp # 注解处理模块,生成 Core 入口文件 ├── buildSrc # 编译脚本,用于编译、打包、分包产物相关脚本 ├── demo # DSL 示例代码 ├── androidApp # Android 宿主壳工程 └── iosApp # iOS 宿主壳工程 ``` ## 系统要求 - iOS 12.0版本及以上 - 安卓 5.0版本及以上 - HarmonyOS Next 5.0.0(12) 版本及以上 - Kotlin版本 1.3.10 版本及以上 ## 快速上手 - [快速体验](https://kuikly.tds.qq.com/%E5%BF%AB%E9%80%9F%E5%BC%80%E5%A7%8B/hello-world.html) - [接入指引](https://kuikly.tds.qq.com/%E5%BF%AB%E9%80%9F%E5%BC%80%E5%A7%8B/overview.html) - [组件特性](https://kuikly.tds.qq.com/API/%E7%BB%84%E4%BB%B6/override.html) ## 源码构建 ### 编译环境 参照[环境搭建](https://kuikly.tds.qq.com/%E5%BF%AB%E9%80%9F%E5%BC%80%E5%A7%8B/env-setup.html)进行配置 ### 运行安卓 APP 在构建 Android App 之前,请确保完成了环境准备 1. 使用 `Android Studio` 打开 `KuiklyUI` 项目根目录,完成 sync 2. Configuration 选择 androidApp,Run 'androidApp' ### 运行iOS APP 在构建 iOS App 之前,请确保完成了环境准备 1. `cd` 到 `iosApp` 2. 执行 `pod install --repo-update` 3. 使用 `Android Studio` 打开 `KuiklyUI` 项目根目录,完成 `sync` 4. Configuration 选择 iOSApp,Run 'iOSApp' 或者使用 XCode 打开 KuiklyUI/iosApp 目录,`Run` ### Kotlin多版本支持 KuiklyUI目录下有各个`Kotlin`版本的gradle配置项 命名规则为 `x.x.xx.gradle.kts`,其中默认使用的是`Kotlin: 1.7.20` 同时,也提供各个版本的测试发布脚本,你可以`x.x.xx_test_publish.sh`构建`kuikly`的本地产物。 > `Kotlin: 1.3.10/1.4.20` 需要切换 `jdk11` 上述任一平台构建成功后,即可通过修改Core、Render、Demo,体验`Kuikly`开发。 ## Roadmap [Roadmap(2025)](https://kuikly.tds.qq.com/%E5%8D%9A%E5%AE%A2/roadmap2025.html) ## 贡献指南 欢迎各位开发者为 `Kuikly` 提出问题或发起 PR,建议你在为 `Kuikly` 贡献代码先阅读 [贡献指引](CONTRIBUTING.md)。 ## 行为准则 请注意,本项目的所有参与者都应遵守我们的[行为准则](CODE_OF_CONDUCT.md)。参与即表示您同意遵守其条款。 ## 常见问题 [Kuikly QA汇总](https://kuikly.tds.qq.com/QA/kuikly-qa.html) ## 贡献者 - 特别感谢首批贡献者tom(邱良雄)kam(林锦涛)watson(金盎),不仅在大前端领域主导 `Kuikly` 跨端方案孵化探索,而且率先在QQ业务落地。 - 感谢以下核心贡献者对Kuikly持续建设维护与发展优化:
tom kam watson rocky jonas ruifan pel layen bird zealot zhenhua vinney xuanxi arnon alexa allens eason ## 欢迎关注交流 欢迎扫码下方二维码关注最新动态或咨询交流。

腾讯端服务微信公众号
TDS
TDS Framework 微信公众号
TDS Framework
在线咨询
在线咨询

## /README.md

Kuikly Logo

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
TDS Framework WeChat Official Account
TDS Framework WeChat Official Account
Online Support
Online Consult

## /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"